2017-08-30 16 views
0

我从this article开始获取不同类型的MutableKey集合。带MutableKey的KeyedCollection

我想为我的所有集合的抽象KeyedCollection基类,所以我使用泛型和我的目的一个接口,但我想知道如果有一个更优雅的解决方案,以避免暴露集合属性我的物品。

public class FooItem : IMyKeyedCollectionItem<FooItem> 
    {    
     // *** In this way the setter of the Collections property is public, any other solution to avoid this? **// 
     public HashSet<MyKeyedCollectionBase<FooItem>> Collections { get; set; } = new HashSet<MyKeyedCollectionBase<FooItem>>(); 

     private string _name;    
     public string Name 
     { 
      get { return _name; } 
      set 
      { 
       if (Collections != null) 
       { 
        foreach (var collection in Collections) 
        { 
         collection.ChangeKey(this, value); 
        } 

       } 
       _name = value; 
      } 
     } 
    } 

    /// <summary> 
    /// Interface for the mutablekey keyedcollection. 
    /// </summary> 
    /// <typeparam name="T"></typeparam>   
    public interface IMyKeyedCollectionItem<T> where T : IMyKeyedCollectionItem<T> 
    { 
     /// <summary> 
     /// Collections that contain this item. 
     /// </summary> 
     HashSet<MyKeyedCollectionBase<T>> Collections { get; set; } 
    } 

    // KeyedCollection is an abstract class, so I have to derive    
    public abstract class MyKeyedCollectionBase<T> : KeyedCollection<string, T> where T : IMyKeyedCollectionItem<T> 
    {    
     public MyKeyedCollectionBase() : base(StringComparer.OrdinalIgnoreCase, 0) { } // case-insensitive 

     public MyKeyedCollectionBase(MyKeyedCollectionBase<T> collection) 
     { 
      if (collection != null) 
      { 
       foreach (var item in collection) 
        Add(item); 
      } 
     }    

     protected override void InsertItem(int index, T item) 
     { 
      base.InsertItem(index, item); 

      AddCollectionToItem(item); 
     } 

     private void AddCollectionToItem(T item) 
     { 
      if (item.Collections == null) 
       item.Collections = new HashSet<MyKeyedCollectionBase<T>>(); 

      item.Collections.Add(this); 
     } 

     private void RemoveCollectionFromItem(T item) 
     { 
      item.Collections.Remove(this); 

      if (item.Collections.Count == 0)     
       item.Collections = null;          
     } 

     protected override void SetItem(int index, T item) 
     { 
      var replaced = Items[index]; 
      base.SetItem(index, item); 
      AddCollectionToItem(item); 
      RemoveCollectionFromItem(replaced); 
     } 

     protected override void RemoveItem(int index) 
     { 
      var removedItem = Items[index]; 
      base.RemoveItem(index); 
      RemoveCollectionFromItem(removedItem); 
     } 

     protected override void ClearItems() 
     { 
      foreach (var removed in Items) 
       RemoveCollectionFromItem(removed); 

      base.ClearItems(); 
     } 

     // Expose this method internally to allow mutable item keys: When the key for an item changes, this method is used to change the key in the lookup dictionary 
     internal virtual void ChangeKey(T item, string newKey) 
     { 
      base.ChangeItemKey(item, newKey); 
     }    
    } 

    public class MyFooKeyedCollection : MyKeyedCollectionBase<FooItem> 
    { 
     protected override string GetKeyForItem(FooItem item) 
     { 
      return item.Name; 
     } 
    } 
+0

https://codereview.stackexchange.com/可能会感兴趣的。 – mjwills

+0

两件事。你总是调用'ChangeKey',即使'_name == value'似乎是不必要的。其次,'FooItem'“知道”它的收藏,这不应该是它的责任。如何实现[INotifyPropertyChanged](https://stackoverflow.com/a/1316417/1336590)并让集合为其包含的每个对象注册该事件,然后在任何引发该事件的对象时适当地调用'ChangeKey' 。 - 这样,集合全权负责持有和维护对象,并且对象保持无视。 – Corak

+0

嗨@Corak,伟大的提示,我阐述了它,它似乎很好。 – ilCosmico

回答

0

感谢@Corak我找到了办法......

public class KeyChangedEventArgs : PropertyChangedEventArgs 
    { 
     public virtual string NewKey { get; } 

     public KeyChangedEventArgs(string propertyName, string newKey) : base(propertyName) 
     { 
      NewKey = newKey; 
     } 
    } 

    public delegate void KeyChangedEventHandler(object sender, KeyChangedEventArgs e); 

    public interface INotifyKeyChanged 
    { 
     event KeyChangedEventHandler KeyChanged; 
    } 

    /// <summary> 
    /// Interface for the mutablekey keyedcollection. 
    /// </summary> 
    /// <typeparam name="T"></typeparam>   
    public interface IMyKeyedCollectionItem<T> : INotifyKeyChanged where T : IMyKeyedCollectionItem<T> 
    { 
     /// <summary> 
     /// Gets the key for the item of the collection. 
     /// </summary> 
     /// <returns>The item key.</returns> 
     string GetKey(); 
    } 

    public class FooItem : IMyKeyedCollectionItem<FooItem> 
    { 
     public FooItem(string name) 
     { 
      Name = name; 
     }       

     private string _name;    
     public string Name 
     { 
      get { return _name; } 
      set 
      { 
       if (!String.Equals(_name, value, StringComparison.OrdinalIgnoreCase)) // case-insensitive 
       {       
        // The key on the KeyedCollection must be changed before changing the key on the item. 
        OnKeyChanged(value); 

        _name = value;       
       } 
      } 
     } 

     public event KeyChangedEventHandler KeyChanged; 

     [NotifyPropertyChangedInvocator] 
     protected virtual void OnKeyChanged(string newKey, [CallerMemberName] string propertyName = null) 
     { 
      KeyChanged?.Invoke(this, new KeyChangedEventArgs(propertyName, newKey)); 
     } 

     public string GetKey() 
     { 
      return Name; 
     } 
    } 

    // KeyedCollection is an abstract class, so I have to derive    
    public abstract class MyKeyedCollectionBase<T> : KeyedCollection<string, T> where T : IMyKeyedCollectionItem<T> 
    {    
     public MyKeyedCollectionBase() : base(StringComparer.OrdinalIgnoreCase, 0) { } // case-insensitive 

     public MyKeyedCollectionBase(MyKeyedCollectionBase<T> collection) 
     { 
      if (collection != null) 
      { 
       foreach (var item in collection) 
        Add(item); 
      } 
     }    

     protected override void InsertItem(int index, T item) 
     { 
      base.InsertItem(index, item); 

      SubscribeKeyChanged(item); 
     } 

     private void SubscribeKeyChanged(T item) 
     {     
      ((INotifyKeyChanged)item).KeyChanged += OnItemKeyChanged; 
     } 

     private void OnItemKeyChanged(object sender, KeyChangedEventArgs e) 
     { 
      var item = (T) sender; 
      ChangeKey(item, e.NewKey); 
     } 

     private void UnsubscribeKeyChanged(T item) 
     { 
      ((INotifyKeyChanged)item).KeyChanged -= OnItemKeyChanged;     
     } 

     protected override void SetItem(int index, T item) 
     { 
      var replaced = Items[index]; 
      base.SetItem(index, item); 
      SubscribeKeyChanged(item); 
      UnsubscribeKeyChanged(replaced); 
     } 

     protected override void RemoveItem(int index) 
     { 
      var removedItem = Items[index]; 
      base.RemoveItem(index); 
      UnsubscribeKeyChanged(removedItem); 
     } 

     protected override void ClearItems() 
     { 
      foreach (var removed in Items) 
       UnsubscribeKeyChanged(removed); 

      base.ClearItems(); 
     } 

     // Expose this method internally to allow mutable item keys: When the key for an item changes, this method is used to change the key in the lookup dictionary 
     internal virtual void ChangeKey(T item, string newKey) 
     { 
      base.ChangeItemKey(item, newKey); 
     }    
    } 

    public class MyFooKeyedCollection : MyKeyedCollectionBase<FooItem> 
    { 
     protected override string GetKeyForItem(FooItem item) 
     { 
      return item.Name; 
     } 
    }