为什么需要在结构体上调用 :this() 才能在 C# 中使用自动属性?

发布于 2024-07-09 02:26:43 字数 1096 浏览 7 评论 0 原文

如果我使用如下自动属性在 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

想你只要分分秒秒 2024-07-16 02:26:43

注意:从 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)?

吾性傲以野 2024-07-16 02:26:43

属性只不过是 Get 方法和/或 Set 方法的封装。 CLR 具有元数据,指示特定方法应被视为属性,这意味着编译器应允许某些方法不允许的构造。 例如,如果 XFoo 的读写属性,编译器会将 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.F6temp2 的 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 a Set 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, if X is a read-write property of Foo, a compiler will translate Foo.X += 5 into Foo.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 as Foo.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 of Foo 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>>>, where HexDecet<T> contained exposed fields F0..F15 of type T, a statement like Foo = MyThing.F3.F6.F9; would simply require reading one integer from MyThing and storing it to Foo, even though MyThing 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, if F0..F15 were auto-properties, the statement Foo = MyThing.F3.F6.F9 would require copying 1K of data from MyThing.F3 to a temporary (call it temp1, then 64 bytes of data from temp1.F6 to temp2) before finally getting around to reading 4 bytes of data from temp2.F9. Ick. Worse, trying to add 26 to the value in MyThing.F3.F6.F9 would require something like var 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 say Var 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) of foo. 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文