目录
属性
属性是代表类的实例或类中的一个数据项的成员。属性指的是一组两个匹配的、称为访问器的方法:
public 返回类型 标识符
{
set 访问器为属性赋值
get 访问器为属性获取值
}
- 注意: public 这样的修饰符是可选的,可以选择私有的,这样就不可以在类外直接访问 set 和 get 访问器了。但是间接 的。
- 注意: 访问器不能被直接调用。
set访问器总是:
- 拥有一个单独的、隐式的值参,名称为value, 其类型与属性的相同。
- set 访问器返回类型为void。
get访问器总是:
- 没有参数。
- 拥有一个与属性类型相同的返回类型。属性返回的类型要跟你要返回的字段的返回类型相同。
属性跟字段的相同点:
- 它是命名的实例成员
- 它有类型
- 它可以被赋值和读取。
属性跟字段的不相同点:
- 属性属于一个成员函数
- 它不为数据存储分配内存
- 它执行代码
访问器的重点如下:
- get 访问器的所有执行路径都必须包含一条return 语句,返回一个属性类型的值。
- 访问器 set 和 get 可以任何顺序声明, 并且只能有这两个方法,不能有其他的。
- 要想不定义属性的某个访问器,可以忽略该访问器的声明。
- 两个访问器中至少有一个必须定义, 否则编译器会产生错误信息。
- 属性它是本身是没有任何存储的,并且它可以被声明为 static。
使用属性
写入和读取属性的访问器时,会被隐式调用:
- 要写入一个属性,在赋值语句的左边使用属性的名称。
- 要读取一个属性,把属性的名称用在表达式中。
注意: 不能显式地调用访问器,就跟调用函数那样, 会出现编译错误。
namespace Ch05Ex03
{
class D // 属性本身不分配内存
{
private double name = 3.14; // 为字段分配内存
public double MyValue
{
set
{
name = value; //返回类型为void
}
get
{
return name; // 返回类型是属性的类型,属性的类型也要跟字段的数据类型一致
}
}
}
class Program
{
static void Main(string[] args)
{
D myD = new D();
myD.MyValue = 5.666; //给访问器中的字段赋值
WriteLine(myD.MyValue); //输出赋值后的字段的值
var tmp = myD.MyValue;
WriteLine(tmp); //输出赋值后的字段的值
var tt = myD.get(); // 错误, 不能这样显式调用
myD.set(5.3); // 错误, 不能这样显式调用
ReadKey();
}
}
}
我们经常将类中的字段声明private以封装该字段, 然后声明一个public的属性来控制从类的外部对该字段的访问。和属性关联的字段称为后备字段或后备存储。
属性和后备字段需要注意的问题有:
- 一种约定是两个名称使用相同的内容, 但字段使用Camel 大小写, 属性使用 Pascal 大小写。
- 另一种是字段使用Camel 大小写,并以下划线开始,属性使用 Pascal 大小写。
private double nameField = 3.14; //第一种约定
public double NameFild
{
set
{
nameField = value;
}
get
{
return nameField;
}
}
private double _nameField = 3.14; // 第二种约定
public double NameFild
{
set
{
_nameField = value;
}
get
{
return _nameField;
}
}
利用 set 和 get 访问器 执行其它运算
属性访问器不仅仅只可以对关联的字段传入传出数据, 还可以执行任何计算,或者不执行任何计算。但是get访问器必须返回一个属性类型的值。
下面看一个更有用的示例:
namespace Ch05Ex03
{
class D
{
private double _nameField = 3.14;
public double NameFild
{
set
{
_nameField = value > 100 ? 100 : value; //执行计算
}
get
{
return _nameField;
}
}
}
class Program
{
static void Main(string[] args)
{
D myD = new D();
myD.NameFild = 200; //给访问器中的字段赋值
WriteLine(myD.NameFild); //输出赋值后的字段的值
ReadKey();
}
}
}
只读和只写属性
- 要想不定义属性的某个访问器, 可以忽略该访问器的声明。
- 只有get访问器的属性是只读属性。它是安全的, 只传出数据。
- 只有set访问器属性是只写属性, 它是安全的, 把一项数据从类的外部传入类, 而不允许太多访问方法。
使用属性比使用公共字段更好
属性比公共字段更好,理由如下:
- 因为属性是函数成员,而不是数据成员,因此它们允许您处理输入和输出,而公共字段则无法处理。
- 属性可以只读或只写,而字段不行。
- 编译后的变量和属性语义不同。
自动实现属性
自动实现属性: 允许只声明属性而不声明后备字段。 编译器会为你创建隐藏的后备字段, 并且自动挂接到get 和 set 访问器上。
自动实现属性的要点如下:
- 不声明后备字段—— 编译器根据属性的类型分配存储。
- 不能提供访问器的方法体—— 它们必须被简单地声明为分号。 get 相当于简单的内存读, set 相当于简单的内存写。
- 除非通过访问器,否则不能访问后备字段。 因为不能用其他的方法访问它, 所以单独实现某个只读或只写属性没有意义, 因此必须同时提供读写访问器。
- 自动实现的属性它本身会分配内存。
namespace Ch05Ex03
{
class D
{
public double NameFild //分配内存
{
set;get;
}
}
class Program
{
static void Main(string[] args)
{
D myD = new D();
myD.NameFild = 200; //给访问器中的字段赋值
WriteLine(myD.NameFild); //输出赋值后的字段的值
ReadKey();
}
}
}
除了方便之外,自动实现的属性还允许您轻松地插入一个属性,本来那里您可能想要声明一个公共字段的。
静态属性
属性也可以声明为 static, 静态属性的访问器和所有静态成员一样,具有以下特点:
- 不能访问类的实例成员,但是可以访问类的静态实例成员; 但是静态属性能被实例成员所访问。
- 不管类是否有实例,它们都是存在。
- 当从类的外部访问时,必需使用类名引用,而不是实例名。
namespace Ch05Ex03
{
class D
{
public static int MyValue
{
get; set;
}
public void PrintValue()
{
WriteLine($"输出值为:{MyValue}");
}
}
class Program
{
static void Main(string[] args)
{
WriteLine($"先输出静态属性的值:{D.MyValue}");
D.MyValue = 10; // 在类的外部设置静态属性的值
WriteLine($"再输出静态属性的值:{D.MyValue}");
ReadKey();
}
}
}
再看一个示例程序:
namespace Ch05Ex03
{
class D
{
int aa = 12;
static int bb;
public static void Show()
{
WriteLine($"输出bb的值:{bb}");
//WriteLine($"输出bb的值:{aa}"); 错误,静态成员函数不可以输出非静态成员数据
}
public void PrintValue()
{
WriteLine($"输出bb的值:{bb}"); // 非静态的成员函数可以输出静态字段的值
WriteLine($"输出aa的值:{aa}");
}
public static int MyValue
{
set
{
//aa = value; //错误,静态属性不可以访问非静态字段
bb = value; //正确,静态属性可以访问静态字段
}
get
{
return bb;
}
}
}
class Program
{
static void Main(string[] args)
{
WriteLine($"先输出静态属性的值:{D.MyValue}");
D.MyValue = 100; // 在类的外部设置静态属性的值
WriteLine($"再输出静态属性的值:{D.MyValue}");
D myD = new D();
myD.PrintValue();
ReadKey();
}
}
}
输出结果为:
先输出静态属性的值:0
再输出静态属性的值:100
输出bb的值:100
输出aa的值:12