2010-01-20 47 views
1

的一般情况的一个例子:返回一个不可变集合当物品必须是可变的最初

public class Validator 
{ 
    // ... 
    public ReadOnlyCollection<InvalidContainer> ContainersUnderMinimum 
    { 
     get { return _containersUnderMinimum.AsReadOnly(); } 
    } 
} 

public class InvalidContainer 
{ 
    // ... 
    public int LowestVolume { get; set; } 
} 

上面的验证类需要在其构造的其他项目的集合,然后将无效项内部列表。每个容器都有许多小容器(想想试管架),而班级希望找到最低容量。当找到物品(管)时,构造函数将添加到列表中,并在找到物品时更新现有列表对象。

的问题是验证要返回不可变对象的只读集合,但对象(InvalidContainers)必须是可变的施工后,这样的值可以是(基本上)积累。

重构使用的接口(IInvalidContainer)导致头痛,如通用集合不能被转换为基本类型的集合。

有什么好的模式或做法来解决这个问题?

编辑:为了澄清,其意图是具有属性值(集合)是不可变的。我知道ReadOnlyCollection只强制收集而不是收集项目的不变性。通常我会让项目不可变,但我不能在这个(和类似的)案例中。但是,我只希望在构造Validator类时突变。防止打电话者做不明智的事情并不是设计目标;目标是避免诱惑拥有可设置公共财产的呼叫者。

编辑:改变了标题为清楚起见。

编辑:这里的重构版本(基于LBushkin和递归建议):

public IEnumerable<IInvalidContainer> ContainersUnderMinimum 
{ 
    get 
    { 
     return _containersUnderMinimum.Cast<IInvalidContainer>(); 
    } 
} 

回答

2

如果我理解你的问题正确,要返回不可变类型的集合,但在内部保留一个可变的集合。这样做的典型方法是为您的类型创建基本类型(或接口),该类型是不可变的并返回该类型。您可以将项目转换为该类型(弱形式的不变性控制),也可以创建包装对象并返回这些(强类型的控件)。包装对象的创建可能更昂贵 - 但它们阻止了外部代码仅仅能够执行类型转换以避免不可变性。这是,顺便说一句,这ReadOnlyCollection<T>使用返回不可变的集合的机制。

为了克服这一事实,集合类型限制了铸造的支持,你可以使用LINQ返回不可变类型的不可变的副本:

_containersUnderMinimum.Cast<IInvalidContainer>().ToList().AsReadOnly() 

这将创建集合的副本 - 但它可能是好的足够 - 如果您不需要集合来反映运行时的更改。

此外,请注意,ReadOnlyCollection不需要(或强制)集合的元素的不可变性。相反,它可以防止接收器添加或删除元素 - 更改集合中的现有元素仍然是可能的。

+0

我担心的是,这是每一个属性被访问时创建一个新的列表。但是由于我经常抱怨过早的优化,这种担忧可能是没有根据的。 – TrueWill 2010-01-20 18:23:27

+0

你不一定需要建立一个新的列表。可以使用类似于'ReadOnlyCollection <>'的方法,并创建也动态地包装使用不可变的包装对象现有的列表元素的衍生物。事实上,你可以写一个返回扩展方法的'的IEnumerable <>'使用产量推迟评估当列表迭代到(假设你不需要直接索引)。 – LBushkin 2010-01-20 18:48:37

+0

这让我觉得,如果我需要* *优化,我可以做在构造函数中的演员/ ToList(填充回收后),并分配到其他领域。但是我怀疑你的收益率回报建议(或者只是将演员投入收益人)是一个更简单的选择。 – TrueWill 2010-01-21 14:48:47

0

也许我误解,但ReadOnlyCollection仅意味着该集合是只读的,而不是对象本身.. 。

+0

真实的,但这里的目的是使该属性值不变。 – TrueWill 2010-01-20 18:10:57

+0

哦............. – 2010-01-20 18:12:06

1

其实,这是可以投泛型集合:

ReadOnlyCollection<IInvalidContainer> result = 
    _containersUnderMinimum.Cast<IInvalidContainer>().ToList().AsReadOnly(); 

然而,这并不能阻止铸造消费者元素回来了。

1

如果你的可变对象只能通过方法来改变,我建议你在可变类型中包含一个引用,如果非null,它将标识一个封装相同数据的不可变类型的实例。你的可变类型应该包含一个创建不可变副本的方法;该方法应该创建并缓存一个新的不可变对象,如果它尚未拥有对其的引用。否则,它应该返回缓存的引用。任何变异方法都应该使不可变对象引用无效。使用这种方法,人们应该能够避免重复制作从未发生变异的对象。

相关问题