In C# 4.0, is there any way to make an otherwise private member of one class available only to a specific other class?

前端 未结 9 1877
旧时难觅i
旧时难觅i 2021-01-31 19:27

We\'re creating an object hierarchy where each item has a collection of other items, and each item also has a Parent property pointing to its parent item. Pretty st

9条回答
  •  遥遥无期
    2021-01-31 20:02

    How about you make sure that only the item's current collection can orphan the item. That way no other collection can set the item's parent while it belongs to a collection. You could use a unique key of some sort so that a third party couldn't get involved:

    public sealed class ItemsCollection : ObservableCollection
    {
        private Dictionary guids = new Dictionary();
    
        public ItemsCollection(Item owner)
        {
            this.Owner = owner;
        }
    
        public Item Owner { get; private set; }
    
        private Guid CheckParent(Item item)
        {
            if (item.Parent != null)
                throw new Exception("Item already belongs to another ItemsCollection");
            //item.Parent = this.Owner; // <-- This is where we need to access the private Parent setter     
            return item.BecomeMemberOf(this);
    
        }
    
        protected override void InsertItem(int index, Item item)
        {
            Guid g = CheckParent(item);
            base.InsertItem(index, item);
            guids.Add(item, g);
        }
    
        protected override void RemoveItem(int index)
        {
            Item item = this[index];
            DisownItem(item);
            base.RemoveItem(index);
        }
    
        protected override void DisownItem(Item item)
        {
            item.BecomeOrphan(guids[item]);
            guids.Remove(item);            
        }
    
        protected override void SetItem(int index, Item item)
        {
            var existingItem = this[index];
            if (item == existingItem)
                return;
            Guid g = CheckParent(item);
            existingItem.BecomeOrphan(guids[existingItem]);
            base.SetItem(index, item);
            guids.Add(item, g);
        }
    
        protected override void ClearItems()
        {
            foreach (var item in this)
                DisownItem(item);
            base.ClearItems();
        }
    }
    
    
    
    
    public class Item
    {
        public string Name { get; set; }
        public Item Parent { get; private set; }
    
        public ItemsCollection ChildItems;
    
        public Item()
        {
            this.ChildItems = new ItemsCollection(this);
        }
    
        private Guid guid;
    
        public Guid BecomeMemberOf(ItemsCollection collection)
        {
            if (Parent != null)
                throw new Exception("Item already belongs to another ItemsCollection");
            Parent = collection.Owner;
            guid = new Guid();
            return guid; // collection stores this privately         
        }
    
        public void BecomeOrphan(Guid guid) // collection passes back stored guid         
        {
            if (guid != this.guid)
                throw new InvalidOperationException("Item can only be orphaned by its current collection");
            Parent = null;
        }
    }
    

    Obviously there is redundancy there; the item collection is storing a second item collection (the dictionary). But there are numerous options for overcoming that which I assume you can think of. It's beside the point here.

    However I do suggest you consider moving the task of child-item management to the item class, and keep the collection as 'dumb' as possible.

    EDIT: in response to your quesion, how does this prevent and item from being in two ItemsCollections:

    You ask what the point of the guids is. Why not just use the collection instance itself?

    If you replace the guid argument with a collection reference, you could add an item to two different collections like this:

    {
        collection1.InsertItem(item);  // item parent now == collection1
        collection2.InsertItem(item);  // fails, but I can get around it:
        item.BecomeOrphan(collection1); // item parent now == null 
        collection2.InsertItem(item);  // collection2 hijacks item by changing its parent (and exists in both collections)
    }
    

    Now imagine doing this with the guid argument:

    {
        collection1.InsertItem(item);  // item parent now == collection1
        collection2.InsertItem(item);  // fails, so...
        item.BecomeOrphan(????); // can't do it because I don't know the guid, only collection1 knows it.
    }
    

    So you can't add an item to more than one ItemsCollection. And ItemsCollection is sealed so you can't subclass it and override its Insert method (and even if you did that, you still couldn't change the item's parent).

提交回复
热议问题