Enforcing parent-child relationship in C# and .Net

后端 未结 5 687
夕颜
夕颜 2020-12-14 23:29

Let\'s take the following two classes:

public class CollectionOfChildren
{
    public Child this[int index] { get; }
    public void Add(Child c);
}

public          


        
5条回答
  •  刺人心
    刺人心 (楼主)
    2020-12-15 00:12

    My answer contains to solutions - the first one uses nested classes to allow the inner class to access the outer class. Later I realized that there is no need to access private data of the other class, hence no need for nested classe, if the property getters and setters are designed carefully to avoid infinite indirect recursions.

    To avoid the problem with internal fields you can just nest the collection class into the item class and make the field private. The following code is not exactly what you requestes, but shows how to create a one-to-many relationship and keep it consistent. An Item may have one parent and many children. If and only if an item has a parent, then it will be in the child collection of the parent. I wrote the code adhoc without testing, but I think there is no way to break this from outsight of the Itemclass.

    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;
    

    The ItemCollection class is nested inside the Item class to gain access to the private field parent.

        public class ItemCollection
        {
            public ItemCollection(Item parent)
            {
                this.parent = parent;
            }
            private readonly Item parent = null;
            private readonly List items = new List();
    
            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;
                }
            }
        }
    }
    

    UPDATE

    I checked the code now (but only roughly) and I believe it will work without nesting the classes, but I am not yet absolutly sure. It's all about using the Item.Parent property without causing an infinite loop, but the checks that were already in there and the one I added for efficency protect from this situation - at least I believe it.

    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);
    

    The important part is the Parent property that will trigger the update of the parent's child collection and prevent from entering a infinte loop.

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

    The the important parts of the ItemCollection class are the private parent field that makes the item collection aware of its owner and the Add() and Remove() methods that trigger updates of the Parent property of the added or removed item.

    public class ItemCollection
    {
        public ItemCollection(Item parent)
        {
            this.parent = parent;
        }
        private readonly Item parent = null;
        private readonly List items = new List();
    
        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;
            }
        }
    }
    

提交回复
热议问题