尽管属性看起来像变量,但每个属性实际上是 get 方法和/或 set 方法的组合。通常,属性 get 方法将返回某个变量或数组槽中内容的副本,而 put 方法会将其参数复制到该变量或数组槽中。如果一个人想做类似someVariable = someObject.someProeprty;
orsomeobject.someProperty = someVariable;
的事情,那么这些语句最终分别被执行为var temp=someObject.somePropertyBackingField; someVariable=temp;
and并不重要var temp=someVariable; someObject.somePropertyBackingField=temp;
。另一方面,有些操作可以用字段完成,但不能用属性完成。
如果一个对象George
公开了一个名为 的字段Field1
,那么代码可以George.Field
作为一个ref
或out
参数传递给另一个方法。此外,如果 的类型Field1
是具有暴露字段的值类型,则访问这些字段的尝试将访问存储在George
. 如果Field1
已经暴露了属性或方法,那么访问它们将导致George.Field1
传递给这些方法,就好像它是一个ref
参数一样。
如果George
暴露了一个名为 的属性Property1
,那么一个Property1
不在赋值运算符左侧的访问将调用“get”方法并将其结果存储在一个临时变量中。尝试读取 的Property1
字段将从临时变量中读取该字段。尝试调用属性 getter 或方法Property1
会将该临时变量作为ref
参数传递给该方法,然后在方法返回后将其丢弃。在方法或属性 getter 或方法中,this
将引用临时变量,并且该方法所做的任何更改都this
将被丢弃。
因为写入临时变量的字段没有意义,所以禁止尝试写入属性的字段。此外,当前版本的 C# 编译器会猜测属性设置器可能会修改this
,因此将禁止使用任何属性设置器,即使它们实际上不会修改底层结构[例如,原因ArraySegment
包括索引get
方法而不是索引set
方法是,如果一个人试图说,例如thing.theArraySegment[3] = 4;
,编译器会认为一个人试图修改theArraySegment
属性返回的结构,而不是修改其引用封装在其中的数组]。如果可以指定特定的结构方法将修改,那将非常有用this
并且不应该在结构属性上调用,但目前还没有机制存在。
如果要写入属性中包含的字段,最好的模式通常是:
var temp = myThing.myProperty; // Assume `temp` is a coordinate-point structure
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<T>
-like 类可能会公开:
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);
}
具有 aFancyList<CoordinatePoint>
并希望将一些局部变量添加dx
到 iit 中项目 5 的字段 X 的代码可以执行以下操作:
myList.ActOnItem(5, (ref Point pt, ref int ddx) => pt.X += ddx, ref dx);
请注意,这种方法将允许就地修改列表中的数据,甚至允许使用诸如Interlocked.CompareExchange
) 之类的方法。不幸的是,没有可能的机制可以让派生自的类型List<T>
支持这种方法,也没有任何机制可以将支持这种方法的类型传递给需要List<T>
.