54

如果我使用这样的自动属性在 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; }
}

当我尝试构建文件时,我得到一个编译错误,说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 来证明这一点,但如果我认为我可以分解 IL,那我只是在开玩笑。但我的猜测是,自动属性通过让编译器在幕后为您的属性生成字段来工作。这些字段无法通过代码访问,所有设置和获取都必须通过属性完成。创建结构时,不能显式定义默认构造函数。因此,在幕后,编译器必须生成一个默认构造函数,该构造函数设置开发人员看不到的字段的值。

欢迎任何和所有 IL 向导来证明或反驳我的理论。

4

2 回答 2

53

注意:从 C# 6 开始,这不是必需的 - 但无论如何您都应该在 C# 6 中使用只读自动实现的属性...

this()确保就编译器而言明确分配了字段 - 它将所有字段设置为其默认值。在开始访问任何属性之前,您必须拥有一个完全构造的结构。

这很烦人,但就是这样。你确定你真的希望这是一个结构吗?为什么要在结构上使用受保护的设置器(不能从中派生)?

于 2008-11-07T14:10:33.207 回答
0

属性只不过是Get方法和/或Set方法的封装。CLR 具有指示特定方法应被视为属性的元数据,这意味着编译器应允许一些它不允许使用方法的构造。例如,如果X是 的读写属性Foo,编译器将转换Foo.X += 5Foo.SET_X_METHOD(Foo.GET_X_METHOD() + 5)(尽管方法名称不同,并且通常不能通过名称访问)。

尽管自动属性实现了一对 get/set 方法,这些方法访问私有字段的方式或多或少像一个字段,但从属性之外的任何代码的角度来看,自动属性是一对 get/像任何其他属性一样设置方法。因此,语句 likeFoo.X = 5;被翻译为Foo.SET_X_METHOD(5)。由于 C# 编译器只是将其视为方法调用,并且由于方法不包含任何元数据来指示它们读取或写入的字段,因此编译器将禁止方法调用,除非它知道每个字段Foo都已写入。

就个人而言,我的建议是避免使用结构类型的自动属性。自动属性对类有意义,因为类属性可以支持更新通知等功能。即使类的早期版本不支持更新通知,让这些版本使用自动属性而不是字段将意味着未来版本可以添加更新通知功能,而无需重新设计类的使用者。然而,结构不能有意义地支持人们可能希望添加到类似字段的属性中的大多数特征类型。

此外,与类类型相比,大型结构的字段和属性之间的性能差异要大得多。事实上,避免大型结构的大部分建议都是这种差异的结果。如果避免不必要地复制它们,大型结构实际上可以非常有效。即使有一个巨大的结构HexDecet<HexDecet<HexDecet<Integer>>>,其中HexDecet<T>包含暴露的字段F0..F15类型T,类似的语句Foo = MyThing.F3.F6.F9;也只需要从 读取一个整数MyThing并将其存储到Foo,即使MyThing按结构标准(4096 个整数占用 16K)是巨大的。此外,可以非常轻松地更新该元素,例如MyThing.F3.F6.F9 += 26;. 相比之下,如果F0..F15是自动属性,该语句Foo = MyThing.F3.F6.F9需要从. 伊克。更糟糕的是,尝试将 26 添加到 in 的值将需要类似.MyThing.F3temp1temp1.F6temp2temp2.F9MyThing.F3.F6.F9var t1 = MyThing.F3; var t2 = t1.F6; t2.F9 += 26; t1.F6 = f2; MyThing.F3 = t1;

许多关于“可变结构类型”的长期抱怨实际上是对具有读/写属性的结构类型的抱怨。只需用字段替换属性,问题就会消失。

PS:有时候,拥有一个结构,它的属性访问一个类对象,它持有一个引用,这可能会很有用。例如,最好有一个ArraySegment<T>允许说 的类的版本Var foo[] = new int[100]; Var MyArrSeg = New ArraySegment<int>(foo, 25, 25); MyArrSeg[6] += 9;,并让最后一条语句将 9 添加到元素 (25+6)foo. 在旧版本的 C# 中可以做到这一点。不幸的是,在框架中频繁使用自动属性,其中字段更合适导致了对编译器允许在只读结构上无用地调用属性设置器的广泛抱怨。因此,现在禁止在只读结构上调用任何属性设置器,无论属性设置器是否会实际修改结构的任何字段。如果人们只是避免通过属性设置器使结构可变(在可变性适当时使字段直接可访问),那么编译器将永远不必实现该限制。

于 2012-10-01T15:46:11.003 回答