私のプレゼンテーション アーキテクチャの一部として、IEditableObject を実装する基本クラスがあります。そのため、BeginEdit() が状態のスナップショットを取得すると、すべての書き込み可能な非コレクション プロパティのリフレクションを介して Dictionary が作成されます。
var propertyInfos = GetType().GetWritableNonCollectionPropertyInfos();
var dic = propertyInfos
.ToDictionary(pi => pi.Name, pi => pi.GetValue(this, null));
このディクショナリは IsDirty の追跡と CancelEdit() でのロールバックの基礎です。
デフォルトの動作でコレクションが除外される理由は、コレクションは通常必要ではなく、列挙にコストがかかる可能性があり、一般的に面倒だからです。
この質問のポイントまで、コレクションの状態が必要なユースケースがあります。これはカスタム オブジェクトの HashSet であり、一度に 5 つを超えることはめったにありません。上記のコードからわかるように、コレクションの値はコレクション自体への参照になります。コレクションが変更されると変更されるため、実際に汚れているかどうかはわかりません。
したがって、不変であり、コレクションの値が変更されたときにそれに匹敵するコレクションの値を保存する必要があります。これが私が思いついたものの一般的なアイデアです:
- 保護された IncludeCollections フラグを提供する
フラグが 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();
}
人生は再び良いです...