diff --git a/src/DynamicMvvm.Tests/Integration/DeactivationIntegrationTests.cs b/src/DynamicMvvm.Tests/Integration/DeactivationIntegrationTests.cs index 8a2b9b6..09f6c2f 100644 --- a/src/DynamicMvvm.Tests/Integration/DeactivationIntegrationTests.cs +++ b/src/DynamicMvvm.Tests/Integration/DeactivationIntegrationTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text; using System.Threading.Tasks; @@ -61,11 +62,41 @@ bool ReadCountChanged() } } + [Fact] + public void GetFromDeactivatableObservable_WithFuncOverload_doesnt_build_observable_more_than_once() + { + var sut = new TestVM2(); + + // InvocationCount should be 0 because the observable is not built until the first time it is accessed. + sut.InvocationCount.Should().Be(0); + sut.Count.Should().Be(0); + + // InvocationCount should be 1 because the observable is built the first time it is accessed. + sut.InvocationCount.Should().Be(1); + sut.Count.Should().Be(0); + + // InvocationCount should still be 1 because the property is cached. + sut.InvocationCount.Should().Be(1); + } + public class TestVM : DeactivatableViewModelBase { public ReplaySubject CountSubject = new ReplaySubject(bufferSize: 1); public int Count => this.GetFromDeactivatableObservable(CountSubject, initialValue: 0); } + + public class TestVM2 : DeactivatableViewModelBase + { + public int InvocationCount { get; private set; } + + public int Count => this.GetFromDeactivatableObservable(GetObservable, initialValue: 0); + + private IObservable GetObservable() + { + ++InvocationCount; + return Observable.Never(); + } + } } } diff --git a/src/DynamicMvvm/Deactivation/IDeactivatableViewModel.Extensions.cs b/src/DynamicMvvm/Deactivation/IDeactivatableViewModel.Extensions.cs index db2976f..152714b 100644 --- a/src/DynamicMvvm/Deactivation/IDeactivatableViewModel.Extensions.cs +++ b/src/DynamicMvvm/Deactivation/IDeactivatableViewModel.Extensions.cs @@ -25,5 +25,21 @@ public static T GetFromDeactivatableObservable(this IDeactivatableViewModel v { return viewModel.Get(viewModel.GetOrCreateDynamicProperty(name, n => new DeactivatableDynamicPropertyFromObservable(name, source, viewModel, initialValue))); } + + /// + /// Gets or creates a attached to this .
+ /// The underlying implements so the observation can be deactivated. + /// This overload uses a to avoid evaluating the observable sequence more than once (which can avoid memory allocations). + ///
+ /// The property type. + /// The owning the property. + /// The provider of the observable of values that feeds the property. + /// The property's initial value. + /// The property's name. + /// The property's value. + public static T GetFromDeactivatableObservable(this IDeactivatableViewModel viewModel, Func> sourceProvider, T initialValue = default, [CallerMemberName] string name = null) + { + return viewModel.Get(viewModel.GetOrCreateDynamicProperty(name, n => new DeactivatableDynamicPropertyFromObservable(name, sourceProvider(), viewModel, initialValue))); + } } }