在C#和.Net中实施父子关系

时间:2009-06-11 18:23:10

标签: c# .net

我们采取以下两个类:

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基本没有区别。

5 个答案:

答案 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,并允许删除孩子。但是后者破坏了亲子关系的强有力执行。