3

作为我的演示架构的一部分,我有一个实现 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. 当标志为真时,给子类一个模板方法来返回仅集合属性的附加键值对

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

这实际上似乎有效,但是......它很复杂!有没有人看到更简单和/或更有效的方法来跟踪收集状态?

这个问题的主旨确实是可以回答的,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();
    }

生活又好起来了……

4

0 回答 0