有没有一种简单的方法可以使一个示例成为不可变的?
让我们来做一个例子,我有一个类,里面有很多数据字段(只有数据,没有行为):
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。
那么,有没有什么自动的方法来生成这个不可变的类呢?还是另一种允许在创建期间可变但一旦初始化就不可变的方法?
我知道字段可以标记为“只读”,但是对象将在类外部初始化,并且将所有字段作为构造函数参数传递似乎是一个坏主意(太多参数)。
8条答案
按热度按时间ou6hu8tu1#
不,没有简单的方法使 * 任何 * 类型不可变,特别是如果你想要“深度”不变性(即。即,其中不可变对象不能通过不可变对象到达)。您必须显式地将类型设计为不可变的。使类型不可变的常用机制如下:
readonly
。(或者,从C# 6 / Visual Studio 2015开始,使用只读自动实现的属性。)T[]
、List<T>
、Dictionary<TKey,TValue>
等)。).如果你需要公开集合,要么在一个防止修改的 Package 器中返回它们(例如:例如
.AsReadOnly()
),或者至少返回内部集合的新副本。kognpnkq2#
嗯,我会列举我对这个问题的第一个想法。..
**1.**如果您唯一担心的是在程序集之外进行操作,请使用
internal
setters。internal
将使您的属性仅对同一程序集中的类可用。例如:**2.**我不同意在构造函数中有很多参数一定是个坏主意。
**3.**您可以在运行时生成另一个类型,该类型是您的类型的只读版本。我可以详细说明这一点,但我个人认为这是矫枉过正。
最好的,尤利安
r8uurelv3#
作为另一种解决方案,您可以使用动态代理。Entity Framework 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/)的原始示例4ktjp1zp4#
我建议使用抽象基类型
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)
,然后根据需要修改它。kmynzznz5#
你在你的问题中暗示了一种方法,但我不确定这对你来说是否不是一种选择:
由于构造函数是操作Author和Title的唯一方法,因此该类在构造后实际上是不可变的。
编辑:
正如stakx所提到的,我也是使用构建器的忠实粉丝--特别是因为它使单元测试更容易。对于上面的类,你可以有一个构建器,比如:
通过这种方式,您可以使用默认值构造对象,或者根据需要覆盖它们,但MyObject的属性在构造后不能更改。
如果你需要向你的类添加新的属性,回到你的单元测试中去要容易得多,它是从构建器而不是从硬编码的对象示例化(我不知道该怎么称呼它,所以请原谅我的术语)。
raogr8fs6#
好吧,如果你有太多的参数,而你不想用参数构造函数。。。。这里有一个选择
aiazj4mn7#
还有一个选择声明一个具有
protected
成员的基类和一个重新定义成员使其为公共的派生类。创建代码将使用派生类。
但是对于所有需要不变性的情况,请使用基类:
n1bvdmb68#
一种简单(但不太安全)的方法是定义一个getter-only接口,并在构建对象后使用它。这类似于将
List<T>
转换为IReadOnlyList<T>
。另一个好处是您不需要复制对象。是的,对象的用户仍然可以向下转换以获得可变对象(只要MyObject
保持public
),但我认为这足以表达“这是一个只读对象”的意图。“