因此,我们都认识到不可变类型的好处,尤其是在多线程方案中。 (或者至少我们应该都意识到这一点;请参见例如System.String。)

但是,我还没有看到太多有关创建不可变实例的讨论,特别是设计指南。

例如,假设我们要具有以下不可变的类:

class ParagraphStyle {
    public TextAlignment Alignment {get;}
    public float FirstLineHeadIndent {get;}
    // ...
}

我见过的最常见的方法是具有可变的/不可变的“对”类型,例如可变List<T>和不可变ReadOnlyCollection<T>类型或可变StringBuilder和不可变String类型。

为了模仿这种现有模式,将需要引入某种“可变” ParagraphStyle类型,以“复制”成员(以提供 setter ),然后提供一个ParagraphStyle构造函数,该结构接受可变类型作为参数。
// Option 1:
class ParagraphStyleCreator {
    public TextAlignment {get; set;}
    public float FirstLineIndent {get; set;}
    // ...
}

class ParagraphStyle {
    // ... as before...
    public ParagraphStyle (ParagraphStyleCreator value) {...}
}

// Usage:
var paragraphStyle = new ParagraphStyle (new ParagraphStyleCreator {
    TextAlignment = ...,
    FirstLineIndent = ...,
});

因此,这行得通,支持IDE内的代码完成,并使事情在构造方面相当明显……但是看起来确实是重复的。

有没有更好的办法?

例如,C#匿名类型是不可变的,并且允许使用“常规”属性 setter 进行初始化:
var anonymousTypeInstance = new {
    Foo = "string",
    Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error

不幸的是,在C#中复制这些语义的最直接方法是使用构造函数参数:
// Option 2:
class ParagraphStyle {
    public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
            /* ... */ ) {...}
}

但这并不能很好地“扩展”。如果您的类型有15个属性,具有15个参数的构造函数绝非友好,为所有15个属性提供“有用的”重载是一场噩梦。我完全拒绝这个。

如果我们尝试模仿匿名类型,则似乎可以在“不可变”类型中使用“一次设置”属性,从而删除“可变”变量:
// Option 3:
class ParagraphStyle {
    bool alignmentSet;
    TextAlignment alignment;

    public TextAlignment Alignment {
        get {return alignment;}
        set {
            if (alignmentSet) throw new InvalidOperationException ();
            alignment = value;
            alignmentSet = true;
        }
    }
    // ...
}

问题在于,属性只能设置一次(编译器当然不会提示)并且初始化不是线程安全的,这并不明显。因此,很容易添加Commit()方法,以便对象可以知道开发人员已完成设置属性的操作(因此,如果调用了它们的setter,则导致先前未设置的所有属性都将引发),但这似乎是一个错误。使事情变得更糟而不是更好的方法。

有没有比可变/不可变类拆分更好的设计?还是我注定要处理成员重复问题?

最佳答案

在几个项目中,我使用的是流利的方法。 IE。大多数通用属性(例如名称,位置,标题)是通过ctor定义的,而其他属性则使用Set方法更新,并返回了新的不可变实例。

class ParagraphStyle {
  public TextAlignment Alignment {get; private set;}
  public float FirstLineHeadIndent {get; private set;}
  // ...
  public ParagraphStyle WithAlignment(TextAlignment ta) {
      var newStyle = (ParagraphStyle)MemberwiseClone();
      newStyle.TextAlignment = ta;
  }
  // ...
}

只要我们的类(class)真正变得一成不变,MemberwiseClone就可以了。

关于.net - 不变的类(class) build 设计,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2196913/

10-13 06:22