您的位置:首页 > Web前端

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

2008-09-10 18:28 519 查看
Item
1: Always Use Properties Instead of Accessible Data Members[/b]

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

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

C#将属性从一些特殊习惯(ad-hoc:Wiki:Ad hoc is a Latin phrase which means "for this [purpose]". It
generally 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 name
of a customer, the x,y location of a point, or last year's revenue. Properties
enable you to create an interface that acts like data access but still has all
the benefits of a function. Client code accesses properties as though they are
accessing public variables. But the actual implementation uses methods, in
which you define the behavior of property accessors.

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

The .NET Framework assumes that you'll use properties for your
public data members. In fact, the data binding code classes in the .NET
Framework support properties, not public data members. Data binding ties a
property of an object to a user interface control, either a web control or a
Windows Forms control. The data binding mechanism uses reflection to find a
named 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 control
to the City property of the address object. (See Item 38
for more details.) It will not work with a public data member named City; the
Framework Class Library designers did not support that practice. Public data
members are bad practice, so support for them was not added. Their decision
simply gives you yet another reason to follow the proper object-oriented
techniques. 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. You
should be using properties instead of the convention of get_ and set_ functions
in those languages.

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

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

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

{

private String name;

public String Name

{

get { return name; }

set

{

if ((value == null) || (value.Length == 0))

{

throw new ArgumentException("Name cannot be blank", "Name");

}

name = value;

}

}

}

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

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

Because properties are implemented with methods, adding
multithreaded support is easier. Simply enhance the implementation of the get
and set methods to provide synchronized access to the data:

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

{

private String name;

public String Name

{

get

{

lock (this)

{

return name;

}

}

set

{

lock (this)

{

name = value;

}

}

}

}

Properties have
all the language features of methods. Properties can be virtual:

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

{

private String name;

public virtual String Name

{

get { return name; }

set { name = value; }

}

}

It's also easy
to see that you can extend properties to be abstract or even part of an
interface definition:

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

{

object Name

{

get;

}

object Value

{

get;

set;
}

}

Last, but
certainly not least, you can use interfaces to create const and nonconst
versions of an interface:

最后一条,但是不意味着最不重要,可以使用接口创建const 和 nonconst版本的接口: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;

public object Name

{

get { return name; }

}

object IConstNameValuePair.Value

{

get { return value; }

}

public object Value

{

get { return value; }

set { this.value = value; }

}

}

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

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

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

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

public class Customer

{

private String name;

public virtual String Name

{

get { return name;}

protected set { name = value;}

}

}

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

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

{

get

{

return values[index];

}

set

{

values[index] = value;

}

}

Indexers have
all the same language support as single-item properties: They are implemented
as methods you write, so you can apply any verification or computation inside
the indexer. Indexers can be virtual or abstract, can be declared in
interfaces, and can be read-only or read-write. Single-dimension indexers with
numeric parameters can participate in data binding. Other indexers can use
noninteger parameters to define maps and dictionaries:

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

public Address this[String name]

{

get

{

return values[name];

}

set

{

values[nam] = value;

}

}

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

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

{

get

{

return ComputeValue(x, y);

}

}

public int this[int x,string name]

{

get

{

return ComputeValue(x, name);

}

}

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

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

This property
functionality is all well and good, and it's a nice improvement. But you might
still be tempted to create an initial implementation using data members and
then replace the data members with properties later when you need one of those
benefits. That sounds like a reasonable strategy but it's wrong. Consider this
portion of a class definition:

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

{

public String Name;

}

It describes a
customer, with a name. You can get or set the name using the familiar member
notation:

它描述了一个customer,包含有一个name。可以使用下面熟悉的语法来获取/设置Name:Customer customerOne = new Customer();

string Name = customerOne.Name;

customerOne.Name = "This company,Inc.";

That's simple
and straightforward. You are thinking that you could later replace the Name
data member with a property, and the code would keep working without any
change. Well, that's sort of true.

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

Properties are
meant to look like data members when accessed. That's the purpose behind the
new syntax. But properties are not data. A property access generates different
MSIL than a data access. The previous customer type generates the following
MSIL for the Name field:

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

.field public string Name

Accessing the field generates these statements:

对字段的访问将生成下面的MSIL: IL_000e: ldloc.0

IL_000f: ldfld string Namespace.Customer::Name

IL_0014: stloc.1

Storing a value
in the field generates this:

向字段写入数据将生成下面的MSIL: IL_0015: ldloc.0

IL_0016: ldstr "This company,Inc."

IL_001b: stfld string Namespace.Customer::Name

Don't worry we're
not going to look at IL all day. But here, it is important because we are about
to see how changing between data members and properties breaks binary
compatibility. Consider this version of the customer type, which is created by
using properties:

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

{

private String name;

public String Name

{

get { return name; }

set { name = value; }

}

}

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

写C#代码时,访问Name属性是使用的同样的语法:Customer customerOne = new Customer();

string Name = customerOne.Name;

customerOne.Name = "This company,Inc.";

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

但是C#编译器生成了完全不同的MSIL。Customer类型是这样的:.property instance string Name()

{

.get instance string NameSpace.Customer::get_Name()

.set instance void NameSpace.Customer::set_Name(string)

} // end of property Customer::Name

.method public hidebysig specialname instance void

set_Name(string 'value') cil managed

{

// 代码大小 9 (0x9)

.maxstack 8

IL_0000: nop

IL_0001: ldarg.0

IL_0002: ldarg.1

IL_0003: stfld string NameSpace.Customer::name

IL_0008: ret

} // end of method Customer::set_Name

.method public hidebysig specialname instance string

get_Name() cil managed

{

// 代码大小 12 (0xc)

.maxstack 1

.locals init ([0] string CS$1$0000)

IL_0000: nop

IL_0001: ldarg.0

IL_0002: ldfld string NameSpace.Customer::name

IL_0007: stloc.0

IL_0008: br.s IL_000a

IL_000a: ldloc.0

IL_000b: ret

} // end of method Customer::get_Name

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

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

Sure, you
expected different MSIL to be generated for the property definition. More
important to this discussion, there are changes to the MSIL generated for the get
and set access to the property as well:

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

IL_000f: callvirt instance string NameSpace.Customer::get_Name()

IL_0014: stloc.1

IL_0015: ldloc.0

IL_0016: ldstr "This company,Inc."

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 MSIL
instructions, depending on whether the Name member is a property or a data
member. 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 different
IL necessary for properties or data members.

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

Although
properties and data members are source compatible, they are not binary
compatible. In the obvious case, this means that when you change from a public
data member to the equivalent public property, you must recompile all code that
uses 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 breaks
binary compatibility. It makes upgrading single assemblies that have been
deployed much more difficult.

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

While looking
at the IL for a property, you probably wonder about the relative performance of
properties and data members. Properties will not be faster than data member
access, but they might not be any slower. The JIT compiler does inline some
method calls, including property accessors. When the JIT compiler does inline
property accessors, the performance of data members and properties is the same.
Even when a property accessor has not been inlined, the actual performance
difference is the negligible cost of one function call. That is measurable only
in a small number of situations.

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

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

无论何时,当你要在类型的公共或者保护接口上,暴露你的数据时,使用属性。为队列或者字典使用索引器。毫无例外的,所有的数据成员都应该是私有的。你可以迅速的得到数据绑定的支持。以后,在实现的方法中进行任何改变都是很容易的。在属性中封装任何变量,额外花费的敲入代码的时间,每天最多是1、2分钟。如果晚点,你发现应该使用属性来正确的表达设计意图,将会花费数小时。现在花费一小点时间,以后会节省很多。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐