作为我的演示架构的一部分,我有一个实现 IEditableObject 的基类,因此当 BeginEdit() 拍摄状态快照时,将通过反射为所有可写的非集合属性创建一个 Dictionary。
var propertyInfos = GetType().GetWritableNonCollectionPropertyInfos();
var dic = propertyInfos
.ToDictionary(pi => pi.Name, pi => pi.GetValue(this, null));
该字典是 IsDirty 跟踪和回滚 CancelEdit() 的基础。
默认行为排除集合的原因是因为它们通常不是必需的,枚举起来可能很昂贵,而且通常很痛苦。
关于这个问题,我有一个需要收集状态的用例。它是自定义对象的 HashSet,一次很少有超过 5 个。从上面的代码可以看出,集合的值最终将成为对集合本身的引用;这将随着集合的变化而变化,因此您无法判断它是否真的很脏。
因此,我需要为集合存储一些值,这些值既不可变,又可与集合的值在更改时进行比较。这是我想出的总体思路:
- 提供受保护的 IncludeCollections 标志
当标志为真时,给子类一个模板方法来返回仅集合属性的附加键值对
if(IncludeCollections) { var collectionOnlyDictionary = GetCollectionOnlyDictionary(); foreach (var kvp in collectionOnlyDictionary) { dic.Add(GetCollectionKey(kvp.Key), kvp.Value); } }
提供另一个模板方法来获取集合的一些不可变值。这应该允许变化,但通常可能是这样的算法
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();
}
生活又好起来了……