有几种不同的使用场景,这取决于Children
属性是否应该返回数据的持久快照、实时视图或临时只读视图,如果数据被修改,将明确失效,或者临时只读视图,如果数据被修改,其行为将未指定。
在使用可变对象 [例如 ] 时,我建议遵循的一个关键规则List<T>
是,如果对对象的引用可能会暴露给可能会改变其状态的某些方面的代码,那么最多应该有一个(通常正好是一个)系统中的对象,该对象将该对象的状态视为其自身的一部分。.NET 和 Java 都没有提供任何约定来指示对象是否持有一个字段,以封装其目标持有的可变状态;必须手动跟踪它。
可变类型的读写属性仅适用于明确公开该属性的对象对其所引用的对象没有所有权利益的情况。例如,aList<Control>
封装了其引用包含在其中的对象的身份;Control
这些控件的可变方面(例如它们的位置)不构成List<Control>
. 然而,像你这样的东西Node<T>
似乎应该拥有Children
,因为添加一个孩子SomeNode.Children
将被视为对SomeNode
. 因此,Children
属性应该是不可变的 [不仅仅是只读!] 类型(在这种情况下所有权无关紧要),否则它应该是只读属性。Children
否则,如果代码试图设置为List<T>
某个其他对象拥有的a ,就无法避免模糊语义。
如果作为对拥有的Children
a 的只读引用公开,则必须在第一次尝试读取属性返回之前创建一个 new (在 的构造函数中创建 可能是最简单的)。另一种方法是定义一个私有类,该类包含对 a 的引用并实现并访问该节点的内部字段。使用这种方法,即使没有添加任何对象,a也必须创建轻量级对象[它可以在访问时延迟创建,但在构造函数中创建它可能更好],但是诸如或之类的东西的实现List<T>
Node<T>
List<T>
Children
List<T>
Node<T>
Node.ChildList
Node<T>
IList<T>
ICollection
Node<T>
Node.ChildList
Children
ChildList
ICollection.Count
IEnumerable<T>.GetEnumerator()
可以首先测试节点是否已经创建了一个集合,如果没有,则简单地返回适合空集合的东西。
顺便说一句,如果T
是一个类类型并且许多节点将只有一个孩子,那么Node<T>
存储 aCount
和 an可能Object
比存储 a更有利List<T>
。如果一个节点没有子节点,Object[]
则null
; 对于一个孩子,它会持有一个T
;对于更多的孩子,要么 aT[]
要么 a List<T>
。当访问节点的子节点时,这将需要更复杂的类型检查逻辑,但对于具有零个或一个子节点的节点,它将消除一些堆分配。