作为我的表示架构的一部分,我有一个实现IEditableObject的基类,所以当BeginEdit()获取状态快照时,会通过反射为所有可写的非集合属性创建一个Dictionary。
var propertyInfos = GetType().GetWritableNonCollectionPropertyInfos();
var dic = propertyInfos
.ToDictionary(pi => pi.Name, pi => pi.GetValue(this, null));
此词典是IsDirty跟踪和回滚CancelEdit()的基础。
默认行为排除集合的原因是因为它们通常不是必需的,枚举可能很昂贵,而且通常只是一种痛苦。
就这个问题而言,我有一个用例,其中集合状态是是必要的。它是自定义对象的HashSet,并且一次很少超过5个。从上面的代码中可以看出,集合的值最终将成为对集合本身的引用;这将随着集合的变化而变化,因此你无法判断它是否真的变脏了。
所以我需要为集合存储一些值,这些值将是不可变的,并且与集合的值相比可以更改。以下是我提出的一般想法:
当标志为true时,为子类提供模板方法以返回仅包含集合属性的其他键值对
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();
}
生活又好了......