Skip to content

Commit

Permalink
feature: add AsValue Extensions (#59)
Browse files Browse the repository at this point in the history
Co-authored-by: Glenn [email protected]
  • Loading branch information
RLittlesII authored and glennawatson committed Jul 17, 2022
1 parent 9a8a423 commit e1c0746
Show file tree
Hide file tree
Showing 42 changed files with 2,328 additions and 1,328 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.Serialization;
using DynamicData.Binding;
using ReactiveUI;
Expand Down Expand Up @@ -28,10 +29,19 @@ public class DummyReactiveObject : ReactiveObject
[IgnoreDataMember]
private string? _usesExprRaiseSet;

private ObservableAsPropertyHelper<string> _observableProperty;

/// <summary>
/// Initializes a new instance of the <see cref="TestFixture"/> class.
/// </summary>
public DummyReactiveObject() => TestCollection = new ObservableCollectionExtended<int>();
public DummyReactiveObject()
{
TestCollection = new ObservableCollectionExtended<int>();
_observableProperty =
this.WhenAnyValue(x => x.IsOnlyOneWord)
.Select(x => x + "Changed")
.ToProperty(this, nameof(ObservableProperty));
}

/// <summary>
/// Gets or sets the is not null string.
Expand Down Expand Up @@ -107,5 +117,10 @@ public string? UsesExprRaiseSet
get => _usesExprRaiseSet;
set => this.RaiseAndSetIfChanged(ref _usesExprRaiseSet, value);
}

/// <summary>
/// Gets the observable property.
/// </summary>
public string ObservableProperty => _observableProperty.Value;
}
}
19 changes: 18 additions & 1 deletion src/ReactiveMarbles.Mvvm.Benchmarks/Memory/DummyRxObject.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Runtime.Serialization;
using DynamicData.Binding;
using ReactiveMarbles.PropertyChanged;

namespace ReactiveMarbles.Mvvm.Benchmarks.Memory
{
Expand All @@ -27,10 +29,20 @@ public class DummyRxObject : RxObject
[IgnoreDataMember]
private string? _usesExprRaiseSet;

private ValueBinder<string> _observableProperty;

/// <summary>
/// Initializes a new instance of the <see cref="TestFixture"/> class.
/// </summary>
public DummyRxObject() => TestCollection = new ObservableCollectionExtended<int>();
public DummyRxObject()
{
TestCollection = new ObservableCollectionExtended<int>();

_observableProperty =
this.WhenChanged(x => x.IsOnlyOneWord)
.Select(x => x + "Changed")
.AsValue(onChanged: x => RaisePropertyChanged(nameof(ObservableProperty)));
}

/// <summary>
/// Gets or sets the is not null string.
Expand Down Expand Up @@ -106,5 +118,10 @@ public string? UsesExprRaiseSet
get => _usesExprRaiseSet;
set => RaiseAndSetIfChanged(ref _usesExprRaiseSet, value);
}

/// <summary>
/// Gets the observable property.
/// </summary>
public string ObservableProperty => _observableProperty.Value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using ReactiveMarbles.Mvvm.Benchmarks.Memory;
using ReactiveMarbles.PropertyChanged;
using ReactiveUI;

namespace ReactiveMarbles.Mvvm.Benchmarks.Performance
{
[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[MemoryDiagnoser]
[MarkdownExporterAttribute.GitHub]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
public class AsValuePerformanceBenchmark
{
[Benchmark(Baseline = true)]
[BenchmarkCategory("Performance")]
public void AsValueBenchmark()
{
var thing = new DummyRxObject();
var sut = thing.WhenChanged(x => x.NotSerialized, x => x.IsOnlyOneWord, (not, one) => not + one).AsValue(onChanged: _ => { });
}

[Benchmark]
[BenchmarkCategory("Performance")]
public void AsValueWhenWordChangedBenchmark()
{
var thing = new DummyRxObject();
thing.IsOnlyOneWord = "Two Words";
}

[Benchmark]
[BenchmarkCategory("Performance")]
public void ToPropertyBenchmark()
{
var thing = new DummyReactiveObject();
var sut = thing.WhenChanged(x => x.NotSerialized, x => x.IsOnlyOneWord, (not, one) => not + one).ToProperty(thing, x => x.ObservableProperty);
}

[Benchmark]
[BenchmarkCategory("Performance")]
public void ToPropertyWhenWordChangedBenchmark()
{
var thing = new DummyReactiveObject();
thing.IsOnlyOneWord = "Two Words";
}
}
}
16 changes: 15 additions & 1 deletion src/ReactiveMarbles.Mvvm.Benchmarks/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,18 @@
| ReactiveObjectCreation | 100 | 15,796.5 ns | 314.80 ns | 398.12 ns | 9.0027 | 1.7090 | - | 75,344 B |
| ReactiveObjectWithChange | 100 | 132,292.6 ns | 2,553.90 ns | 3,580.21 ns | 24.1699 | 1.9531 | 0.7324 | 203,267 B |
| ReactiveObjectCreation | 4000 | 1,530,059.8 ns | 26,466.29 ns | 24,756.59 ns | 359.3750 | 179.6875 | - | 3,008,145 B |
| ReactiveObjectWithChange | 4000 | 11,304,993.4 ns | 146,890.54 ns | 137,401.50 ns | 953.1250 | 468.7500 | 218.7500 | 8,091,139 B |
| ReactiveObjectWithChange | 4000 | 11,304,993.4 ns | 146,890.54 ns | 137,401.50 ns | 953.1250 | 468.7500 | 218.7500 | 8,091,139 B |

## AsValue

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated |
|----------------------------------- |----------:|---------:|---------:|------:|--------:|-------:|-------:|----------:|
| AsValueBenchmark | 48.78 μs | 0.257 μs | 0.241 μs | 1.00 | 0.00 | 1.8311 | 0.3662 | 15 KB |
| AsValueWhenWordChangedBenchmark | 20.54 μs | 0.096 μs | 0.090 μs | 0.42 | 0.00 | 0.7935 | 0.1221 | 7 KB |


## ReactiveUI ToProperty
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated |
|----------------------------------- |----------:|---------:|---------:|------:|--------:|-------:|-------:|----------:|
| ToPropertyBenchmark | 105.46 μs | 0.971 μs | 0.860 μs | 2.16 | 0.02 | 2.9297 | 0.4883 | 25 KB |
| ToPropertyWhenWordChangedBenchmark | 54.09 μs | 1.078 μs | 1.580 μs | 1.12 | 0.03 | 1.5869 | 0.2441 | 13 KB |
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="ReactiveMarbles.PropertyChanged" />
<PackageReference Include="ReactiveUI" />
</ItemGroup>

Expand Down
219 changes: 219 additions & 0 deletions src/ReactiveMarbles.Mvvm.Tests/AsLazyValueExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright (c) 2019-2021 ReactiveUI Association Incorporated. All rights reserved.
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using System.Reactive.Concurrency;
using System.Reactive.Linq;
using FluentAssertions;
using Microsoft.Reactive.Testing;
using ReactiveMarbles.PropertyChanged;
using Xunit;

namespace ReactiveMarbles.Mvvm.Tests;

/// <summary>
/// Tests for the <see cref="AsValueExtensions"/>.
/// </summary>
public class AsLazyValueExtensionsTests
{
/// <summary>
/// Tests the default value.
/// </summary>
[Fact]
public void GivenNoChanges_WhenAsValue_ThenFullNameIsEmpty()
{
// Given, When
var sut = new AsLazyValueTestObject();

// Then
sut.FullName.Should().BeNullOrEmpty();
}

/// <summary>
/// Tests the property is produced from the sequence.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenSequence_WhenAsValue_ThenPropertyProduced(string first, string last)
{
// Given
var sut = new AsLazyValueTestObject();

// When
sut.FirstName = first;
sut.LastName = last;

// Then
sut.FullName.Should().Be(first + last);
}

/// <summary>
/// Tests the property is produced from the sequence.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenFirstName_WhenAsValue_ThenPropertyProduced(string first, string last)
{
// Given
var sut = new AsLazyValueTestObject();

// When
sut.FirstName = first;

// Then
sut.FullName.Should().Be(first);
}

/// <summary>
/// Tests the property is produced from the sequence.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenLastName_WhenAsValue_ThenPropertyProduced(string first, string last)
{
// Given
var sut = new AsLazyValueTestObject();

// When
sut.LastName = last;

// Then
sut.FullName.Should().Be(last);
}

/// <summary>
/// Tests the value of the value binder.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenOnChanged_WhenAsValue_ThenValueCorrect(string first, string last)
{
// Given
var testObject = new AsLazyValueTestObject();
var sut =
testObject
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
.AsLazyValue(onChanged: _ => { });

// When
testObject.FirstName = first;
testObject.LastName = last;

// Then
sut.Value.Should().Be(first + last);
}

/// <summary>
/// Tests the value of the value binder.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenOnChangedAndInitialValue_WhenAsValue_ThenValueCorrect(string first, string last)
{
// Given
var testObject = new AsLazyValueTestObject();
var sut =
testObject
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
.AsLazyValue(onChanged: _ => { }, initialValue: () => string.Empty);

// When
testObject.FirstName = first;
testObject.LastName = last;

// Then
sut.Value.Should().Be(first + last);
}

/// <summary>
/// Tests the value of the value binder.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenOnChangedAndOnChangingAndInitialValue_WhenAsValue_ThenValueCorrect(string first, string last)
{
// Given
var testObject = new AsLazyValueTestObject();
var sut =
testObject
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
.AsLazyValue(onChanging: _ => { }, onChanged: _ => { }, initialValue: () => string.Empty);

// When
testObject.FirstName = first;
testObject.LastName = last;

// Then
sut.Value.Should().Be(first + last);
}

/// <summary>
/// Tests the value of the value binder.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenOnChangedAndOnChangingAndSchedulerAndInitialValue_WhenAsValue_ThenValueCorrect(string first, string last)
{
// Given
const string start = "start";
var testScheduler = new TestScheduler();
var testObject = new AsLazyValueTestObject();
var sut =
testObject
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
.AsLazyValue(onChanged: _ => { }, testScheduler, () => start);

sut.Value.Should().Be(start);

// When
testObject.FirstName = first;
testObject.LastName = last;
testScheduler.Start();

// Then
sut.Value.Should().Be(first + last);
}

/// <summary>
/// Tests the value of the value binder.
/// </summary>
/// <param name="first">The first name.</param>
/// <param name="last">The last name.</param>
[Theory]
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
public void GivenAllParameters_WhenAsValue_ThenValueCorrect(string first, string last)
{
// Given
const string start = "start";
var testScheduler = new TestScheduler();
var testObject = new AsLazyValueTestObject();
var sut =
testObject
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
.AsLazyValue(_ => { }, _ => { }, testScheduler, () => start);

sut.Value.Should().Be(start);

// When
testObject.FirstName = first;
testObject.LastName = last;
testScheduler.Start();

// Then
sut.Value.Should().Be(first + last);
}
}
Loading

0 comments on commit e1c0746

Please sign in to comment.