Effective C# 1:Always Use Properties Instead of Accessible Data Members

来源:互联网 发布:samba 端口 被屏蔽 编辑:程序博客网 时间:2024/06/06 01:50

Item1: Always Use Properties Instead of Accessible Data Members

条款1:总是使用属性替代可访问的数据成员

The C# language promoted properties from an ad-hoc convention to afirst-class language feature. If you're still creating public variables in yourtypes, stop now. If you're still creating get and set methods by hand, stopnow. Properties let you expose data members as part of your public interfaceand still provide the encapsulation you want in an object-oriented environment.Properties are language elements that are accessed as though they are datamembers, but they are implemented as methods.

C#将属性从一些特殊习惯(ad-hocWikiAd hoc is a Latin phrase which means "for this [purpose]". Itgenerally signifies a solution designed for a specific problem or task,non-generalisable, and which cannot be adapted to other purposes.)提升为第一等的语言特性。如果你现在在你的类型中,仍然在创建公共变量,立刻住手吧!如果你现在仍然在手工创建get/set方法,立刻住手吧!属性让你将数据成员作为公共接口的一部分暴露出来,同时,依然提供了面向对象环境中你想要的封装性。属性是这样的一种语言元素:像数据成员一样被访问;像方法(函数)一样被实现。

Some members of a type really are best represented as data: the nameof a customer, the x,y location of a point, or last year's revenue. Propertiesenable you to create an interface that acts like data access but still has allthe benefits of a function. Client code accesses properties as though they areaccessing public variables. But the actual implementation uses methods, inwhich you define the behavior of property accessors.

     一些类型的成员确实适合被表示成数据:客户的名字,点的xy坐标,去年的收入。属性使你可以创建一个这样的接口:可以像数据一样被访问,但是依然有方法的所有优点,客户代码像访问公共变量一样访问属性。但是属性的实际实现使用了方法,在方法中,你可以定义属性访问符的行为。

The .NET Framework assumes that you'll use properties for yourpublic data members. In fact, the data binding code classes in the .NETFramework support properties, not public data members. Data binding ties aproperty of an object to a user interface control, either a web control or aWindows Forms control. The data binding mechanism uses reflection to find anamed property in a type:

.NET框架假设你会为公共数据成员使用属性。事实上,.NET框架中的数据绑定类支持属性,而不支持公共数据成员。数据绑定将一个对象的属性与一个用户接口控件结合起来,这个控件可以是Web控件,也可以是Windows Form控件。数据绑定机制使用反射在一个类型中寻找已命名的属性。

textBoxCity.DataBindings.Add("Text", address, "City");

The previous code binds the Text property of the textBoxCity controlto the City property of the address object. (See Item 38for more details.) It will not work with a public data member named City; theFramework Class Library designers did not support that practice. Public datamembers are bad practice, so support for them was not added. Their decisionsimply gives you yet another reason to follow the proper object-orientedtechniques. Let me add a quick note for the grizzled C++ and Java programmers:The data binding code does not look for get and set functions, either. Youshould be using properties instead of the convention of get_ and set_ functionsin those languages.

上面的代码,将textBoxCity控件的Text属性与address对象的City属性绑定在一起(更多细节请看Item38)。如果City是公共数据成员的话,这是不能工作的;框架类库的设计者不支持那样做。使用公共数据成员是个坏的实践,因此在FCL中没有加入对此的支持。他们的决定很简单的就告诉你另外一个理由来遵从恰当的面向对象技术。让我给沮丧的C++/Java程序员一个快速的注释吧:数据绑定也不去寻找get/set函数。你应该使用属性来代替在那些语言中养成的get_/set_函数的习惯。

Yes, data binding applies only to those classes that containelements that are displayed in your user interface logic. But that doesn't meanproperties should be used exclusively in UI logic. You should still be usingproperties for other classes and structures. Properties are far easier tochange as you discover new requirements or behaviors over time. You might soondecide that your customer type should never have a blank name. If you used apublic property for Name, that's easy to fix in one location:

是的,数据绑定仅仅应用于这样的类中:包含需要在用户接口逻辑上显示的元素。但是这并不意味着属性只能被用于UI逻辑,在其他的类或者结构中你也应该使用属性。过了一段时间,当你发现有新需求或新行为时,属性更加容易进行改变。你可能很快就决定Customer类型不应该再有一个空白名字。如果你使用公共的Name属性,那就很容易了,只需要在一个地方进行修改:

  1. public class Customer
  2. {
  3.     private String name;
  4.  
  5.     public String Name
  6.     {
  7.         get { return name; }
  8.         set
  9.         {
  10.             if ((value == null) || (value.Length == 0))
  11.             {
  12.                 throw new ArgumentException("Name cannot be blank""Name");
  13.             }
  14.             name = value;
  15.         }
  16.       }
  17. }

 

If you had used public data members, you're stuck looking for everybit of code that sets a customer's name and fixing it there. That takes moretime much more time.

如果你已经使用了公共数据成员,就得不停的去寻找任何设置Customer名字的地方,进行修正,那会花费很多的时间,相当多。

Because properties are implemented with methods, addingmultithreaded support is easier. Simply enhance the implementation of the getand set methods to provide synchronized access to the data:

因为属性是用方法来实现的,所以添加多线程支持很容易。对get/set方法进行简单的加强就可以提供对数据同步访问的支持:

  1. public class Customer
  2. {
  3. private String name;
  4.  
  5.     public String Name
  6.     {
  7.         get
  8.         {
  9.             lock (this)
  10.             {
  11.                 return name;
  12.             }
  13.         }
  14.         set
  15.         {
  16.             lock (this)
  17.             {
  18.                 name = value;
  19.             }
  20.         }
  21.       }
  22. }

Properties haveall the language features of methods. Properties can be virtual:

属性具有方法的所有语言特性。属性可以是Virtual的:

  1.     public class Customer
  2.     {
  3.         private String name;
  4.  
  5.         public virtual String Name
  6.         {
  7.             get { return name; }
  8.             set { name = value; }
  9.         }
  10.   }

It's also easyto see that you can extend properties to be abstract or even part of aninterface definition:

很容易看出来,可以将属性扩展为abstract,甚至是接口的一部分:

  1. public interface INameValuePair

  2. {

  3.     object Name

  4.     {

  5.         get;

  6.     }

  7.  

  8.     object Value

  9.     {

  10.         get;

  11.         set;
  12.   }

  13. }

Last, butcertainly not least, you can use interfaces to create const and nonconstversions of an interface:

最后一条,但是不意味着最不重要,可以使用接口创建const nonconst版本的接口:

  1. public interface IConstNameValuePair
  2. {
  3.     object Name
  4.     {
  5.         get;
  6.     }
  7.  
  8.     object Value
  9.     {
  10.         get;
  11.     }
  12. }
  13.  
  14. public interface INameValuePair
  15. {
  16.     object Value
  17.     {
  18.         get;
  19.         set;
  20.     }
  21. }
  22.  
  23. //usage
  24. public class Stuff : IConstNameValuePair, INameValuePair
  25. {
  26.  
  27.     private String name;
  28.     private object value;
  29.  
  30.     public object Name
  31.     {
  32.         get { return name; }
  33.     }
  34.  
  35.     object IConstNameValuePair.Value
  36.     {
  37.         get { return value; }
  38.     }
  39.  
  40.     public object Value
  41.     {
  42.         get { return value; }
  43.         set { this.value = value; }
  44.     }
  45. }

Properties arefull-fledged, first-class language elements that are an extension of methodsthat access or modify internal data. Anything you can do with member functions,you can do with properties.

属性是成熟的,第一等的语言元素,是对访问或者修改内部数据的方法的扩展。任何使用成员方法可以做的事情,都可以用属性来完成。

The accessorsfor a property are two separate methods that get compiled into your type. Youcan specify different accessibility modifiers to the get and set accessors in aproperty in C# 2.0. This gives you even greater control over the visibility ofthose data elements you expose as properties:

属性访问符是两个独立的方法,被编译到了类型中。在C#2.0属性中,可以对get/set指定不同级别的访问修饰符,使你对数据成员的可见性有更好的控制:

 

  1.    public class Customer
  2.     {
  3.         private String name;
  4.  
  5.         public virtual String Name
  6.         {
  7.            get { return name;}
  8.            protected set { name = value;}
  9.         }
  10. }

The propertysyntax extends beyond simple data fields. If your type should contain indexeditems as part of its interface, you can use indexers(which are parameterized properties). It's a useful way to create a propertythat returns the items in a sequence:

属性的语法对简单数据字段进行了扩展。如果你的类型要将索引作为接口的一部分,可以使用索引器(这是参数化的属性)。创建可以返回一个队列中的某一项的属性,这是一个有用的方法。

  1. public int this[int index]
  2. {
  3.     get
  4.     {
  5.         return values[index];
  6.     }
  7.     set
  8.     {
  9.         values[index] = value;
  10.     }
  11. }

Indexers haveall the same language support as single-item properties: They are implementedas methods you write, so you can apply any verification or computation insidethe indexer. Indexers can be virtual or abstract, can be declared ininterfaces, and can be read-only or read-write. Single-dimension indexers withnumeric parameters can participate in data binding. Other indexers can usenoninteger parameters to define maps and dictionaries

索引器拥有和单项属性同样的所有的语言支持:以你编写的方法来实现,因此可以在索引器内部进行任何验证或者计算。索引器可以是虚的,也可以是抽象的,可以在接口内部声明,可以是只读的或者只写的。使用数字参数的一维索引器可以参与数据绑定。其它索引器可以使用非数字参数来定义图(map)和字典(dictionarie)

 

  1.    public Address this[String  name]
  2. {
  3.     get
  4.     {
  5.         return values[name];
  6.     }
  7.     set
  8.     {
  9.         values[nam] = value;
  10.     }
  11. }

In keeping withthe multidimensional arrays in C#, you can create multidimensional indexers,with similar or different types on each axis:

为了与C#中的多维数组保持一致,你可以创建多维的索引器,在每个纬度上使用相同的或者不同的类型都可以。

  1. public int this[int x,int y]
  2. {
  3.     get
  4.     {
  5.         return ComputeValue(x, y);
  6.     }
  7. }
  8.  
  9. public int this[int x,string name]
  10. {
  11.     get
  12.     {
  13.         return ComputeValue(x, name);
  14.     }
  15. }

Notice that allindexers are declared with the this keyword. You cannot name an indexer.Therefore, you can have, at most, one indexer with the same parameter list ineach type.

所有的索引器都使用this关键字来声明,不可以给索引器命名。因此,在一个类型里面,同样的参数列表情况下,最多只能有一个索引器。

This propertyfunctionality is all well and good, and it's a nice improvement. But you mightstill be tempted to create an initial implementation using data members andthen replace the data members with properties later when you need one of thosebenefits. That sounds like a reasonable strategy but it's wrong. Consider thisportion of a class definition:

属性的功能已经很好了,这是很好的改进。但是仍然有这样的可能:使用数据成员临时创建了一个初步实现,以后,当需要一个或者多个属性的优点时,再使用属性来替换。这听起来是一个不错的策略,但是是错误的。考虑下面一个类定义的一部分:

  1.     public class Customer
  2.     {
  3.         public String Name;
  4.     }

It describes acustomer, with a name. You can get or set the name using the familiar membernotation:

它描述了一个customer,包含有一个name。可以使用下面熟悉的语法来获取/设置Name

  1. Customer customerOne = new Customer();
  2. string Name = customerOne.Name;
  3. customerOne.Name = "This company,Inc.";

That's simpleand straightforward. You are thinking that you could later replace the Namedata member with a property, and the code would keep working without anychange. Well, that's sort of true.

这是简单直观的,你在想,可以在以后使用属性来替换Name这个数据成员,代码呢,不需要任何更改就可以使用。可是,这只是在某种程度上才是正确的。

Properties aremeant to look like data members when accessed. That's the purpose behind thenew syntax. But properties are not data. A property access generates differentMSIL than a data access. The previous customer type generates the followingMSIL for the Name field:

属性想要在访问时看起来像数据成员,这是新语法背后的目的,但是属性不是数据,属性访问和数据访问生成不同的MSIL。前面的customer类型为Name字段生成下面的MSIL

  1. .field public string Name

Accessing the field generates these statements:

对字段的访问将生成下面的MSIL:

  1.   IL_000e:  ldloc.0
  2.   IL_000f:  ldfld      string Namespace.Customer::Name
  3.   IL_0014:  stloc.1

Storing a valuein the field generates this:

向字段写入数据将生成下面的MSIL:

  1.   IL_0015:  ldloc.0
  2.   IL_0016:  ldstr      "This company,Inc."
  3.   IL_001b:  stfld      string Namespace.Customer::Name

Don't worry we'renot going to look at IL all day. But here, it is important because we are aboutto see how changing between data members and properties breaks binarycompatibility. Consider this version of the customer type, which is created byusing properties:

不用担心,我们不会整天都看IL的。但是在这,我们读IL是为了看在数据成员和属性之间的改变是如何打破二进制代码的兼容性的。下面是Customer类型的属性实现版本:

  1.     public class Customer
  2.     {
  3.         private String name;
  4.  
  5.         public String Name
  6.         {
  7.             get { return name; }
  8.             set { name = value; }
  9.         }
  10.  }

When you writeC# code, you access the name property using the exact same syntax:

C#代码时,访问Name属性是使用的同样的语法:

  1. Customer customerOne = new Customer();
  2. string Name = customerOne.Name;
  3. customerOne.Name = "This company,Inc.";

But the C#compiler generates completely different MSIL for this version. The Customer typehas this:

但是C#编译器生成了完全不同的MSILCustomer类型是这样的:

  1. .property instance string Name()
  2. {
  3.   .get instance string NameSpace.Customer::get_Name()
  4.   .set instance void NameSpace.Customer::set_Name(string)
  5. // end of property Customer::Name
  6.  
  7. .method public hidebysig specialname instance void
  8.         set_Name(string 'value') cil managed
  9. {
  10.   // 代码大小       9 (0x9)
  11.   .maxstack  8
  12.   IL_0000:  nop
  13.   IL_0001:  ldarg.0
  14.   IL_0002:  ldarg.1
  15.   IL_0003:  stfld      string NameSpace.Customer::name
  16.   IL_0008:  ret
  17. // end of method Customer::set_Name
  18.  
  19. .method public hidebysig specialname instance string
  20.         get_Name() cil managed
  21. {
  22.   // 代码大小       12 (0xc)
  23.   .maxstack  1
  24.   .locals init ([0] string CS$1$0000)
  25.   IL_0000:  nop
  26.   IL_0001:  ldarg.0
  27.   IL_0002:  ldfld      string NameSpace.Customer::name
  28.   IL_0007:  stloc.0
  29.   IL_0008:  br.s       IL_000a
  30.   IL_000a:  ldloc.0
  31.   IL_000b:  ret
  32. // end of method Customer::get_Name
  33.  

Two majorpoints must be understood about how property definitions translate into MSIL.First, the .property directive defines the type of the property and thefunctions that implement the get and set accessors of the property. The two functionsare marked with hidebysig and specialname. For our purposes, those designationsmean that these functions are not called directly in C# source code, and theyare not to be considered part of the formal type definition. Instead, youaccess them through the property.

对于属性定义如何转化成MSIL,有主要的两点必须理解。首先,.properity 直接定义了属性的类型以及实现属性get/set访问符的方法。这两个方法被 hidebysig specialname来标记。对我们的目的来说,这样的设计意味着:这些方法在C#源代码里面不是被直接调用的,它们不被考虑成正式类型定义的一部分。取而代之,你使用属性来访问它们。

Sure, youexpected different MSIL to be generated for the property definition. Moreimportant to this discussion, there are changes to the MSIL generated for the getand set access to the property as well:

当然,你希望属性的定义生成不同的MSIL。这次讨论中,更重要的是,属性get或者set,生成的MSIL也有了变化:

  1.   IL_000e:  ldloc.0
  2.   IL_000f:  callvirt   instance string NameSpace.Customer::get_Name()
  3.   IL_0014:  stloc.1
  4.  
  5.   IL_0015:  ldloc.0
  6.   IL_0016:  ldstr      "This company,Inc."
  7.   IL_001b:  callvirt   instance void NameSpace.Customer::set_Name(string)

The same C#source to access the name of a customer compiles to very different MSILinstructions, depending on whether the Name member is a property or a datamember. Accessing a property and accessing a data member use the same C#source. It's the work of the C# compiler to translate the source into differentIL necessary for properties or data members.

C#中,访问CustomerName的同样的代码,会编译成非常不同的MSIL结构,这取决于Name是数据成员还是属性。访问属性和数据成员的C#代码是一样的。将属性和数据成员转换成不同的IL代码,是C#编译器的必要的工作。

Althoughproperties and data members are source compatible, they are not binarycompatible. In the obvious case, this means that when you change from a publicdata member to the equivalent public property, you must recompile all code thatuses the public data member. Chapter 4,"Creating Binary Components," discusses binary components in detail,but remember that the simple act of changing a data member to a property breaksbinary compatibility. It makes upgrading single assemblies that have beendeployed much more difficult.

虽然属性和数据成员在源代码上是兼容的,但是它们在二进制码上是不兼容的。在这个明显的例子里,意味着,当你将公共数据成员转成等价的公共属性时,你必须重新编译所有使用公共数据成员的代码。需要记住:对数据成员的简单的改变,打破了二进制码的兼容性,这使得更新已经部署的单程序集非常困难。

While lookingat the IL for a property, you probably wonder about the relative performance ofproperties and data members. Properties will not be faster than data memberaccess, but they might not be any slower. The JIT compiler does inline somemethod calls, including property accessors. When the JIT compiler does inlineproperty accessors, the performance of data members and properties is the same.Even when a property accessor has not been inlined, the actual performancedifference is the negligible cost of one function call. That is measurable onlyin a small number of situations.

当我们检视属性的IL代码时,你可能想知道属性和数据成员之间的性能关系。属性不会比数据成员快,但是也不可能慢。当JIT编译器不对属性访问进行内联时,数据成员和属性的性能是一样的。甚至当属性访问符没有内联时,实际的性能差别是一个方法的调用,是可以忽略不计的。只有在很少的情况下才是可以计量的。

Whenever youexpose data in your type's public or protected interfaces, use properties. Usean indexer for sequences or dictionaries. All data members should be private,without exception. You immediately get support for data binding, and you makeit much easier to make any changes to the implementation of the methods in thefuture. The extra typing to encapsulate any variable in a property amounts toone or two minutes of your day. Finding that you need to use properties laterto correctly express your designs will take hours. Spend a little time now, andsave yourself lots of time later.

无论何时,当你要在类型的公共或者保护接口上,暴露你的数据时,使用属性。为队列或者字典使用索引器。毫无例外的,所有的数据成员都应该是私有的。你可以迅速的得到数据绑定的支持。以后,在实现的方法中进行任何改变都是很容易的。在属性中封装任何变量,额外花费的敲入代码的时间,每天最多是12分钟。如果晚点,你发现应该使用属性来正确的表达设计意图,将会花费数小时。现在花费一小点时间,以后会节省很多。

原创粉丝点击