尽管属性看起来像变量,但每个属性实际上都是一个get方法和/或set方法的组合。通常情况下,属性get方法会返回某个变量或数组槽中的副本,而put方法会将其参数复制到该变量或数组槽中。如果想执行类似于
someVariable = someObject.someProeprty;
或
someobject.someProperty = someVariable;
这样的操作,则不必担心这些语句最终会被执行为
var temp=someObject.somePropertyBackingField; someVariable=temp;
和
var temp = someVariable; someObject.somePropertyBackingField = temp;
。与此相反,有一些使用字段可以完成但无法使用属性完成的操作。
如果一个对象George公开了一个名为Field1的字段,那么代码可以将George.Field作为ref或out参数传递给另一个方法。此外,如果Field1的类型是一个具有公开字段的值类型,则尝试访问这些字段将访问存储在George内部的结构的字段。如果Field1具有公开的属性或方法,则访问这些属性或方法将导致将George.Field1作为ref参数传递给这些方法,就像它是一个ref参数一样。
如果George公开了一个名为Property1的属性,则访问Property1并不是赋值运算符的左侧,将调用“get”方法并将其结果存储在临时变量中。尝试读取Property1的字段将从临时变量中读取该字段。尝试在Property1上调用属性getter或方法将将该临时变量作为ref参数传递给该方法,然后在方法返回后将其丢弃。在方法或属性getter或方法内部,this将引用临时变量,并且方法对this所做的任何更改都将被丢弃。
由于对于临时变量的字段写入没有意义,因此禁止尝试写入属性字段。此外,当前版本的C#编译器会猜测属性设置器可能会修改
this
并因此禁止任何使用属性设置器即使它们实际上不会修改底层结构 [例如,
ArraySegment
包括一个索引
get
方法而不是索引
set
方法的原因是,如果尝试说例如
thing.theArraySegment [3] = 4;
,编译器会认为正在尝试修改由
theArraySegment
属性返回的结构,而不是修改其内封装的数组的引用所在的数组]。 如果可以指定特定结构方法将修改
this
并且不应在结构属性上调用它们,那将非常有用,但目前还不存在这种机制。
如果想要写入属性中包含的字段,最好的模式通常是:
var temp = myThing.myProperty
temp.X += 5
myThing.myProperty = temp
如果
myProperty
的类型旨在封装一组相关但独立的固定值(例如点的坐标),最好将这些变量公开为字段。尽管有些人似乎更喜欢设计结构体以便需要像以下构造函数一样:
var temp = myThing.myProperty; // Assume `temp` is some kind of XNA Point structure
myThing.myProperty = new CoordinatePoint(temp.X+5, temp.Y);
我认为这种代码比之前的风格更难读、效率更低,错误率更高。如果CoordinatePoint暴露了一个带有X、Y、Z参数的构造函数以及一个仅带有X、Y参数并假设Z为零的构造函数,那么像第二种形式的代码将在没有任何指示的情况下将Z清零(无论是有意还是无意)。相比之下,如果X是一个公开字段,那么第一种形式只会修改X就更加清晰。
在某些情况下,一个类通过将内部字段或数组插槽作为ref参数传递给用户定义的例程的方法来公开它,例如List类可能会公开:
delegate void ActByRef<T1>(ref T1 p1);
delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
void ActOnItem(int index, ActByRef<T> proc)
{
proc(ref BackingArray[index]);
}
void ActOnItem<PT>(int index, ActByRef<T,PT> proc, ref PT extraParam)
{
proc(ref BackingArray[index], ref extraParam);
}
如果有一段代码中有一个 FancyList<CoordinatePoint>
并且想要在第5个项目的字段X中添加一些本地变量 dx
,则可以执行以下操作:
myList.ActOnItem(5, (ref Point pt, ref int ddx) => pt.X += ddx, ref dx);
请注意,这种方法允许对列表中的数据进行原地修改,甚至允许使用
Interlocked.CompareExchange
等方法。不幸的是,从
List<T>
派生的类型无法支持此类方法,也没有机制可以将支持此类方法的类型传递给期望
List<T>
的代码。