为什么有必要调用:这个()结构在c#中使用自动属性?
如果我使用这样的自动属性在C#中定义一个结构:
public struct Address { public Address(string line1, string line2, string city, string state, string zip) { Line1 = line1; Line2 = line2; City = city; State = state; Zip = zip; } public string Line1 { get; protected set; } public string Line2 { get; protected set; } public string City { get; protected set; } public string State { get; protected set; } public string Zip { get; protected set; } }
当我尝试构build文件时,出现编译错误,说明The 'this' object cannot be used before all of its fields are assigned to
。 这可以通过更改构造函数来解决,像下面这样对默认的构造函数进行链接调用:
public Address(string line1, string line2, string city, string state, string zip): this() { Line1 = line1; Line2 = line2; City = city; State = state; Zip = zip; }
我的问题是,为什么这个工作,发生了什么? 我有一个猜测,我试图通过看IL来certificate这一点,但如果我认为我可以打破IL,我只是在开玩笑。 但我的猜测是,自动属性的工作是让编译器在后台为你的属性生成字段。 这些字段不能通过代码访问,所有的设置和获取必须通过属性完成。 当创build一个结构体时,默认的构造函数不能被明确定义。 所以在幕后,编译器必须生成一个默认的构造函数来设置开发人员看不到的字段的值。
欢迎任何和所有的IL巫师来certificate或反驳我的理论。
注意:从C#6开始,这不是必需的,但是您应该使用C#6的只读自动实现的属性。
this()
确保字段在编译器方面是明确分配的 – 它将所有字段设置为默认值。 在开始访问任何属性之前,您必须具有完全构造的结构。
这很烦人,但就是这样。 你确定你确实希望这是一个结构吗? 为什么在一个结构体上使用受保护的setter(不能派生自)?
一个属性只不过是Get
方法和/或Set
方法的封装。 CLR的元数据表明特定的方法应该被视为一个属性,这意味着编译器应该允许一些方法不允许的构造。 例如,如果X
是Foo
的读写属性,编译器会将Foo.X += 5
转换为Foo.SET_X_METHOD(Foo.GET_X_METHOD() + 5)
(尽pipe这些方法的命名方式不同,通常不能访问按名字)。
尽pipe一个autoproperty实现了一对get / set方法,这个方法访问一个私有字段的方式或多或less像一个字段,从属性外的任何代码的angular度来看,autoproperty是一对get /像任何其他属性一样设置方法。 因此,像Foo.X = 5;
被翻译为Foo.SET_X_METHOD(5)
。 由于C#编译器只是将其视为方法调用,并且由于方法不包含任何元数据来指示它们读取或写入的字段,所以编译器将禁止方法调用,除非知道Foo
每个字段都已经写入。
就我个人而言,我的build议是避免使用结构types的自动属性。 自动属性对类有意义,因为类属性可以支持更新通知等function。 即使一个类的早期版本不支持更新通知,让这些版本使用自动属性而不是字段将意味着未来的版本可以添加更新通知function,而不需要重新使用该类的消费者。 但是,结构不能有效地支持人们可能希望添加到类似字段的属性的大多数types的function。
此外,大型结构的字段和属性之间的性能差异远大于types。 事实上,避免大型结构的build议大部分是这种差异的结果。 大型结构实际上可以非常有效,如果避免不必要地复制它们。 即使HexDecet<HexDecet<HexDecet<Integer>>>
,其中HexDecet<T>
包含HexDecet<T>
types的暴露字段F0
.. F15
,类似于Foo = MyThing.F3.F6.F9;
只需要从MyThing
读取一个整数并将其存储到Foo
,即使MyThing
将由巨大的结构标准(4096个整数占用16K)来完成。 另外,可以很容易地更新该元素,例如MyThing.F3.F6.F9 += 26;
。 相反,如果F0
.. F15
是自动属性,则语句Foo = MyThing.F3.F6.F9
将需要从MyThing.F3
复制1K个数据到临时(称为temp1
,然后从temp1.F6
调用64个字节的数据temp1.F6
到temp2
),然后才能从temp2.F9
读取4个字节的数据。 伊克。 更糟糕的是,试图添加26到MyThing.F3.F6.F9
的值需要类似var t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;
var t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;
。
许多关于“可变结构types”的长期抱怨实际上是关于具有读/写属性的结构types的抱怨。 只需用字段replace属性,问题就消失了。
PS:有时候可以有一个结构的属性可以访问一个拥有引用的类对象。 例如,最好有一个允许说Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;
的ArraySegment<T>
类的版本Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;
Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;
,并且最后一条语句将9加到foo
元素(25 + 6)上。 在旧版本的C#中可以做到这一点。 不幸的是,在框架中频繁使用自动属性会导致对编译器的广泛的抱怨,从而允许在只读结构上无用地调用属性设置器; 因此,现在禁止在只读结构上调用任何属性设置器,无论该属性设置器是否实际修改了该结构体的任何字段。 如果人们只是不想通过属性设置者使结构变得可变(当可变性适当时可以直接访问字段),编译器就不必实现这个限制。