在C#和.Net中实现父子关系
让我们学习以下两个课程:在C#和.Net中实现父子关系,c#,.net,C#,.net,让我们学习以下两个课程: public class CollectionOfChildren { public Child this[int index] { get; } public void Add(Child c); } public class Child { public CollectionOfChildren Parent { get; } } 子项的父属性应始终返回子项所在的CollectionOfChildren,如果子项不在此类集合中,则返回nu
public class CollectionOfChildren
{
public Child this[int index] { get; }
public void Add(Child c);
}
public class Child
{
public CollectionOfChildren Parent { get; }
}
子项的父属性应始终返回子项所在的CollectionOfChildren,如果子项不在此类集合中,则返回null。在这两个类之间,这个不变量应该被维护,并且不应该被类的使用者破坏(很好,很容易)
你如何实现这种关系?CollectionOfChildren无法设置Child的任何私有成员,那么它应该如何通知Child它已添加到集合中?
(如果子项已经是集合的一部分,则可以抛出异常。)
提到了
内部
关键字。目前我正在编写一个WinForms应用程序,因此所有内容都在同一个程序集中,这与public
基本上没有什么不同。我的答案包含解决方案-第一个使用嵌套类允许内部类访问外部类。后来我意识到,如果仔细设计属性getter和setter以避免无限间接递归,则不需要访问另一个类的私有数据,因此不需要嵌套类
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; }
}
为了避免内部
字段出现问题,您可以将collection类嵌套到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
类中,以获得对私有字段父项的访问权
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;
}
}
}
}
重要的部分是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
类的重要部分是privateparent
字段,该字段使项集合知道其所有者,以及触发已添加或已删除项的parent
属性更新的Add()
和Remove()
方法
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;
}
}
}
公共类ItemCollection
{
公共项目集合(项目父级)
{
this.parent=parent;
}
私有只读项父项=null;
私有只读列表项=新列表();
公共项目本[Int32索引]
{
获取{返回this.items[index];}
}
公共作废添加(项目)
{
如果(!this.items.Contains(item))
{
此.items.Add(item);
item.Parent=this.Parent;
}
}
公共作废删除(项目)
{
如果(本项目包含(项目))
{
此。项。删除(项);
item.Parent=null;
}
}
}
我最近实施了一个类似于AgileJon的解决方案,其形式为一个通用集合和一个由子项实现的接口:
ChildItemCollection:
当您想在XML中序列化此类对象时,这一点特别有用:您无法序列化父属性,因为它会导致循环引用,但您希望保留父/子关系。此序列是否适用于您
- 调用CollectionOfChild.Add(子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作为子级检查父级的“真实性”的方式,有效地创建和内部方法
请记住,你可以通过反思来消除这一切,所以你真的只需要稍微努力去阻止人们。Thomas使用显式接口是另一种阻止错误的方法,尽管我认为这有点困难。我最近也在研究它,并考虑尽可能地加强这种关系以防错误。此外,我还尽量保持它的通用性和类型安全性。这可能是过度设计,但我还是想和大家分享
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);
}
}
}
最后的实现还具有子项的ID,并允许删除子项。但是后者破坏了父子关系的强大执行。我正在编写一个WinForms程序,因此所有内容都在同一个程序集中。根据我的理解,在这里,内部和公共对我来说没有什么不同。是的,在这种情况下,你完全可以去掉“内部”修饰符。我把你的“类消费者”部分理解为这可能是第三方开发者使用的API的一部分。啊-我明白了。我想的更多的是使用该类的其他代码、其他程序员等等。即使我是唯一使用该类的人,如果某个类具有逻辑不变量,我也不应该(即使在该类之外的我自己的代码中)违反该不变量。您仍然会遇到子对象没有父对象的风险(因为您可以只创建一个子级而不将其添加到父级),您需要使用某种父级/子级工厂来创建这些子级来处理该问题。该属性不应该改名为SiblingsAndSelf吗
public interface IChildItem<P> where P : class
{
P Parent { get; set; }
}
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; }
}
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;
}
}
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)
{
}
}
var parent = new Parent();
var child1 = new Child(parent.ChildCollection);