Effective C# Item1:Always Use Properties Instead of Accessible Data Members
在C#语言中,属性的地位已经从编程习惯提升至语言的特性(这里用到first-class language。这个含义可以参见wikipedia)。你不必为你的类型创建公有数据成员,也不必手工创建属性中的get和set方法。属性让你可以通过公有接口暴露数据成员,同时又可以提供oo环境中你所希望的封装。属性在可达性上就像一个公有数据成员,但是实现上像一个方法。
对于一些类型的成员来讲,例如客户的姓名、xy坐标等,数据型是它们最好的表现形式。属性允许你创建一个接口,使得对它们的操作可以像公有数据成员一样,同时又保留了使用成员函数一样的优势。
在.net framework中已经假设你会为你的公有数据成员使用属性。实际上,在.net framework的数据绑定中支持的也是属性而不是公有的数据成员。不论是web控件还是win form控件,都是通过属性来实现数据绑定的。下面的例子中,数据绑定机制通过反射在已知类型中寻找特定属性:
上面的代码将adress中的City属性绑定到名为textBoxCity控件的Text属性上。如果City不是属性而是adress中的一个公有成员,上面的代码就不能实现。
当遇到新的需求或行为变更时,属性更容易修改。例如你现在决定客户的名字不能为空,那你只要在对应属性的set中添加判断就可以了。
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if ((value == null) || (value.Length == 0))
{
throw new ArgumentException();
}
_name = value;
}
}
}
但是如果你使用的是公有数据成员,那你必须检查你所有的代码并一一添加判断,那将消耗大量的时间。
属性对于多线程的操作支持也很方便。一般的只要在get和set时加锁就可以了。
{
private string _name;
public string Name
{
get
{
lock (this)
{
return _name;
}
}
set
{
lock (this)
{
_name = value;
}
}
}
}
属性拥有方法的语言特性。属性可以为虚拟成员(virtual)
{
private string _name;
public virtual string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
同样属性可以是抽象的(abstract)或是接口定义中的一部分
{
string Name
{
get;
}
}
我们还可以通过接口属性来创建常量
{
string Name
{
get;
//没有set
}
}
public class Customer : ICustomer
{
string _name;
ICustomer Members
}
在C#2.0中可以对get和set设定不同的可达性,这使得程序可以更加可控。下例中对Name属性的set为protected
{
private string _name;
public virtual string Name
{
get
{
return _name;
}
protected set
{
_name = value;
}
}
}
属性不仅可以应用在简单数据类型领域。你可以使用索引来创建返回序列项的属性:
{
private string[] _name;
public string this[int index]
{
get
{
return _name[index];
}
set
{
_name[index] = value;
}
}
}
索引拥有所有简单属性所包含的特性,可以在索引内部进行校验和计算,可以为虚拟或抽象成员,可以在接口中声明,可以设为只读或读写。一维整型参数的索引可以参与数据绑定,其他非整型参数索引可以定义映射(map)和字典(dictionary)。
C#中你可以创建多维索引如下例所示:
{
private string[,] _name;
public string this[int index1,int index2]
{
get
{
return _name[index1,index2];
}
set
{
_name[index1,index2] = value;
}
}
//当然这里string型的index2是不行的了,我只是举个例子而已
public string this[int index1, string index2]
{
get
{
return _name[index1, index2];
}
set
{
_name[index1, index2] = value;
}
}
}
注意所有的索引在声明时都使用this关键字。索引不能自定义命名。在一个类型中,同样参数的索引最多只能有一个。
虽然属性有很多好处,但是有人会依旧先创建公有成员,当需要用到属性的特性时在将其修改为属性。这看起来好像是蛮有道理的,不过这是不好的做法。对于属性和公有成员来说,它们的实现代码是一致的,这会为我们造成误解,认为只要修改公有成员为属性就万事大吉了。实际上这种说法只是部分的正确。
在C#的源码中使用属性和公有成员的代码虽然相同,但通过编译器生成的中间语言却是不同的。它们是代码兼容的,重新编译就可以正确运行,但却不是二进制兼容的。(对于二进制兼容这点,我也不是十分清楚… 我觉得这种兼容是应该与开发平台无关的,因此上面的改变才会引发不兼容性。望指教)。
在效率上虽然使用属性并不会比使用公有数据成员快,但是一般情况下也不会比它慢。属性在JIT编译器编译时做为内联函数(inline)处理,相当程度上减少了效率的损失。
P.S. Item1还是比较简单的,就不写什么了。在我刚参加工作的时候就在老一辈的指导下养成了这个习惯,虽然声明时候会麻烦一些,但是比起事后的修改工作实在是好太多了… 另外可将其在不同权限下分别设为只读或读写非常实用。
回到目录