如何在结构体构造函数中设置自动属性的后备字段的值?

21

给定这样的一个结构体:

public struct SomeStruct
{
    public SomeStruct(String stringProperty, Int32 intProperty)
    {
        this.StringProperty = stringProperty;
        this.IntProperty = intProperty;
    }

    public String StringProperty { get; set; }
    public Int32 IntProperty { get; set; }
}

当然,编译器会生成一个错误信息:The 'this' object cannot be used before all of its fields are assigned to

是否可以给后备字段或属性本身分配值,或者我必须以我自己的显式后备字段实现属性的老方式?


1
顺便说一句 - 使用“int”和“string”比使用“Int32”和“String”更符合惯用语。 - Marc Gravell
2
我们在我所在的商店中的所有代码中都使用String/Int32等...现在你提出来了,我说不清为什么 - 但我个人更喜欢它的外观。 - Daniel Schaffer
就前面两条评论而言,为了好玩:https://dev59.com/B3VD5IYBdhLWcg3wKoaH - Daniel Schaffer
回复我的帖子评论 - 实际上,当时我已经达到了最大值,所以我只得到了+15声望。 - Marc Gravell
可能是自动属性和结构体不兼容?的重复问题。 - nawfal
2个回答

45

在C# 6之前,您需要在此场景中使用“this”构造函数:

public SomeStruct(String stringProperty, Int32 intProperty) : this()
{
    this.StringProperty = stringProperty;
    this.IntProperty = intProperty;
}

这样做会调用默认构造函数,从而初始化所有字段,使得this可以在自定义构造函数中被引用。


编辑:直到C# 6之前,这种方式才开始合法。然而,现在最好将其定义为readonly struct

public readonly struct SomeStruct
{
    public SomeStruct(string stringProperty, int intProperty)
    {
        this.StringProperty = stringProperty;
        this.IntProperty = intProperty;
    }

    public string StringProperty { get; }
    public int IntProperty { get; }
}

在构造函数中使用后备字段和直接使用自动属性有什么区别?我真的无法理解这个问题。我认为构造函数用于初始化字段,而自动属性是setter和getter方法。那么为什么我们要将自动属性放在构造函数中呢? - user10242552
@K.K 因为在这种情况下它们是只读的 - 它们只有一个 get;,因此该字段被标记为 readonly,它们只能通过构造函数分配。 - Marc Gravell

0

如果一个结构体需要具备以下三个特征,并且其用户期望它具备这些特征:

  1. 其整个状态被封装在某个特定的可读成员集合中。
  2. 可以轻松地创建一个实例,其中这些成员具有各自类型的有效值的任意组合。
  3. 该类型的默认实例应将所有这些成员设置为其各自类型的默认值。

该类型应将其成员公开为字段。上述要求意味着结构体无法执行任何暴露字段结构体无法执行的操作,并且还意味着如果代码没有遇到任何线程问题,则结构体将能够执行暴露字段结构体所能执行的任何操作,尽管可能会更慢并且存在更多的线程问题。

如果结构体 Foo 具有字段 f1f2,以及按照该顺序设置这些字段的构造函数,并且如果 fooExpr 是某种类型为 Foo 的表达式 [可能是变量、字段、数组引用、属性或其他任何东西],则语句如下:

myFoo.f2 = someValue;

只有在可以合法说出来的情况下,才会被允许。

myFoo = new Foo(myFoo.f1, someValue);

在所有允许使用第一种形式并且第二种形式的行为已经定义语义的情况下,它们将表现得完全相同。因此,试图“封装”Foo的属性实际上并没有实现任何目标,只会使代码更加繁琐难写、不够清晰,并且执行速度更慢。

这并不完全正确。首先,在发布版本中,JIT编译器很可能会进行内联,可能会消除任何性能差异。但是,如果您将字段包装为属性,则可以保护自己免受对结构的未来更改的影响:如果以后需要向字段添加一些逻辑,则意味着用属性替换该字段。如果该字段已经封装为属性,则没有更改公共接口,这消除了潜在的回归错误来源。 - Simon Robinson
@SimonRobinson:如果我在我的回答中列出的三个限制是结构体合同的一部分,那么属性或构造函数除了违反合同之外还能添加什么有用的逻辑呢? 我个人的哲学是,结构体应该代表对象(在这种情况下它们应该是不可变的),或者代表变量的聚合(在这种情况下它们应该像变量的聚合一样行事)。 - supercat
我猜如果你把一个结构体仅仅看作是变量的集合,那么你是正确的,不需要额外的逻辑。我主要考虑检查设置的值的有效性的逻辑,但是在仔细阅读你的答案后,我发现你的第二个限制排除了这一点。我的主要担忧是,即使你认为在编写结构体时值可以不受限制,你可能会发现以后限制值更好。或者你可能希望更改内部表示(例如,使字段惰性计算)。 - Simon Robinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接