在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
类的重要部分是private
parent
字段,该字段使项集合知道其所有者,以及触发已添加或已删除项的
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);