1.什么是属性
定义:是字段和方法的交集—— 看起来像字段,用起来像方法。
访问属性所用的语法和访问字段一样。然而,编译器会将这种字段风格的语法自动转换成对特定访问器的调用。
访问器:取值和献值方法统称为访问器方法。两个方法有时也称为get访问器和set访问器,或者getter 和setter。
属性的声明如下所示:
AccessModifier Type ProperName
{
get
{
//取值代码
}
set
{
//赋值代码
}
}
属性可以包含两个代码块,分别以get和set关键字开头。其中,get 块包含读取属性时执行的语句,set块包含在向属性写入时执行的语句。属性的类型指定了由get和set访问器读取和写入的数据的类型。
属性和字段名称的注意事项:
例如一下代码,它实现了名为Employee的类。EmployeeID 属性提供对私有字段employeeID字段的公共访问,
class Employee
{
private int employeeID;
public int EmployeeID
{
get { return this. EmployeeID; }
set { this. EmployeeID = value; }
}
}
代码编译没有问题,但每次访问EmployeeID属性都会抛出StackoverflowException异常,这是由于get和set访问器不小心引用属性(以大写字母E开头)而不是私有字段(小写e),这造成了无限递归,最终造成可用内存被耗尽。这种bug是很难发现的!有鉴于此,我们以下划线开头命名为属性提供数据的私有字段。这样可以更加明显地和属性进行区分。除此之外的其他所有私有字段还是使用不以下划线开头的camelCase 标识符。
1.1使用属性
在表达式中使用属性时,要么从中取值,要么向其赋值。下例从ScreenPosition结构的X和Y属性中取值:
ScreenPosition origin=new ScreenPosition(e=0, 0);
int xpos =origin.x; // 实际调用origin.X. get
int ypos=origin.Y; // 实际调用origin.Y.get
注意,现在属性和字段是用相同的语法来访问。从属性取值时,编译器自动将字段风格的代码转换成对属性的get访问器的调用。类似地,向属性赋值时,编译器自动将字段风格的代码转换成对该属性的set访问器的调用:
origin.X=40;//实际调用origin.X.set. value 设为40
origin.Y=100; //实际调用origin.Y.set, value 设为100
如前所述,要赋的新值通过value变量传给set访问器。“ 运行时”自动完成传值。
还可同时对属性进行取值和赋值。在这种情况下,get和set访问器都会被用到。例如,编译器自动将以下语句转换成对get和set访问器的调用:
origin.X +=10;
提示:可采取和声明静态字段及方法一样的方式声 明静态属性。访问静态属性时,要附加类或结构名称作为前缀,而不是附加类或结构的实例名称作为前缀。
1.2只读属性
可以声明只包含get 访问器的属性,这称为只读属性。例如,以下代码将ScreenPosition结构的X属性声明为只读属性:
struct ScreenPosition
{
private int. x;
public int X
{
get { return this. X; }
}
}
X属性不含set访问器,向X写入会报告编译时错误,例如:
origin.x =140; //编译时错误
1.3只写属性
类似地,可声明只包含set 访问器的属性,这称为只写属性。例如,以下代码将ScreenPosition结构的x属性声明为只写属性:
struct ScreenPosition
{
private int. x;
...
public int X
{
set { this. x = rangeCheckedX(value); }
}
}
X属性不包含get访问器。所以,读取X会报告编译时错误,例如:
Console.WriteLine(origin.X);//编译时错误
origin.X = 200; //编译通过
origin.X += 18;//编译时错误
注意:只写属性适合对密码这样的数据进行保护。理想情况下,实现了安全性的应用程
序允许设置密码,但不允许读取密码。登录时用户要提供密码。登录方法将用户提供的密码与存储的密码比较,只返回两者是否匹配的消息。
1.4属性的可访问性
声明属性时要指定可访问性(public, private 或protected)。但在属性声明中,可为get和set访问器单独指定可访问性,从而覆盖属性的可访问性。例如,下面这个版本的ScreenPosition结构将x和Y属性的set访问器定义成私有,而get访问器仍为公共(因为属性是公共的):
struct ScreenPosition
{
private int _x, _y;
…
public int X
{
get { return this._x; }
private set { this._x=rangeCheckedX(value); }
}
public int Y
{
get { return this._y; }
private set { this._y=rangeCheckedY(value); }
}
…
}
为两个访问器定义不同的可访问性时,必须遵守以下规则。
(1)只能改变 一个访问器的可访问性。例如,将属性声明为公共,但将它的两个访问器都声明成私有是没有意义的。
(2)访问器 的访问修饰符(也就是public, private或者protected)所指定的可访问性在限制程度上必须大于属性的可访问性。例如,将属性声明为私有,就不能将get访问器声明为公共(相反,应该属性公共,set访问器私有)。
2.理解属性的局限性
属性在外观、 行为和感觉上都像字段。但属性本质上是方法而不是字段。另外,属性存在以下限制。
(1)只有在结构或类初始化好之后,才能通过该结构或类的属性来赋值。下例的代码非法,因为结构变量location尚未使用new来初始化:
ScreenPosition location;
location.X = 40; //编译时错误,location 尚未赋值
(2)不能将属性作为ref或out参数值传给方法:但可写的字段能作为ref或out参数值传递。这是由于属性并不真正指向一个内存位置:相反,它指向的是一个访问器方法,例如:
MyMethod(ref location.X); // 编译时错误
(3)属性最多只能包含一个get和一个set访问器。不能包含其他方法、字段或属性。
(4)get和set访问器不能获取任何参数。要赋的值会通过内建的、隐藏的value变量自动传给set访问器。
(5)不能声明const 属性,例如:
const int X{ get { ... }set { ...} } //编译时错误
3.在接口中声明属性
接口除了能定义方法,还能定义属性。
例如:
interface IScreenPosition
{
int X{get;set;}
int Y{get;set;};
}
实现该接口的任何类或结构都必须实现X和Y属性,并在属性中定义get和set访问器,例如:
class ScreenPositlon : IScreenPosition
{
public int X
{
get{...}
set{...}
}
public int Y
{
get{...}
set{…}
}
…
}
用途:例如可以用属性替代方法
4.生成自动属性
C#语言的设计者知道程序员都是“大忙人”,不该花时间写多余的代码。所以,C#编译器现在能自动为属性生成代码,如下所示:
class Circle
{
public int Radius{ get; set; }
…
}
在这个例子中,Circle 类包含名为Radius的属性。除了属性的类型,不必指定这个属性是如何工作的一get 和set访问器都是空白的。C#编译器自动将这个定义转换成私有字段以及一个默认的实现,如下所示:
class Circle
{
private int _radius;
public int Radius{
get
{
return this._ radius;
}
set
{
this._radius = value;
}
}
…
}
所以,只需写很少的代码就能实现简单属性。以后如果添加了额外的逻辑,也不会干扰现有的任何应用程序。
参考书籍:《Visual C#从入门到精通》