如果我使用如下自动属性在 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; }
}
当我尝试构建文件时,我收到编译错误,提示 在将“this”对象的所有字段分配给之前,无法使用“this”对象
。 这可以通过更改构造函数以对默认构造函数进行链式调用来解决,如下所示:
public Address(string line1, string line2, string city, string state, string zip): this()
{
Line1 = line1;
Line2 = line2;
City = city;
State = state;
Zip = zip;
}
我的问题是,为什么会这样,以及发生了什么? 我有一个猜测,我试图通过查看 IL 来证明它,但如果我认为我可以分解 IL,那我只是在自欺欺人。 但我的猜测是,自动属性的工作原理是让编译器在幕后为您的属性生成字段。 这些字段无法通过代码访问,所有设置和获取都必须通过属性完成。 创建结构体时,不能显式定义默认构造函数。 因此,在幕后,编译器必须生成一个默认构造函数来设置开发人员看不到的字段值。
欢迎所有 IL 奇才来证明或反驳我的理论。
If I define a struct in C# using automatic properties like this:
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; }
}
When I attempt to build the file, I get a compilation error saying The 'this' object cannot be used before all of its fields are assigned to
. This can be solved by changing the constructor to make a chained call to the default constructor like this:
public Address(string line1, string line2, string city, string state, string zip): this()
{
Line1 = line1;
Line2 = line2;
City = city;
State = state;
Zip = zip;
}
My question is, why does this work, and what is happening? I have a guess, and I tried to prove it by looking at IL, but I'm only kidding myself if I think I can break down IL. But my guess is, auto properties work by having the compiler generate fields for your properties behind the scenes. Those fields cannot be accessed through code, all setting and getting must be done through the properties. When creating a struct, a default constructor cannot be explicitly defined. So behind the scenes, the compiler must be generating a default constructor that sets the values of the fields that the developer can't see.
Any and all IL wizards are welcome to prove or disprove my theory.
发布评论
评论(2)
注意:从 C# 6 开始,这不是必需的 - 但无论如何,您应该在 C# 6 中使用只读自动实现的属性...
this()
使得确保就编译器而言,这些字段是明确分配的 - 它将所有字段设置为其默认值。 在开始访问任何属性之前,您必须拥有一个完全构造的结构。这很烦人,但事实就是这样。 你确定你真的希望这是一个结构吗? 为什么要在结构体上使用受保护的 setter(无法从中派生)?
Note: as of C# 6, this isn't required - but you should be using read-only automatically-implemented properties with C# 6 anyway...
this()
makes sure that the fields are definitely assigned as far as the compiler is concerned - it sets all fields to their default values. You have to have a fully constructed struct before you can start accessing any properties.It's annoying, but that's the way it is. Are you sure you really want this to be a struct though? And why use a protected setter on a struct (which can't be derived from)?
属性只不过是
Get
方法和/或Set
方法的封装。 CLR 具有元数据,指示特定方法应被视为属性,这意味着编译器应允许某些方法不允许的构造。 例如,如果X
是Foo
的读写属性,编译器会将Foo.X += 5
翻译为Foo .SET_X_METHOD(Foo.GET_X_METHOD() + 5)
(尽管这些方法的命名不同,并且通常不能通过名称访问)。尽管自动属性实现了一对 get/set 方法,这些方法以或多或少类似于字段的方式访问私有字段,但从属性外部的任何代码的角度来看,自动属性是一对 get/set 方法。设置方法就像任何其他属性一样。 因此,像
Foo.X = 5;
这样的语句会被翻译为Foo.SET_X_METHOD(5)
。 由于 C# 编译器仅将其视为方法调用,并且方法不包含任何元数据来指示它们读取或写入哪些字段,因此编译器将禁止方法调用,除非它知道Foo
的每个字段已被写入。就我个人而言,我的建议是避免将自动属性与结构类型一起使用。 自动属性对于类来说很有意义,因为类属性可以支持更新通知等功能。 即使类的早期版本不支持更新通知,让这些版本使用自动属性而不是字段也意味着未来的版本可以添加更新通知功能,而无需重新设计该类的使用者。 然而,结构无法有意义地支持人们可能希望添加到类字段属性中的大多数类型的功能。
此外,大型结构的字段和属性之间的性能差异比类类型的性能差异大得多。 事实上,许多避免大型结构的建议都是这种差异的结果。 如果避免不必要的复制,大型结构实际上可以非常高效。 即使有一个巨大的结构
HexDecet>>
,其中HexDecet
包含公开字段F0
..T
类型的F15
,像Foo = MyThing.F3.F6.F9;
这样的语句只需要从读取一个整数>MyThing
并将其存储到Foo
,尽管MyThing
按照结构标准会很大(4096 个整数占用 16K)。 此外,可以非常轻松地更新该元素,例如MyThing.F3.F6.F9 += 26;
。 相比之下,如果F0
..F15
是自动属性,则语句Foo = MyThing.F3.F6.F9
将需要复制 1K从MyThing.F3
到临时文件(称为temp1
)的数据,然后从temp1.F6
到temp2
的 64 字节数据code>),然后最终从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;。许多关于“可变结构类型”的长期抱怨实际上是对具有读/写属性的结构类型的抱怨。 只需用字段替换属性,问题就会消失。
PS:有时,拥有一个其属性访问它所持有的引用的类对象的结构会很有用。 例如,如果有一个
ArraySegment
类的版本,允许人们说Var foo[] = new int[100]; 那就太好了。 Var MyArrSeg = New ArraySegment(foo, 25, 25); MyArrSeg[6] += 9;
,并让最后一条语句将 9 添加到foo
的元素 (25+6) 中。 在旧版本的 C# 中可以做到这一点。 不幸的是,在框架中频繁使用自动属性(其中字段更合适)导致了对编译器允许在只读结构上无用地调用属性设置器的广泛抱怨; 因此,现在禁止在只读结构上调用任何属性设置器,无论属性设置器是否实际上会修改结构的任何字段。 如果人们只是避免通过属性设置器使结构可变(当可变性合适时使字段可直接访问),编译器将永远不必实现该限制。A property is nothing more than an encapsulation of a
Get
method and/or aSet
method. The CLR has metadata which indicates that particular methods should be regarded as being a properties, meaning compilers should allow some constructs which it would not allow with methods. For example, ifX
is a read-write property ofFoo
, a compiler will translateFoo.X += 5
intoFoo.SET_X_METHOD(Foo.GET_X_METHOD() + 5)
(though the methods are named differently, and are not generally accessible by name).Although an autoproperty implements a pair of get/set methods which access a private field in such a way as to behave more or less like a field, from the point of view of any code outside the property, an autoproperty is a pair of get/set methods just like any other property. Consequently, a statement like
Foo.X = 5;
is translated asFoo.SET_X_METHOD(5)
. Since the C# compiler just sees that as a method call, and since methods do not include any metadata to indicate what fields they read or write, the compiler will forbid the method call unless it knows every field ofFoo
has been written.Personally, my advice would be to avoid the use of autoproperties with structure types. Autoproperties make sense with classes, since it's possible for class properties to support features like update notifications. Even if early versions of a class do not support update notifications, having those versions use an autoproperty rather than a field will mean that future versions can add update-notification features without requiring consumers of the class to be reworked. Structures, however, cannot meaningfully support most of the types of features that one might wish to add to field-like properties.
Further, the performance differences between fields and properties is much greater with large structures than it is with class types. Indeed, much of the recommendation to avoid large structures is a consequence of this difference. Large structures can actually be very efficient, if one avoids copying them unnecessarily. Even if one had an enormous structure
HexDecet<HexDecet<HexDecet<Integer>>>
, whereHexDecet<T>
contained exposed fieldsF0
..F15
of typeT
, a statement likeFoo = MyThing.F3.F6.F9;
would simply require reading one integer fromMyThing
and storing it toFoo
, even thoughMyThing
would by huge by struct standards (4096 integers occupying 16K). Additionally, one could update that element very easily, e.g.MyThing.F3.F6.F9 += 26;
. By contrast, ifF0
..F15
were auto-properties, the statementFoo = MyThing.F3.F6.F9
would require copying 1K of data fromMyThing.F3
to a temporary (call ittemp1
, then 64 bytes of data fromtemp1.F6
totemp2
) before finally getting around to reading 4 bytes of data fromtemp2.F9
. Ick. Worse, trying to add 26 to the value inMyThing.F3.F6.F9
would require something likevar t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;
.Many of the long-standing complaints about "mutable structure types" are really complaints about structure types with read/write properties. Simply replace properties with fields and the problems go away.
PS: There are times it can be useful to have a structure whose properties access a class object to which it holds a reference. For example, it would be nice to have a version of an
ArraySegment<T>
class which allowed one to sayVar foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;
, and have the last statement add nine to element (25+6) offoo
. In older versions of C# one could do that. Unfortunately, the frequent use of autoproperties in the Framework where fields would have been more appropriate led to widespread complaints about the compiler allowing property setters to be called uselessly on read-only structures; consequently, calling any property setter on a read-only structure is now forbidden, whether or not the property setter would actually modify any fields of the struct. If people had simply refrained from making structs mutable via property setters (making fields directly accessible when mutability was appropriate) compilers would never have had to implement that restriction.