実装を追ってみる
ネスト対応のPropertyObservable生成部分がここで
ExpressionTreeからネストしたプロパティ指定をノードに変換するのがここで
ノード内でネスト指定されたプロパティを取得してるのがここ
public object? GetPropertyPathValue()
{
if (Source == null)
{
return null;
}
if (Next != null)
{
return Next.GetPropertyPathValue();
}
return GetPropertyValue();
}
Nextノードがある場合(プロパティがネストして指定された状態)と、Nextが無い場合で呼び分けられており、
GerPropertyValue()
がソースとプロパティ名から実際の値を取得していますが、この中で動的にプロパティのgetterを(キャッシュして)呼び出しています。なるほどすごい。
private object? GetPropertyValue()
{
EnsureDispose();
if (Source == null) return null;
return (_getAccessor ?? (_getAccessor = AccessorCache.LookupGet(Source.GetType(), PropertyName)))
.DynamicInvoke(Source);
}
ObservePropertyのネスト指定が出来て何が嬉しいのか?
C#+Xamlの環境では、ViewModelアイテムにObservableなModelインスタンスを持たせてXamlからバインディング(INotifyPropertyChangedを通じてプロパティ変更をView/Modelに相互に反映する紐づけ動作)することもあるんですが、ViewModelでModelと似たようなObservableなプロパティを重複定義させることなく、Modelオブジェクトを単に公開してしまう形で定義しておいて任意のプレゼンテーション部分(例えばPageViewModelとか)で
// PageVM上で画像レイヤーを定義していたとして
public ObservableCollection<StoryboardImageLayerCategoryViewModel> ImageLayers { get; }
// IsExportEnabledが変わったらプレビュー動画を差し替えたりしたい
ImageLayers.ObserveElementProperty(x => x.ImageLayerCategory.IsExportEnabled)
.Subscribe(x => ...)
.AddTo(_navigationDisposer);
// ViewModelアイテムからModelインスタンスを公開してる
public sealed partial class StoryboardImageLayerCategoryViewModel : ObservableObject
{
public StoryboardImageLayerCategory ImageLayerCategory { get; }
public StoryboardImageLayerCategoryViewModel(
StoryboardImageLayerCategory imageLayerCategory
)
{
ImageLayerCategory = imageLayerCategory;
}
// 他にもModel内のプロパティをクッションしたり、Page表現を補助するような別のコードがあったりするので、ViewModel表現自体は必要。
}
みたいに書けてとても嬉しいわけです。実際便利。
Modelは変更されたら確実に保存したいので常にプロパティ変更時を捕捉しながらローカルDBに更新掛けたりするので、ViewModelだけでなくModelもObservableな実装にする必要があります。
// CommunityToolkit.Mvvm を導入してる前提で
public sealed partial class StoryboardImageLayerCategory : DeferSaveAwareObservableObject
{
public StoryboardImageLayerCategory(
StoryboardImageLayerCategoryEntity layerCategory
, StoryboardSceneRepository repository)
{
LayerCategoryEntity = layerCategory;
_repository = repository;
}
public StoryboardImageLayerCategoryEntity LayerCategoryEntity { get; }
public bool IsExportEnabled
{
get => LayerCategoryEntity.IsExportEnabled;
set => SetProperty(LayerCategoryEntity.IsExportEnabled, value, LayerCategoryEntity, static (m, v) => m.IsExportEnabled = v);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
_repository._imageLayerCategoryCollection.Upsert(LayerCategoryEntity);
base.OnPropertyChanged(e);
}
}