Skip to content

Commit e1c0746

Browse files
RLittlesIIglennawatson
authored andcommitted
feature: add AsValue Extensions (#59)
Co-authored-by: Glenn [email protected]
1 parent 9a8a423 commit e1c0746

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2328
-1328
lines changed

src/ReactiveMarbles.Mvvm.Benchmarks/Memory/DummyReactiveObject.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using System.Reactive.Linq;
34
using System.Runtime.Serialization;
45
using DynamicData.Binding;
56
using ReactiveUI;
@@ -28,10 +29,19 @@ public class DummyReactiveObject : ReactiveObject
2829
[IgnoreDataMember]
2930
private string? _usesExprRaiseSet;
3031

32+
private ObservableAsPropertyHelper<string> _observableProperty;
33+
3134
/// <summary>
3235
/// Initializes a new instance of the <see cref="TestFixture"/> class.
3336
/// </summary>
34-
public DummyReactiveObject() => TestCollection = new ObservableCollectionExtended<int>();
37+
public DummyReactiveObject()
38+
{
39+
TestCollection = new ObservableCollectionExtended<int>();
40+
_observableProperty =
41+
this.WhenAnyValue(x => x.IsOnlyOneWord)
42+
.Select(x => x + "Changed")
43+
.ToProperty(this, nameof(ObservableProperty));
44+
}
3545

3646
/// <summary>
3747
/// Gets or sets the is not null string.
@@ -107,5 +117,10 @@ public string? UsesExprRaiseSet
107117
get => _usesExprRaiseSet;
108118
set => this.RaiseAndSetIfChanged(ref _usesExprRaiseSet, value);
109119
}
120+
121+
/// <summary>
122+
/// Gets the observable property.
123+
/// </summary>
124+
public string ObservableProperty => _observableProperty.Value;
110125
}
111126
}

src/ReactiveMarbles.Mvvm.Benchmarks/Memory/DummyRxObject.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using System.Reactive.Linq;
34
using System.Runtime.Serialization;
45
using DynamicData.Binding;
6+
using ReactiveMarbles.PropertyChanged;
57

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

32+
private ValueBinder<string> _observableProperty;
33+
3034
/// <summary>
3135
/// Initializes a new instance of the <see cref="TestFixture"/> class.
3236
/// </summary>
33-
public DummyRxObject() => TestCollection = new ObservableCollectionExtended<int>();
37+
public DummyRxObject()
38+
{
39+
TestCollection = new ObservableCollectionExtended<int>();
40+
41+
_observableProperty =
42+
this.WhenChanged(x => x.IsOnlyOneWord)
43+
.Select(x => x + "Changed")
44+
.AsValue(onChanged: x => RaisePropertyChanged(nameof(ObservableProperty)));
45+
}
3446

3547
/// <summary>
3648
/// Gets or sets the is not null string.
@@ -106,5 +118,10 @@ public string? UsesExprRaiseSet
106118
get => _usesExprRaiseSet;
107119
set => RaiseAndSetIfChanged(ref _usesExprRaiseSet, value);
108120
}
121+
122+
/// <summary>
123+
/// Gets the observable property.
124+
/// </summary>
125+
public string ObservableProperty => _observableProperty.Value;
109126
}
110127
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Configs;
3+
using BenchmarkDotNet.Jobs;
4+
using ReactiveMarbles.Mvvm.Benchmarks.Memory;
5+
using ReactiveMarbles.PropertyChanged;
6+
using ReactiveUI;
7+
8+
namespace ReactiveMarbles.Mvvm.Benchmarks.Performance
9+
{
10+
[SimpleJob(RuntimeMoniker.NetCoreApp31)]
11+
[MemoryDiagnoser]
12+
[MarkdownExporterAttribute.GitHub]
13+
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
14+
public class AsValuePerformanceBenchmark
15+
{
16+
[Benchmark(Baseline = true)]
17+
[BenchmarkCategory("Performance")]
18+
public void AsValueBenchmark()
19+
{
20+
var thing = new DummyRxObject();
21+
var sut = thing.WhenChanged(x => x.NotSerialized, x => x.IsOnlyOneWord, (not, one) => not + one).AsValue(onChanged: _ => { });
22+
}
23+
24+
[Benchmark]
25+
[BenchmarkCategory("Performance")]
26+
public void AsValueWhenWordChangedBenchmark()
27+
{
28+
var thing = new DummyRxObject();
29+
thing.IsOnlyOneWord = "Two Words";
30+
}
31+
32+
[Benchmark]
33+
[BenchmarkCategory("Performance")]
34+
public void ToPropertyBenchmark()
35+
{
36+
var thing = new DummyReactiveObject();
37+
var sut = thing.WhenChanged(x => x.NotSerialized, x => x.IsOnlyOneWord, (not, one) => not + one).ToProperty(thing, x => x.ObservableProperty);
38+
}
39+
40+
[Benchmark]
41+
[BenchmarkCategory("Performance")]
42+
public void ToPropertyWhenWordChangedBenchmark()
43+
{
44+
var thing = new DummyReactiveObject();
45+
thing.IsOnlyOneWord = "Two Words";
46+
}
47+
}
48+
}

src/ReactiveMarbles.Mvvm.Benchmarks/README.MD

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,18 @@
1919
| ReactiveObjectCreation | 100 | 15,796.5 ns | 314.80 ns | 398.12 ns | 9.0027 | 1.7090 | - | 75,344 B |
2020
| ReactiveObjectWithChange | 100 | 132,292.6 ns | 2,553.90 ns | 3,580.21 ns | 24.1699 | 1.9531 | 0.7324 | 203,267 B |
2121
| ReactiveObjectCreation | 4000 | 1,530,059.8 ns | 26,466.29 ns | 24,756.59 ns | 359.3750 | 179.6875 | - | 3,008,145 B |
22-
| 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 |
22+
| 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 |
23+
24+
## AsValue
25+
26+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated |
27+
|----------------------------------- |----------:|---------:|---------:|------:|--------:|-------:|-------:|----------:|
28+
| AsValueBenchmark | 48.78 μs | 0.257 μs | 0.241 μs | 1.00 | 0.00 | 1.8311 | 0.3662 | 15 KB |
29+
| AsValueWhenWordChangedBenchmark | 20.54 μs | 0.096 μs | 0.090 μs | 0.42 | 0.00 | 0.7935 | 0.1221 | 7 KB |
30+
31+
32+
## ReactiveUI ToProperty
33+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated |
34+
|----------------------------------- |----------:|---------:|---------:|------:|--------:|-------:|-------:|----------:|
35+
| ToPropertyBenchmark | 105.46 μs | 0.971 μs | 0.860 μs | 2.16 | 0.02 | 2.9297 | 0.4883 | 25 KB |
36+
| ToPropertyWhenWordChangedBenchmark | 54.09 μs | 1.078 μs | 1.580 μs | 1.12 | 0.03 | 1.5869 | 0.2441 | 13 KB |

src/ReactiveMarbles.Mvvm.Benchmarks/ReactiveMarbles.Mvvm.Benchmarks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="BenchmarkDotNet" />
12+
<PackageReference Include="ReactiveMarbles.PropertyChanged" />
1213
<PackageReference Include="ReactiveUI" />
1314
</ItemGroup>
1415

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright (c) 2019-2021 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using System.Reactive.Concurrency;
6+
using System.Reactive.Linq;
7+
using FluentAssertions;
8+
using Microsoft.Reactive.Testing;
9+
using ReactiveMarbles.PropertyChanged;
10+
using Xunit;
11+
12+
namespace ReactiveMarbles.Mvvm.Tests;
13+
14+
/// <summary>
15+
/// Tests for the <see cref="AsValueExtensions"/>.
16+
/// </summary>
17+
public class AsLazyValueExtensionsTests
18+
{
19+
/// <summary>
20+
/// Tests the default value.
21+
/// </summary>
22+
[Fact]
23+
public void GivenNoChanges_WhenAsValue_ThenFullNameIsEmpty()
24+
{
25+
// Given, When
26+
var sut = new AsLazyValueTestObject();
27+
28+
// Then
29+
sut.FullName.Should().BeNullOrEmpty();
30+
}
31+
32+
/// <summary>
33+
/// Tests the property is produced from the sequence.
34+
/// </summary>
35+
/// <param name="first">The first name.</param>
36+
/// <param name="last">The last name.</param>
37+
[Theory]
38+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
39+
public void GivenSequence_WhenAsValue_ThenPropertyProduced(string first, string last)
40+
{
41+
// Given
42+
var sut = new AsLazyValueTestObject();
43+
44+
// When
45+
sut.FirstName = first;
46+
sut.LastName = last;
47+
48+
// Then
49+
sut.FullName.Should().Be(first + last);
50+
}
51+
52+
/// <summary>
53+
/// Tests the property is produced from the sequence.
54+
/// </summary>
55+
/// <param name="first">The first name.</param>
56+
/// <param name="last">The last name.</param>
57+
[Theory]
58+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
59+
public void GivenFirstName_WhenAsValue_ThenPropertyProduced(string first, string last)
60+
{
61+
// Given
62+
var sut = new AsLazyValueTestObject();
63+
64+
// When
65+
sut.FirstName = first;
66+
67+
// Then
68+
sut.FullName.Should().Be(first);
69+
}
70+
71+
/// <summary>
72+
/// Tests the property is produced from the sequence.
73+
/// </summary>
74+
/// <param name="first">The first name.</param>
75+
/// <param name="last">The last name.</param>
76+
[Theory]
77+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
78+
public void GivenLastName_WhenAsValue_ThenPropertyProduced(string first, string last)
79+
{
80+
// Given
81+
var sut = new AsLazyValueTestObject();
82+
83+
// When
84+
sut.LastName = last;
85+
86+
// Then
87+
sut.FullName.Should().Be(last);
88+
}
89+
90+
/// <summary>
91+
/// Tests the value of the value binder.
92+
/// </summary>
93+
/// <param name="first">The first name.</param>
94+
/// <param name="last">The last name.</param>
95+
[Theory]
96+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
97+
public void GivenOnChanged_WhenAsValue_ThenValueCorrect(string first, string last)
98+
{
99+
// Given
100+
var testObject = new AsLazyValueTestObject();
101+
var sut =
102+
testObject
103+
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
104+
.AsLazyValue(onChanged: _ => { });
105+
106+
// When
107+
testObject.FirstName = first;
108+
testObject.LastName = last;
109+
110+
// Then
111+
sut.Value.Should().Be(first + last);
112+
}
113+
114+
/// <summary>
115+
/// Tests the value of the value binder.
116+
/// </summary>
117+
/// <param name="first">The first name.</param>
118+
/// <param name="last">The last name.</param>
119+
[Theory]
120+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
121+
public void GivenOnChangedAndInitialValue_WhenAsValue_ThenValueCorrect(string first, string last)
122+
{
123+
// Given
124+
var testObject = new AsLazyValueTestObject();
125+
var sut =
126+
testObject
127+
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
128+
.AsLazyValue(onChanged: _ => { }, initialValue: () => string.Empty);
129+
130+
// When
131+
testObject.FirstName = first;
132+
testObject.LastName = last;
133+
134+
// Then
135+
sut.Value.Should().Be(first + last);
136+
}
137+
138+
/// <summary>
139+
/// Tests the value of the value binder.
140+
/// </summary>
141+
/// <param name="first">The first name.</param>
142+
/// <param name="last">The last name.</param>
143+
[Theory]
144+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
145+
public void GivenOnChangedAndOnChangingAndInitialValue_WhenAsValue_ThenValueCorrect(string first, string last)
146+
{
147+
// Given
148+
var testObject = new AsLazyValueTestObject();
149+
var sut =
150+
testObject
151+
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
152+
.AsLazyValue(onChanging: _ => { }, onChanged: _ => { }, initialValue: () => string.Empty);
153+
154+
// When
155+
testObject.FirstName = first;
156+
testObject.LastName = last;
157+
158+
// Then
159+
sut.Value.Should().Be(first + last);
160+
}
161+
162+
/// <summary>
163+
/// Tests the value of the value binder.
164+
/// </summary>
165+
/// <param name="first">The first name.</param>
166+
/// <param name="last">The last name.</param>
167+
[Theory]
168+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
169+
public void GivenOnChangedAndOnChangingAndSchedulerAndInitialValue_WhenAsValue_ThenValueCorrect(string first, string last)
170+
{
171+
// Given
172+
const string start = "start";
173+
var testScheduler = new TestScheduler();
174+
var testObject = new AsLazyValueTestObject();
175+
var sut =
176+
testObject
177+
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
178+
.AsLazyValue(onChanged: _ => { }, testScheduler, () => start);
179+
180+
sut.Value.Should().Be(start);
181+
182+
// When
183+
testObject.FirstName = first;
184+
testObject.LastName = last;
185+
testScheduler.Start();
186+
187+
// Then
188+
sut.Value.Should().Be(first + last);
189+
}
190+
191+
/// <summary>
192+
/// Tests the value of the value binder.
193+
/// </summary>
194+
/// <param name="first">The first name.</param>
195+
/// <param name="last">The last name.</param>
196+
[Theory]
197+
[MemberData(nameof(AsValueTestData.Data), MemberType=typeof(AsValueTestData))]
198+
public void GivenAllParameters_WhenAsValue_ThenValueCorrect(string first, string last)
199+
{
200+
// Given
201+
const string start = "start";
202+
var testScheduler = new TestScheduler();
203+
var testObject = new AsLazyValueTestObject();
204+
var sut =
205+
testObject
206+
.WhenChanged(x => x.FirstName, x => x.LastName, (firstName, lastName) => firstName + lastName)
207+
.AsLazyValue(_ => { }, _ => { }, testScheduler, () => start);
208+
209+
sut.Value.Should().Be(start);
210+
211+
// When
212+
testObject.FirstName = first;
213+
testObject.LastName = last;
214+
testScheduler.Start();
215+
216+
// Then
217+
sut.Value.Should().Be(first + last);
218+
}
219+
}

0 commit comments

Comments
 (0)