Effective C#: Item 1 Always use properties instead of accessible data members
Item 1: 当设计类时,永远用Property, 而不是可直接访问的Data Member
在C#里,Property已经晋升为一类公民。如果你的类里还有Public的变量,Stop! 如果你还在手写get and set 方法,Stop! Property在不破坏你类的封装的情况下,仍可以把类的data member变成public interface的一部分。访问Property的方式和访问data member的方式一样,但Property是用methods实现的。
有些类的成员只能用data最好的表示,比如:你一个客户的名字,一个点的坐标,等等。而Property就是用来欺骗使用你类的客户,让它们错误的认为它们在访问你类的public变量。你还可以通过Property的实现方法来控制Property的访问。
.Net Framework假定你使用Property来让外界访问你类里想让外界访问到的data member (也就是public data member) 。实际上也是这样的,因为.Net的data binding只支持Property,而不支持public data member的访问。Data binding的目的就是把一个object的Property绑定到一个用户界面的control上,web control或者windows form control. Data binding是通过reflection来实现的,如下例:
textBoxCity.DataBindings.Add("Text", address, "City");
这段code就是把textBoxCity的Text Property绑定到address这个object的City Property上。如果你把address的City Property改成public data member,这段code是不会运行的。因为.Net Framework Class Library的设计者不支持你的这种行为,他们认为public data member是非常不好的行为和习惯,所以他们不会支持,他们想让你遵从正确的Object Oriented设计方法。Data binding也不会去找get and set methods,所以一定要用Property,而不是传统的get and set methods.
你也许要说,data binding只适用于那些含有要显示在用户界面的元素的类。但实际情况并不是这样,对于你所有的类,都要使用Property而不是public data member。因为当有新的需求时,通过修改Property的实现方法来适应这个新的需求,要比在你的程序里修改所有的public data member去适应这个需求容易太多了。比如说你以前定义了一个类customer,现在你发现由于当初的粗心没有强制customer姓名不能为空,如果你使用了Property,你可以非常轻松的添加一个检查机制,如下面这段code:
public class Customer
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (( value == null) || ( value.Length == 0 ))
{
throw new ArgumentException( "Name can not be blank", "Name");
}
_name = value;
}
}
//...
}
如果你使用了public data member,你就要找遍你的程序,在每个地方都修改,那样就很愚蠢了。而且浪费了无数青春好时光。
因为Property是用methods实现的,所以添加multi-threaded的支持是非常方便的。比如想要添加同步访问的支持:
public string Name
{
get
{
lock( this )
{
return _name;
}
}
set
{
lock( this )
{
_name = value;
}
}
}
因为Property是用methods实现的,所以它拥有methods所拥有的一切。Property可以被定义为virtual:
public class Customer
{
private string _name;
public virtual string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
//...
}
显而易见,你也可以把Property扩展为abstract,甚至成为interface的一部分。
public interface INameValuePair
{
object Name
{
get;
}
object Value
{
get;
set;
}
}
你当然也可以扩展出const和nonconst版本的interface。
public interface IConstNameValuePair
{
object Name
{
get;
}
object Value
{
get;
}
}
public interface INameValuePair
{
object Value
{
get;
set;
}
}
//usage:
public class Stuff : IConstNameValuePair, INameValuePair
{
private string _name;
private object _value;
#region IConstNameValuePair Members
public object Name
{
get
{
return _name;
}
}
object IConstNameValuePair.Value
{
get
{
return _value;
}
}
#endregion
#region INameValuePair Members
public object Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
#endregion
}
如前所述,Property是访问内部数据的method的扩展,它拥有member function的一切特性。
因为实现Property访问的方法get and set是独立的两个method,在C# 2.0中,你可以给它们定义不同的访问级别,来更好的控制类成员的可见性,如下例:
public class Customer
{
private string _name;
public virtual string Name
{
get
{
return _name;
}
protected set
{
_name = value;
}
}
//...
}
Property的语法已经超越了单纯的data field。如果你的类包含indexed item,你可以使用indexer(参数化的Property),你可以创建一个可返回一个序列元素的Property,如下例:
public int this [ int index ]
{
get
{
return _theValues [ index ];
}
set
{
_theValues [ index ] = value;
}
}
//usage:
int val = MyObject[ i ];
indexer和单元素Property有着相同的特性。一维的indexer可以用于data binding,二维和多维的indexer可以用来实现其他的数据结构,比如map和dictionary:
public Address this [ string name ]
{
get
{
return _theValues[ name ];
}
set
{
_theValues[ name ] = value;
}
}
多维的indexer的每个axis上的数据类型可以相同,也可以不同:
public int this [ int x, int y ]
{
get
{
return ComputeValue( x, y );
}
}
public int this [ int x, string name ]
{
get
{
return ComputeValue( x, name );
}
}
所有的indexer都必须也只能用this来定义,所以参数表相同的indexer,每个类最多只能有一个。
因为使用Property和data member对于数据访问的code没有什么区别,比如:
public class Customer
{
public string Name;
//...
}
在这个类中使用了public data member,数据访问的code如下:
string name = CustomerOne.Name;
CustomerOne.Name = "customer name";
你也许会想,如果在以后的修改中,用Property来代替public data member是可行的,因为数据访问的code相同,但实际上这是行不通的。确实,访问Property和访问data member的code是相同的,但Property不是data,访问Property所产生的IL code和数据访问的IL code是不一样的。所以访问Property和访问data member只具有code兼容性,而不具有binary的兼容性。如果有兴趣,你可以使用Reflector (http://www.aisto.com/roeder/dotnet/ )来分析使用Property和public data member的类。
你会发现在使用Property的类中,存在.property directive,这个directive定义了Property的类型以及get and set实现方法。Get and set都被标注为hidebysig, specialname。也就是说它们不能被C#源代码直接调用,它们也不是正是的类型定义。你只能通过Property来访问它们。
C#的编译器会根据类的情况(是用Property还是data member)来自动产生不同的IL code。如上所述,访问Property和访问data member只具有code兼容性,而不具有binary的兼容性。所以,如果你改变最初的设计,用Property来代替public data member的话,你必须重新编译整个程序。这使得升级已经部署的程序或assembly是非常的麻烦。
那么两种实现谁的效率更好呢?Property确实不会比public data member快,但也不一定会慢。因为JIT对Property的存取方法set and get进行inline的优化。这时,Property和public data member的效率是一样的。即使Property的存取方法没有被inline优化,它和public data member的效率差别也只是一个可以忽略的function call。只有在很少的情况下,这种差别才可以被测量出来。
总而言之,当你想让你类内部的数据被外界访问到时(不管是public还是protected),一定要用Property。对于序列和字典,使用indexer。你类的data member永远应该是private,绝无例外。使用Property,你可以得到如下好处:
1.Data binding支持
2.对于需求变化有更强的适应性,更方便的修改实现方法
记住,现在多花1分钟使用Property,会在你修改程序以适应设计变化时,为你节约n小时。
本系列文章只是作者读书笔记,版权完全属于原作者 (Bill Wagner),任何人及组织不得以任何理由以商业用途使用本文,任何对本文的引用和转载必须通知作者:zphillm@hotmail.com