让我们学习以下两个课程:
public class CollectionOfChildren
{
public Child this[int index] { get; }
public void Add(Child c);
}
public class Child
{
public CollectionOfChildren Parent { get; }
}
Child的Parent属性应始终返回Child所在的CollectionOfChildren;如果孩子不在此类集合中,则返回null。在这两个类之间,该不变性应得到维护,并且该类的使用者不应被破坏(很容易)。
您如何实现这种关系? CollectionOfChildren无法设置Child的任何私有成员,那么应该如何通知Child它已添加到集合中?(如果孩子已经是集合的一部分,则抛出异常是可以接受的。)
internal
关键字已被提及。我现在正在编写WinForms应用程序,因此所有内容都在同一程序集中,这与public
基本没有区别。
public class CollectionOfChildren
{
public Child this[int index] { get; }
public void Add(Child c) {
c.Parent = this;
innerCollection.Add(c);
}
}
public class Child
{
public CollectionOfChildren Parent { get; internal set; }
}
我的答案包含解决方案-第一个使用嵌套类,以允许内部类访问外部类。后来我意识到,如果精心设计了属性getter和setter以避免无限的间接递归,则无需访问其他类的私有数据,因此也不需要嵌套的classe。
为了避免internal
字段出现问题,您可以将集合类嵌套到项目类中,然后将字段设置为private
。以下代码并不完全符合您的要求,但是显示了如何创建一对多关系并使之保持一致。一个Item
可能有一个父母和多个孩子。当且仅当某项具有父项时,该项才会在父项的子级集合中。我未经测试就编写了即席代码,但我认为没有办法打破Item
类的视线。
public class Item
{
public Item() { }
public Item(Item parent)
{
// Use Parent property instead of parent field.
this.Parent = parent;
}
public ItemCollection Children
{
get { return this.children; }
}
private readonly ItemCollection children = new ItemCollection(this);
public Item Parent
{
get { return this.parent; }
set
{
if (this.parent != null)
{
this.parent.Children.Remove(this);
}
if (value != null)
{
value.Children.Add(this);
}
}
}
private Item parent = null;
ItemCollection
类嵌套在Item
类内部,以访问私有字段parent
。
public class ItemCollection
{
public ItemCollection(Item parent)
{
this.parent = parent;
}
private readonly Item parent = null;
private readonly List<Item> items = new List<Item>();
public Item this[Int32 index]
{
get { return this.items[index]; }
}
public void Add(Item item)
{
if (!this.items.Contains(item))
{
this.items.Add(item);
item.parent = this.parent;
}
}
public void Remove(Item item)
{
if (this.items.Contains(item))
{
this.items.Remove(item);
item.parent = null;
}
}
}
}
UPDATE
我现在(但只是粗略地)检查了代码,并且我相信它不会嵌套类就可以工作,但是我还不确定。都是使用Item.Parent
属性而不会引起无限循环,但是已经存在的检查以及为效率而添加的检查可以避免这种情况-至少我相信。
public class Item { // Constructor for an item without a parent. public Item() { } // Constructor for an item with a parent. public Item(Item parent) { // Use Parent property instead of parent field. this.Parent = parent; } public ItemCollection Children { get { return this.children; } } private readonly ItemCollection children = new ItemCollection(this);
[重要的是
Parent
属性,它将触发父级的子级集合的更新并防止进入无限循环。
public Item Parent { get { return this.parent; } set { if (this.parent != value) { // Update the parent field before modifing the child // collections to fail the test this.parent != value // when the child collection accesses this property. // Keep a copy of the old parent for removing this // item from its child collection. Item oldParent = this.parent; this.parent = value; if (oldParent != null) { oldParent.Children.Remove(this); } if (value != null) { value.Children.Add(this); } } } } private Item parent = null; }
ItemCollection
类的重要部分是私有parent
字段,该字段使项目集合了解其所有者以及触发所添加Add()
属性更新的Remove()
和Parent
方法或已删除的项目。
public class ItemCollection
{
public ItemCollection(Item parent)
{
this.parent = parent;
}
private readonly Item parent = null;
private readonly List<Item> items = new List<Item>();
public Item this[Int32 index]
{
get { return this.items[index]; }
}
public void Add(Item item)
{
if (!this.items.Contains(item))
{
this.items.Add(item);
item.Parent = this.parent;
}
}
public void Remove(Item item)
{
if (this.items.Contains(item))
{
this.items.Remove(item);
item.Parent = null;
}
}
}
我最近以通用集合和要由子项实现的接口的形式实现了与AgileJon类似的解决方案:
此序列对您有用吗?
我最近也正在研究它,并考虑将这种关系真正地实施为尽可能防止错误。另外,我尝试保持其通用性并尽可能安全地输入。这可能只是过分的杀伤力,但我仍然想分享。