代码之家  ›  专栏  ›  技术社区  ›  Thanatos

在C和.NET中加强父子关系

  •  6
  • Thanatos  · 技术社区  · 16 年前

    我们上以下两节课:

    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它已添加到集合中? (如果子项已经是集合的一部分,则可以引发异常。)


    这个 internal 关键字已被提及。我正在编写一个winforms应用程序,所以所有的东西都在同一个程序集中,这与 public .

    4 回复  |  直到 16 年前
        1
  •  7
  •   AgileJon    16 年前
    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; }
    }
    
        2
  •  2
  •   Daniel Brückner    16 年前

    我的答案包含解决方案-第一个使用嵌套类来允许内部类访问外部类。后来我意识到,如果属性getter和setter是为了避免无限的间接递归而精心设计的,则不需要访问另一个类的私有数据,因此不需要嵌套类。

    为了避免 internal 字段您可以将collection类嵌套到item类中并使字段 private . 下面的代码并不完全是您所请求的,而是演示如何创建一对多关系并保持其一致性。安 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 类嵌套在 项目 类以访问私有字段 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;
    }
    

    的重要部分 项目集合 班级是私人的 起源 使项集合知道其所有者和 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;
            }
        }
    }
    
        3
  •  1
  •   Thomas Levesque    16 年前

    我最近实现了一个类似于agilejon的解决方案,其形式是一个泛型集合和一个由子项实现的接口:

    子项集合<p,t>:

    /// <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
    }
    

    一级项目:

    public interface IChildItem<P> where P : class
    {
        P Parent { get; 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序列化此类对象时,这一点特别有用:您不能序列化父属性,因为它会导致循环引用,但您希望保留父/子关系。

        4
  •  0
  •   Colin Burnett    16 年前

    这个序列对你有用吗?

    • 呼叫 CollectionOfChild.Add(Child c)
    • 将子项添加到内部集合
    • CollectionOfChild.Add 调用 Child.UpdateParent(this)
    • Child.UpdateParent(CollectionOfChild newParent) 电话 newParent.Contains(this) 若要确保子项在该集合中,请更改 Child.Parent 因此。也要打电话 CollectionOfChild.Remove(this) 从旧父集合中移除自身。
    • CollectionOfChild.Remove(Child) 会检查 父母 以确保在从集合中移除子集合之前不再是子集合。

    写下一些代码:

    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作为子方法来检查父对象的“真实性”,从而有效地创建和内部方法。

    记住,你可以用反省来消除所有这些,所以你只需要让周围的人变得稍微有点困难就可以阻止他们。托马斯使用显式接口是另一种阻止的方法,尽管我认为这有点难。