所以我们都意识到不可变类型的好处,特别是在多线程场景中.(或者至少我们都应该意识到这一点;参见例如System.String.)
但是,我没有看到很多关于创建不可变实例的讨论,特别是设计指南.
例如,假设我们想要具有以下不可变类:
class ParagraphStyle {
public TextAlignment Alignment {get;}
public float FirstLineHeadIndent {get;}
// ...
}
Run Code Online (Sandbox Code Playgroud)
我见过的最常见的方法是拥有可变/不可变的"对"类型,例如可变的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 = ...,
});
Run Code Online (Sandbox Code Playgroud)
因此,这有效,支持IDE中的代码完成,并使得如何构建事物的事情相当明显......但它确实看起来相当重复.
有没有更好的办法?
例如,C#匿名类型是不可变的,并允许使用"普通"属性设置器进行初始化:
var anonymousTypeInstance = new {
Foo = "string",
Bar = 42;
};
anonymousTypeInstance.Foo = "another-value"; // compiler error
Run Code Online (Sandbox Code Playgroud)
不幸的是,在C#中复制这些语义的最接近的方法是使用构造函数参数:
// Option 2:
class ParagraphStyle {
public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
/* ... */ ) {...}
}
Run Code Online (Sandbox Code Playgroud)
但这并不能很好地"扩展"; 如果您的类型具有例如15个属性,则具有15个参数的构造函数是友好的,并且为所有15个属性提供"有用"重载是一个噩梦的秘诀.我完全拒绝这一点.
如果我们试图模仿匿名类型,似乎我们可以在"immutable"类型中使用"set-once"属性,从而删除"mutable"变体:
// Option 3:
class ParagraphStyle {
bool alignmentSet;
TextAlignment alignment;
public TextAlignment Alignment {
get {return alignment;}
set {
if (alignmentSet) throw new InvalidOperationException ();
alignment = value;
alignmentSet = true;
}
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
这样做的问题是,属性只能设置一次并不明显(编译器肯定不会抱怨),并且初始化不是线程安全的.因此,添加一个Commit()方法变得很诱人,这样对象就可以知道开发人员已经完成了设置属性(从而导致以前没有设置的所有属性在调用setter时都抛出),但这似乎是一种方式使事情变得更糟,而不是更好.
是否有比mutable/immutable类拆分更好的设计?或者我注定要处理成员重复?
在几个项目中我使用了流畅的方法。即,大多数通用属性(例如名称、位置、标题)是通过 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;
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
一旦我们的类真正做到了深度不可变,MemberwiseClone 就可以了。
| 归档时间: |
|
| 查看次数: |
709 次 |
| 最近记录: |