From 729810028fd435d8a1904955e7d44b19969f4fcb Mon Sep 17 00:00:00 2001 From: Jean-Philippe Levesque Date: Wed, 10 Jan 2024 10:33:40 -0500 Subject: [PATCH] perf: Improve performance when reading dynamic properties. --- .../IViewModel.Extensions.Properties.cs | 142 +++++++++++++++++- 1 file changed, 136 insertions(+), 6 deletions(-) diff --git a/src/DynamicMvvm.Abstractions/ViewModel/IViewModel.Extensions.Properties.cs b/src/DynamicMvvm.Abstractions/ViewModel/IViewModel.Extensions.Properties.cs index b6e48c8..4afb822 100644 --- a/src/DynamicMvvm.Abstractions/ViewModel/IViewModel.Extensions.Properties.cs +++ b/src/DynamicMvvm.Abstractions/ViewModel/IViewModel.Extensions.Properties.cs @@ -5,8 +5,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; namespace Chinook.DynamicMvvm { @@ -65,7 +63,40 @@ public static T Get( this IViewModel viewModel, T initialValue = default, [CallerMemberName] string name = null - ) => viewModel.Get(viewModel.GetOrCreateDynamicProperty(name, n => viewModel.GetDynamicPropertyFactory().Create(n, initialValue, viewModel))); + ) + { + // We don't use GetOrCreateDynamicProperty internally to avoid the performance costs of the lambda and closure. + if (viewModel.IsDisposed) + { + return default(T); + } + + if (viewModel.TryGetDisposable(name, out var property)) + { + return viewModel.Get(property); + } + else + { + property = AddDynamicPropertyFromValue(viewModel, initialValue, name); + + return (T)property.Value; + } + } + + private static IDynamicProperty AddDynamicPropertyFromValue(IViewModel viewModel, T initialValue, string name) + { + var property = viewModel.GetDynamicPropertyFactory().Create(name, initialValue, viewModel); + property.ValueChanged += OnDynamicPropertyChanged; + + viewModel.AddDisposable(name, property); + + return property; + + void OnDynamicPropertyChanged(IDynamicProperty dynamicProperty) + { + viewModel.RaisePropertyChanged(dynamicProperty.Name); + } + } /// /// Gets or creates a attached to this . @@ -79,7 +110,40 @@ public static T Get( this IViewModel viewModel, Func initialValue, [CallerMemberName] string name = null - ) => viewModel.Get(viewModel.GetOrCreateDynamicProperty(name, n => viewModel.GetDynamicPropertyFactory().Create(n, initialValue(), viewModel))); + ) + { + // We don't use GetOrCreateDynamicProperty internally to avoid the performance costs of the lambda and closure. + if (viewModel.IsDisposed) + { + return default(T); + } + + if (viewModel.TryGetDisposable(name, out var property)) + { + return viewModel.Get(property); + } + else + { + property = AddDynamicPropertyFromValue(viewModel, initialValue, name); + + return (T)property.Value; + } + } + + private static IDynamicProperty AddDynamicPropertyFromValue(IViewModel viewModel, Func initialValue, string name) + { + var property = viewModel.GetDynamicPropertyFactory().Create(name, initialValue(), viewModel); + property.ValueChanged += OnDynamicPropertyChanged; + + viewModel.AddDisposable(name, property); + + return property; + + void OnDynamicPropertyChanged(IDynamicProperty dynamicProperty) + { + viewModel.RaisePropertyChanged(dynamicProperty.Name); + } + } /// /// Gets or creates a attached to this . @@ -95,7 +159,40 @@ public static T GetFromTask( Func> source, T initialValue = default, [CallerMemberName] string name = null - ) => viewModel.Get(viewModel.GetOrCreateDynamicProperty(name, n => viewModel.GetDynamicPropertyFactory().CreateFromTask(n, source, initialValue, viewModel))); + ) + { + // We don't use GetOrCreateDynamicProperty internally to avoid the performance costs of the lambda and closure. + if (viewModel.IsDisposed) + { + return default(T); + } + + if (viewModel.TryGetDisposable(name, out var property)) + { + return viewModel.Get(property); + } + else + { + property = AddDynamicPropertyTask(viewModel, source, initialValue, name); + + return (T)property.Value; + } + } + + private static IDynamicProperty AddDynamicPropertyTask(IViewModel viewModel, Func> source, T initialValue, string name) + { + var property = viewModel.GetDynamicPropertyFactory().CreateFromTask(name, source, initialValue, viewModel); + property.ValueChanged += OnDynamicPropertyChanged; + + viewModel.AddDisposable(name, property); + + return property; + + void OnDynamicPropertyChanged(IDynamicProperty dynamicProperty) + { + viewModel.RaisePropertyChanged(dynamicProperty.Name); + } + } /// /// Gets or creates a attached to this . @@ -111,7 +208,40 @@ public static T GetFromObservable( IObservable source, T initialValue = default, [CallerMemberName] string name = null - ) => viewModel.Get(viewModel.GetOrCreateDynamicProperty(name, n => viewModel.GetDynamicPropertyFactory().CreateFromObservable(n, source, initialValue, viewModel))); + ) + { + // We don't use GetOrCreateDynamicProperty internally to avoid the performance costs of the lambda and closure. + if (viewModel.IsDisposed) + { + return default(T); + } + + if (viewModel.TryGetDisposable(name, out var property)) + { + return viewModel.Get(property); + } + else + { + property = AddDynamicPropertyFromObservable(viewModel, source, initialValue, name); + + return (T)property.Value; + } + } + + private static IDynamicProperty AddDynamicPropertyFromObservable(IViewModel viewModel, IObservable source, T initialValue, string name) + { + var property = viewModel.GetDynamicPropertyFactory().CreateFromObservable(name, source, initialValue, viewModel); + property.ValueChanged += OnDynamicPropertyChanged; + + viewModel.AddDisposable(name, property); + + return property; + + void OnDynamicPropertyChanged(IDynamicProperty dynamicProperty) + { + viewModel.RaisePropertyChanged(dynamicProperty.Name); + } + } /// /// Sets the value of a property.