如何获取集合的不可变值

时间:2012-09-24 00:29:10

标签: c# algorithm design-patterns collections

作为我的表示架构的一部分,我有一个实现IEditableObject的基类,所以当BeginEdit()获取状态快照时,会通过反射为所有可写的非集合属性创建一个Dictionary。

var propertyInfos = GetType().GetWritableNonCollectionPropertyInfos();
var dic = propertyInfos
            .ToDictionary(pi => pi.Name, pi => pi.GetValue(this, null));

此词典是IsDirty跟踪和回滚CancelEdit()的基础。

默认行为排除集合的原因是因为它们通常不是必需的,枚举可能很昂贵,而且通常只是一种痛苦。

就这个问题而言,我有一个用例,其中集合状态是必要的。它是自定义对象的HashSet,并且一次很少超过5个。从上面的代码中可以看出,集合的值最终将成为对集合本身的引用;这将随着集合的变化而变化,因此你无法判断它是否真的变脏了。

所以我需要为集合存储一些值,这些值将是不可变的,并且与集合的值相比可以更改。以下是我提出的一般想法:

  1. 提供受保护的IncludeCollections标志
  2. 当标志为true时,为子类提供模板方法以返回仅包含集合属性的其他键值对

        if(IncludeCollections) {
            var collectionOnlyDictionary = GetCollectionOnlyDictionary();
            foreach (var kvp in collectionOnlyDictionary) {                 
                dic.Add(GetCollectionKey(kvp.Key), kvp.Value);
            }
        }
    
  3. 提供另一种模板方法来获取集合的一些不可变值。这应该允许变化,但通常可能是这样的算法

    protected  override object GetLatestCollectionValue(string key) {
        if (key != _key_AgeHistoryCollection)
            return null;
        return GetImmutableCollectionValue(AgeHistory);
    }  
    
    protected static object GetImmutableCollectionValue<T>(ICollection<T> c) {
        var hash = c.Count.GetHashCode();
        unchecked {
            hash = c.Aggregate(hash, (current, i) => current += i.GetHashCode());
        }
        return hash;        
    }
    
  4. 这实际上似乎有效,但......复杂!有没有人看到更简单和/或更有效的方法来跟踪收集状态?

    问题的要点,实际上是可以回答的,GetImmutableCollectionValue方法看起来像一个很好的通用算法来获取集合的不可变值吗?

    干杯

    解决方案

    冒着进一步犯罪的风险“这个问题无法回答警察”......这是一个解决方案。

    是的,我认为通用算法很有用,我将其添加为扩展名。我添加了另一个扩展来加速和简化基于具有DisplayName属性的域超类的集合的过程:

        public static int GetVmCollectionHashCode<T>(this ICollection<T> c) where T : ViewModelBase
        {
            if (c == null)
                return 0;
    
            var hash = c.Count.GetHashCode();
            unchecked
            {
                hash = c.
                    Aggregate(hash, (current, i) => current += i.DisplayName.GetHashCode());
            }
            return hash;
        }
    

    但我真正的洞察力是,不是使我的EditingNotifier超类与集合处理混乱和复杂化,而是将集合本身​​封装到一个类中,并在修改后通知它包含类,所以:

    /// <summary>
    /// A collection with the ability to broadcast <see cref="Messenger"/> notifications
    /// when the collection is altered. Subscribers that need to know if they are dirty 
    /// because this collection was modified can use the information to calculate a stateful
    /// proerty using <see cref="EditingHelpers.GetVmCollectionHashCode{T}"/>,
    /// </summary>
    public class PcmDetailCollectionVm : ObservableCollection<PcmDetailVm>
    {
        #region Creation
    
        public PcmDetailCollectionVm(IEnumerable<PcmDetailVm> pcms) : base(pcms) {
    
            // set INPC for each vm
            foreach (var vm in this)
                vm.PropertyChanged += OnDetailVmChanged;
        }
    
        #endregion
    
        #region Collection Modification Handlers
    
        public void AddDetailVm(PcmDetailVm item) {
            Add(item);
            item.PropertyChanged += OnDetailVmChanged;
            Messenger.GetInstance.Notify(MessengerMessages.PcmCollectionChanged, this);
        }
    
        public void RemoveDetailVm(PcmDetailVm item) {
            Remove(item);
            Messenger.GetInstance.Notify(MessengerMessages.PcmCollectionChanged, this);
        }
    
        private readonly string _propName_DisplayName = ExprHelper.GetPropertyName<ViewModelBase>(vm => vm.DisplayName);
    
        private void OnDetailVmChanged(object sender, PropertyChangedEventArgs e) {
            if (e.PropertyName == _propName_DisplayName)
                Messenger.GetInstance.Notify(MessengerMessages.PcmCollectionChanged, this);
        }
    
        #endregion
    }
    

    现在,包含类只需要一个在收到通知时更新的简单属性,并且状态跟踪正常进行。

    ///包含类

    Messenger.GetInstance.Register(MessengerMessages.PcmCollectionChanged, (Action<PcmDetailCollectionVm>)(OnPcmCollectionChanged));
    
    
        public int DetailVmsHashCode
        {
            get { return _detailVmsHashCode; } 
            protected set {
                if (_detailVmsHashCode == value)
                    return;
                _detailVmsHashCode = value;
                Notify(() => DetailVmsHashCode);
            }
        }
        private int _detailVmsHashCode;
    
        private void OnPcmCollectionChanged(PcmDetailCollectionVm obj)
        {
            if(!ReferenceEquals(obj, DetailVms))
                return;
            DetailVmsHashCode = DetailVms.GetVmCollectionHashCode();
        }
    

    生活又好了......

0 个答案:

没有答案