有一种简单的方法可以使实例不可变吗?
让我们举个例子,我有一个包含大量数据字段的类(只有数据,没有行为):
class MyObject
{
// lots of fields painful to initialize all at once
// so we make fields mutable :
public String Title { get; set; }
public String Author { get; set; }
// ...
}
创建示例:
MyObject CreationExample(String someParameters)
{
var obj = new MyObject
{
Title = "foo"
// lots of fields initialization
};
// even more fields initialization
obj.Author = "bar";
return obj;
}
但是现在我已经完全创建了我的对象,我不希望该对象再次变得可变(因为数据使用者永远不需要改变状态),所以我想要像List.AsReadOnly这样的东西:
var immutableObj = obj.AsReadOnly();
但是如果我想要这种行为,我需要创建另一个具有完全相同字段但没有setter的类 .
那么有没有自动生成这个不可变类的方法呢?或者在创建过程中允许可变性的另一种方法,但是一旦初始化就不可变?
我知道字段可以标记为“只读”,但是对象将在类之外初始化,并且将所有字段作为构造函数参数传递似乎是一个坏主意(参数太多) .
7 回答
不,没有简单的方法可以使任何类型成为不可变的,特别是如果你想要"deep" immutability(即不能通过不可变对象到达可变对象的地方) . 您必须明确地将类型设计为不可变的 . 使类型不可变的通常机制是:
声明(属性支持)字段
readonly
. (或者,从C#6 / Visual Studio 2015开始,使用read-only auto-implemented properties . )不要暴露属性设置器,只暴露getter .
为了初始化(属性支持)字段,必须在构造函数中初始化它们 . 因此,将(property)值传递给构造函数 .
不要公开可变对象,例如基于可变默认类型的集合(如
T[]
,List<T>
,Dictionary<TKey,TValue>
等) .如果需要公开集合,请将其返回到阻止修改的包装器(例如
.AsReadOnly()
),或者至少返回内部集合的新副本 .另一种解决方案是使用动态代理 . 实体框架http://blogs.msdn.com/b/adonet/archive/2009/12/22/poco-proxies-part-1.aspx使用了相似的方法 . 以下是使用
Castle.DynamicProxy
框架如何执行此操作的示例 . 此代码基于Castle Dynamic代理的原始示例(http://kozmic.net/2008/12/16/castle-dynamicproxy-tutorial-part-i-introduction/)嗯,我将列举我对此的第一个想法......
1. 如果您唯一的担心是在装配体外进行操作,请使用
internal
setter .internal
将使您的属性仅适用于同一程序集中的类 . 例如:2. 我不喜欢在构造函数中包含大量参数 .
3. 您可以在运行时生成另一种类型的只读版本 . 我可以详细说明这一点,但我个人认为这是过度的 .
最好的,尤利安
我建议有一个抽象基类型
ReadableMyObject
以及派生类型MutableMyObject
和ImmutableMyObject
. 具有所有类型的构造函数接受ReadableMyObject
,并且在更新其支持字段之前,让ReadableMyObject
的所有属性设置器调用抽象ThrowIfNotMutable
方法 . 此外,ReadableMyObject
支持公共抽象AsImmutable()
方法 .虽然这种方法需要为对象的每个属性编写一些样板,但这将是所需代码重复的范围 .
MutableMyObject
和ImmutableMyObject
的构造函数只是将接收到的对象传递给基类构造函数 . 类MutableMyObject
应该实现ThrowIfNotMutable
什么也不做,而AsImmutable()
应该返回new ImmutableMyObject(this);
. 类ImmutableByObject
应该实现ThrowIfNotMutable
以抛出异常,AsImmutable()
实现return this;
.接收
ReadableMyObject
并希望保留其内容的代码应调用其AsImmutable()
方法并存储生成的ImmutableMyObject
. 接收ReadableMyObject
并希望稍微修改版本的代码应调用new MutableMyObject(theObject)
然后根据需要进行修改 .你在问题中暗示了某种方式,但我不确定这不是你的选择:
由于构造函数是操作Author和Title的唯一方法,因此该类在构造后实际上是不可变的 .
编辑:
正如stakx所提到的,我也是使用构建器的忠实粉丝 - 特别是因为它使单元测试变得更容易 . 对于上述你可以有一个建造者,如:
这样,您可以使用默认值构造对象,或者根据需要覆盖它们,但构建后无法更改MyObject的属性 .
如果你需要为你的类添加新的属性,那么回到你从构建器消耗的单元测试而不是从硬编码的对象实例化中更容易(我不知道该怎么称呼它,所以请原谅我的术语) .
好吧,如果你有太多的参数,你不想做带参数的构造函数....这是一个选项
这是另一种选择 . 使用
protected
成员和派生类声明一个基类,该派生类重新定义成员以使它们是公共的 .创建代码将使用派生类 .
但是对于所有需要不变性的情况,请使用基类: