Skip to content

Commit e37e475

Browse files
authored
Add Support for DynamicData (#180)
* Add Support for DynamicData * Update Docs
1 parent 0ab3159 commit e37e475

15 files changed

+457
-19
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ReactiveUI Source Generators automatically generate ReactiveUI objects to stream
2424
- `[IViewFor(nameof(ViewModelName))]`
2525
- `[RoutedControlHost("YourNameSpace.CustomControl")]`
2626
- `[ViewModelControlHost("YourNameSpace.CustomControl")]`
27+
- `[BindableDerivedList]` Generates a derived list from a ReadOnlyObservableCollection backing field
2728

2829
### Compatibility Notes
2930
- For ReactiveUI versions **older than V19.5.31**, all `[ReactiveCommand]` options are supported except for async methods with a `CancellationToken`.
@@ -521,6 +522,18 @@ public partial class MyReactiveControl : UserControl
521522
}
522523
```
523524

525+
### Usage ReadOnlyObservableCollection
526+
527+
```csharp
528+
using ReactiveUI.SourceGenerators;
529+
530+
public partial class MyReactiveClass
531+
{
532+
[BindableDerivedList]
533+
private readonly ReadOnlyObservableCollection<string> _myList;
534+
}
535+
```
536+
524537
## Platform specific Attributes
525538

526539
### WinForms
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//HintName: ReactiveUI.SourceGenerators.BindableDerivedListAttribute.g.cs
2+
// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
3+
// Licensed to the .NET Foundation under one or more agreements.
4+
// The .NET Foundation licenses this file to you under the MIT license.
5+
// See the LICENSE file in the project root for full license information.
6+
7+
using System;
8+
9+
// <auto-generated/>
10+
#pragma warning disable
11+
#nullable enable
12+
namespace ReactiveUI.SourceGenerators;
13+
14+
/// <summary>
15+
/// ReactiveAttribute.
16+
/// </summary>
17+
/// <seealso cref="Attribute" />
18+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
19+
internal sealed class BindableDerivedListAttribute : Attribute;
20+
#nullable restore
21+
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
</ItemGroup>
3636

3737
<ItemGroup>
38+
<Folder Include="DERIVEDLIST\" />
3839
<Folder Include="IVIEWFOR\" />
3940
<Folder Include="OAPH\" />
4041
<Folder Include="REACTIVECMD\" />

src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ namespace ReactiveUI.SourceGenerator.Tests;
3333
public sealed class TestHelper<T>(ITestOutputHelper testOutput) : IDisposable
3434
where T : IIncrementalGenerator, new()
3535
{
36+
#pragma warning disable CS0618 // Type or member is obsolete
3637
/// <summary>
3738
/// Represents the NuGet library dependency for the Splat library.
3839
/// </summary>
39-
#pragma warning disable CS0618 // Type or member is obsolete
4040
private static readonly LibraryRange SplatLibrary =
4141
new("Splat", VersionRange.AllStableFloating, LibraryDependencyTarget.Package);
4242

@@ -90,6 +90,7 @@ public string VerifiedFilePath()
9090
nameof(IViewForGenerator) => "IVIEWFOR",
9191
nameof(RoutedControlHostGenerator) => "ROUTEDHOST",
9292
nameof(ViewModelControlHostGenerator) => "CONTROLHOST",
93+
nameof(BindableDerivedListGenerator) => "DERIVEDLIST",
9394
_ => name,
9495
};
9596
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using ReactiveUI.SourceGenerators;
7+
using Xunit.Abstractions;
8+
9+
namespace ReactiveUI.SourceGenerator.Tests;
10+
11+
/// <summary>
12+
/// BindableDerivedListGeneratorTests.
13+
/// </summary>
14+
public class BindableDerivedListGeneratorTests(ITestOutputHelper output) : TestBase<BindableDerivedListGenerator>(output)
15+
{
16+
/// <summary>
17+
/// Tests that the source generator correctly generates reactive properties.
18+
/// </summary>
19+
/// <returns>A task to monitor the async.</returns>
20+
[Fact]
21+
public Task FromReactiveProperties()
22+
{
23+
// Arrange: Setup the source code that matches the generator input expectations.
24+
const string sourceCode = """
25+
using System.Collections.ObjectModel;
26+
using DynamicData;
27+
28+
namespace TestNs;
29+
30+
public partial class TestVM
31+
{
32+
[BindableDerivedList]
33+
private ReadOnlyObservableCollection<int> _test1;
34+
}
35+
""";
36+
37+
// Act: Initialize the helper and run the generator. Assert: Verify the generated code.
38+
return TestHelper.TestPass(sourceCode);
39+
}
40+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using ReactiveUI;
7+
using ReactiveUI.SourceGenerators;
8+
9+
namespace SGReactiveUI.SourceGenerators.Test;
10+
11+
/// <summary>
12+
/// Person.
13+
/// </summary>
14+
/// <seealso cref="ReactiveUI.ReactiveObject" />
15+
public partial class Person : ReactiveObject
16+
{
17+
/// <summary>
18+
/// Gets or sets a value indicating whether this <see cref="Person"/> is deleted.
19+
/// </summary>
20+
/// <value>
21+
/// <c>true</c> if deleted; otherwise, <c>false</c>.
22+
/// </value>
23+
[Reactive]
24+
public bool Deleted { get; set; }
25+
}

src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using System.Collections.ObjectModel;
67
using System.Diagnostics.CodeAnalysis;
78
using System.Reactive;
89
using System.Reactive.Concurrency;
@@ -11,6 +12,7 @@
1112
using System.Reactive.Subjects;
1213
using System.Runtime.Serialization;
1314
using System.Text.Json.Serialization;
15+
using DynamicData;
1416
using ReactiveUI;
1517
using ReactiveUI.SourceGenerators;
1618

@@ -66,6 +68,12 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis
6668
[Reactive(SetModifier = AccessModifier.Init, UseRequired = true)]
6769
private string _mustBeSet;
6870

71+
[Reactive]
72+
private IEnumerable<Person> _people = [new Person()];
73+
74+
[BindableDerivedList]
75+
private ReadOnlyObservableCollection<Person>? _visiblePeople;
76+
6977
/// <summary>
7078
/// Initializes a new instance of the <see cref="TestViewModel"/> class.
7179
/// </summary>
@@ -186,6 +194,15 @@ public TestViewModel()
186194
_observableAsPropertyFromPropertyHelper = _fromPartialTestSubject.ToProperty(this, x => x.ObservableAsPropertyFromProperty);
187195
_fromPartialTestSubject.OnNext(11);
188196
Console.Out.WriteLine($"Observable updated, value should be 11, value is : {ObservableAsPropertyFromProperty}");
197+
198+
this.WhenAnyValue(vm => vm.People)
199+
.Subscribe(people => people
200+
.AsObservableChangeSet()
201+
.AutoRefresh(x => x.Deleted)
202+
.Filter(x => !x.Deleted)
203+
.Bind(out _visiblePeople)
204+
.Subscribe());
205+
189206
Console.ReadLine();
190207
}
191208

src/ReactiveUI.SourceGenerators.Roslyn/AnalyzerReleases.Shipped.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RXUISG0015 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https:/
2222
RXUISG0017 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
2323
RXUISG0018 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
2424
RXUISG0018 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
25-
25+
RXUISG0019 | ReactiveUI.SourceGenerators.BindableDerivedListGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
2626

2727
## Rules
2828
Shipped in ReactiveUI.SourceGenerators
@@ -83,3 +83,6 @@ This rule checks if the `ObservableAsProperty` has Invalid class inheritance, mu
8383

8484
- RXUISG0018 - ReactiveGenerator
8585
This rule checks if the `Reactive` has Invalid class inheritance, must inherit from `ReactiveObject`.
86+
87+
- RXUISG0019 - BindableDerivedListGenerator
88+
- This rule checks if the `BindableDerivedList` has Invalid property inheritance type.

src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -435,33 +435,29 @@ internal sealed class RoutedControlHostAttribute(string? baseType) : Attribute;
435435
#pragma warning restore
436436
""";
437437

438-
public const string IsExternalInitType = "System.Runtime.CompilerServices.IsExternalInit";
438+
public const string BindableDerivedListAttributeType = "ReactiveUI.SourceGenerators.BindableDerivedListAttribute";
439439

440-
public static string IsExternalInit => $$"""
440+
public static string BindableDerivedListAttribute => $$"""
441441
// Copyright (c) {{DateTime.Now.Year}} .NET Foundation and Contributors. All rights reserved.
442442
// Licensed to the .NET Foundation under one or more agreements.
443443
// The .NET Foundation licenses this file to you under the MIT license.
444444
// See the LICENSE file in the project root for full license information.
445-
446-
// <auto-generated />
447-
#pragma warning disable
448445
449-
#if !NET5_0_OR_GREATER
450-
451-
namespace System.Runtime.CompilerServices;
446+
using System;
452447
453-
using System.Diagnostics;
454-
using System.Diagnostics.CodeAnalysis;
448+
// <auto-generated/>
449+
#pragma warning disable
450+
#nullable enable
451+
namespace ReactiveUI.SourceGenerators;
455452
456453
/// <summary>
457-
/// Reserved to be used by the compiler for tracking metadata. This class should not be used by developers in source code.
454+
/// ReactiveAttribute.
458455
/// </summary>
459-
[ExcludeFromCodeCoverage]
460-
[DebuggerNonUserCode]
461-
static class IsExternalInit;
462-
463-
#endif
464-
456+
/// <seealso cref="Attribute" />
457+
[global::System.CodeDom.Compiler.GeneratedCode("{{ReactiveGenerator.GeneratorName}}", "{{ReactiveGenerator.GeneratorVersion}}")]
458+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
459+
internal sealed class BindableDerivedListAttribute : Attribute;
460+
#nullable restore
465461
#pragma warning restore
466462
""";
467463
}

0 commit comments

Comments
 (0)