我们采取以下两个类:
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
基本没有区别。
答案 0 :(得分:7)
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; }
}
答案 1 :(得分:2)
我的答案包含解决方案 - 第一个使用嵌套类来允许内部类访问外部类。后来我意识到没有必要访问其他类的私有数据,因此如果设计getter和setter属性以避免无限的间接递归,则不需要嵌套的classe。
为避免internal
字段出现问题,您可以将集合类嵌套到项类中,并创建字段private
。以下代码并不完全符合您的要求,但展示了如何创建一对多关系并使其保持一致。 Item
可能有一个父母和多个孩子。当且仅当项目具有父项时,它将在父项的子集合中。我在没有测试的情况下编写了代码adhoc,但我认为没有办法从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;
}
}
}
}
<强>更新强>
我现在检查了代码(但只是大致),我相信它可以在没有嵌套类的情况下工作,但我还没有绝对确定。这都是关于使用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
属性,它将触发父级子集合的更新并阻止进入infinte循环。
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;
}
}
}
答案 2 :(得分:1)
我最近实现了类似于AgileJon的解决方案,采用通用集合的形式,以及由子项实现的接口:
ChildItemCollection&LT; P,T&GT; :
/// <summary>
/// Collection of child items. This collection automatically set the
/// Parent property of the child items when they are added or removed
/// </summary>
/// <typeparam name="P">Type of the parent object</typeparam>
/// <typeparam name="T">Type of the child items</typeparam>
public class ChildItemCollection<P, T> : IList<T>
where P : class
where T : IChildItem<P>
{
private P _parent;
private IList<T> _collection;
public ChildItemCollection(P parent)
{
this._parent = parent;
this._collection = new List<T>();
}
public ChildItemCollection(P parent, IList<T> collection)
{
this._parent = parent;
this._collection = collection;
}
#region IList<T> Members
public int IndexOf(T item)
{
return _collection.IndexOf(item);
}
public void Insert(int index, T item)
{
if (item != null)
item.Parent = _parent;
_collection.Insert(index, item);
}
public void RemoveAt(int index)
{
T oldItem = _collection[index];
_collection.RemoveAt(index);
if (oldItem != null)
oldItem.Parent = null;
}
public T this[int index]
{
get
{
return _collection[index];
}
set
{
T oldItem = _collection[index];
if (value != null)
value.Parent = _parent;
_collection[index] = value;
if (oldItem != null)
oldItem.Parent = null;
}
}
#endregion
#region ICollection<T> Members
public void Add(T item)
{
if (item != null)
item.Parent = _parent;
_collection.Add(item);
}
public void Clear()
{
foreach (T item in _collection)
{
if (item != null)
item.Parent = null;
}
_collection.Clear();
}
public bool Contains(T item)
{
return _collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _collection.Count; }
}
public bool IsReadOnly
{
get { return _collection.IsReadOnly; }
}
public bool Remove(T item)
{
bool b = _collection.Remove(item);
if (item != null)
item.Parent = null;
return b;
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return _collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (_collection as System.Collections.IEnumerable).GetEnumerator();
}
#endregion
}
IChildItem&LT; T&GT; :
public interface IChildItem<P> where P : class
{
P Parent { get; set; }
}
使用接口的唯一缺点是无法在set访问器上放置internal
修饰符......但无论如何,在典型的实现中,此成员将“隐藏”在显式实现之后:
public class Employee : IChildItem<Company>
{
[XmlIgnore]
public Company Company { get; private set; }
#region IChildItem<Company> explicit implementation
Company IChildItem<Company>.Parent
{
get
{
return this.Company;
}
set
{
this.Company = value;
}
}
#endregion
}
public class Company
{
public Company()
{
this.Employees = new ChildItemCollection<Company, Employee>(this);
}
public ChildItemCollection<Company, Employee> Employees { get; private set; }
}
当您想要在XML中序列化此类对象时,这尤其有用:您无法序列化Parent属性,因为它会导致循环引用,但您希望保持父/子关系。
答案 3 :(得分:0)
这个序列可以适合你吗?
CollectionOfChild.Add(Child c)
CollectionOfChild.Add
调用Child.UpdateParent(this)
Child.UpdateParent(CollectionOfChild newParent)
调用newParent.Contains(this)
以确保该子集在该集合中,然后相应地更改Child.Parent
的支持。还必须致电CollectionOfChild.Remove(this)
以从旧父母的集合中删除自己。CollectionOfChild.Remove(Child)
会检查Child.Parent
以确保它不再是孩子的收藏品,然后才会将该儿童从收藏品中删除。放下一些代码:
public class CollectionOfChild
{
public void Add(Child c)
{
this._Collection.Add(c);
try
{
c.UpdateParent(this);
}
catch
{
// Failed to update parent
this._Collection.Remove(c);
}
}
public void Remove(Child c)
{
this._Collection.Remove(c);
c.RemoveParent(this);
}
}
public class Child
{
public void UpdateParent(CollectionOfChild col)
{
if (col.Contains(this))
{
this._Parent = col;
}
else
{
throw new Exception("Only collection can invoke this");
}
}
public void RemoveParent(CollectionOfChild col)
{
if (this.Parent != col)
{
throw new Exception("Removing parent that isn't the parent");
}
this._Parent = null;
}
}
不确定这是否有效但想法应该如此。它通过使用Contains作为孩子检查父母“真实性”的方式来有效地创建和内部方法。
请记住,你可以用反射来消除所有这些,所以你真的只需要让它稍微难以绕过来阻止人们。托马斯使用显式接口是另一种阻止的方法,尽管我认为这有点难度。
答案 4 :(得分:0)
我最近也正在研究它,并考虑将这种关系真正地实施为尽可能防止错误。另外,我尝试保持其通用性并尽可能安全地输入。它可能只是工程过度,但我还是要分享。
man grep
以下是示例:
public class ChildCollection<TChild> : IEnumerable<TChild>
where TChild : ChildCollection<TChild>.Child
{
private readonly List<TChild> childCollection = new List<TChild>();
private void Add(TChild child) => this.childCollection.Add(child);
public IEnumerator<TChild> GetEnumerator() => this.childCollection.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public abstract class Child
{
private readonly ChildCollection<TChild> childCollection;
protected Child(ChildCollection<TChild> childCollection)
{
this.childCollection = childCollection;
childCollection.Add((TChild)this);
}
}
}
然后将一个孩子添加到父母中看起来像:
public class Parent
{
public ChildCollection<Child> ChildCollection { get; }
public Parent()
{
ChildCollection = new ChildCollection<Child>();
}
}
public class Child : ChildCollection<Child>.Child
{
public Child(ChildCollection<Child> childCollection) : base(childCollection)
{
}
}
最后的实现还为孩子提供了ID,并允许删除孩子。但是后者破坏了亲子关系的强有力执行。