Skip to content

Commit b022d99

Browse files
authored
Add AlsoNotify support to ReactiveAttribute and generator (#354)
* Add AlsoNotify support to ReactiveAttribute and generator Introduces the AlsoNotify parameter to the ReactiveAttribute, allowing additional property change notifications to be specified. Updates the source generator to handle AlsoNotify, emitting RaisePropertyChanged calls for the specified properties in generated setters. Includes new and updated tests to verify the new behavior, and updates documentation to describe the new feature. * Trim whitespace and refactor AlsoNotify property handling Removed trailing whitespace in generated property setters across multiple test verification files. Refactored AlsoNotify property handling in the source generator to filter and add valid property names more efficiently, and simplified the code for generating AlsoNotify attributes. Updated a test summary for clarity. * Fix typos in README.md Corrected 'decalaration' to 'declaration' in two instances to improve documentation accuracy. * Fix typo in XML doc comment for Inheritance property Corrected '</sumary>' to '</summary>' in the XML documentation comments for the Inheritance property in ReactiveAttribute. Updated both the source and generated test files for consistency.
1 parent f8a5eca commit b022d99

File tree

30 files changed

+450
-45
lines changed

30 files changed

+450
-45
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ This documentation covers using ReactiveUI Source Generators to simplify and enh
2525
ReactiveUI Source Generators automatically generate ReactiveUI objects to streamline your code. These Source Generators are designed to work with ReactiveUI V19.5.31+ and support the following features:
2626

2727
- `[Reactive]` With field and access modifiers, partial property support (C# 13 Visual Studio Version 17.12.0), partial properties with initializer support (C# preview only)
28-
- `[Reactive(SetModifier = AccessModifier.Protected)]` With field and access modifiers, (Not Required for partial properties, configure set accessor with the property decalaration).
28+
- `[Reactive(SetModifier = AccessModifier.Protected)]` With field and access modifiers, (Not Required for partial properties, configure set accessor with the property declaration).
2929
- `[Reactive(Inheritance = InheritanceModifier.Virtual)]` With field and access modifiers. This will generate a virtual property.
30-
- `[Reactive(UseRequired = true)]` With field and access modifiers. This will generate a required property, (Not Required for partial properties, use required keyword for property decalaration).
30+
- `[Reactive(UseRequired = true)]` With field and access modifiers. This will generate a required property, (Not Required for partial properties, use required keyword for property declaration).
31+
- `[Reactive(nameof(RaiseProperty1), nameof(RaiseProperty2))]` With field and property changed notification for additional properties.
3132
- `[ObservableAsProperty]` With field, method, Observable property and partial property support (C# 13 Visual Studio Version 17.12.0)
3233
- `[ObservableAsProperty(ReadOnly = false)]` Removes readonly keyword from the generated helper field
3334
- `[ObservableAsProperty(PropertyName = "ReadOnlyPropertyName")]`
@@ -91,6 +92,9 @@ Generates a derived list from a `ReadOnlyObservableCollection` backing field.
9192
### `[ReactiveCollection]`
9293
Generates property changed notifications on add, remove, and new actions on an `ObservableCollection` backing field.
9394

95+
### `[IReactiveObject]`
96+
Generates `IReactiveObject` implementation for classes not able to inherit from `ReactiveObject`.
97+
9498
## Historical Approach
9599

96100
### Read-Write Properties
@@ -685,7 +689,7 @@ public partial class MyReactiveClass : ReactiveObject
685689
}
686690
```
687691

688-
### ReactiveObject implementation for classes not able to inherit from ReactiveObject
692+
### IReactiveObject implementation for classes not able to inherit from ReactiveObject
689693
```csharp
690694
using ReactiveUI;
691695
using ReactiveUI.SourceGenerators;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//HintName: ReactiveUI.SourceGenerators.AccessModifier.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+
// <auto-generated/>
8+
#pragma warning disable
9+
#nullable enable
10+
namespace ReactiveUI.SourceGenerators;
11+
12+
/// <summary>
13+
/// AccessModifier.
14+
/// </summary>
15+
internal enum AccessModifier
16+
{
17+
Public,
18+
Protected,
19+
Internal,
20+
Private,
21+
InternalProtected,
22+
PrivateProtected,
23+
Init,
24+
}
25+
26+
/// <summary>
27+
/// Property Access Modifier.
28+
/// </summary>
29+
internal enum PropertyAccessModifier
30+
{
31+
Public,
32+
Protected,
33+
Internal,
34+
Private,
35+
InternalProtected,
36+
PrivateProtected,
37+
}
38+
39+
/// <summary>
40+
/// InheritanceModifier.
41+
/// </summary>
42+
internal enum InheritanceModifier
43+
{
44+
None,
45+
Virtual,
46+
Override,
47+
New,
48+
}
49+
50+
internal enum SplatRegistrationType
51+
{
52+
None,
53+
LazySingleton,
54+
Constant,
55+
PerRequest,
56+
}
57+
#nullable restore
58+
#pragma warning restore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//HintName: ReactiveUI.SourceGenerators.ReactiveAttribute.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+
// <auto-generated/>
8+
#pragma warning disable
9+
#nullable enable
10+
namespace ReactiveUI.SourceGenerators;
11+
12+
/// <summary>
13+
/// ReactiveAttribute.
14+
/// </summary>
15+
/// <seealso cref="Attribute" />
16+
[global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
17+
internal sealed class ReactiveAttribute : global::System.Attribute
18+
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="ReactiveAttribute"/> class.
21+
/// </summary>
22+
public ReactiveAttribute(params string[] alsoNotify)
23+
{
24+
AlsoNotify = alsoNotify;
25+
}
26+
27+
/// <summary>
28+
/// Gets the AccessModifier of the set property.
29+
/// </summary>
30+
/// <value>
31+
/// The AccessModifier of the set property.
32+
/// </value>
33+
public AccessModifier SetModifier { get; init; }
34+
35+
/// <summary>
36+
/// Gets the InheritanceModifier of the property.
37+
/// </summary>
38+
public InheritanceModifier Inheritance { get; init; }
39+
40+
/// <summary>
41+
/// Use Required attribute to indicate that the property is required.
42+
/// </summary>
43+
public bool UseRequired { get; init; }
44+
45+
/// <summary>
46+
/// Gets the AlsoNotify properties to raise change notifications for.
47+
/// </summary>
48+
public string[]? AlsoNotify { get; }
49+
}
50+
#nullable restore
51+
#pragma warning restore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//HintName: TestNs.TestVM.Properties.g.cs
2+
// <auto-generated/>
3+
using ReactiveUI;
4+
5+
#pragma warning disable
6+
#nullable enable
7+
8+
namespace TestNs
9+
{
10+
11+
public partial class TestVM
12+
{
13+
14+
/// <inheritdoc cref="_test4"/>
15+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
16+
public int Test4
17+
{
18+
get => _test4;
19+
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_test4")]
20+
set
21+
{
22+
this.RaiseAndSetIfChanged(ref _test4, value);
23+
}
24+
}
25+
}
26+
}
27+
#nullable restore
28+
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePartialProperties#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ namespace ReactiveUI.SourceGenerators;
1616
[global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
1717
internal sealed class ReactiveAttribute : global::System.Attribute
1818
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="ReactiveAttribute"/> class.
21+
/// </summary>
22+
public ReactiveAttribute(params string[] alsoNotify)
23+
{
24+
AlsoNotify = alsoNotify;
25+
}
26+
1927
/// <summary>
2028
/// Gets the AccessModifier of the set property.
2129
/// </summary>
@@ -26,13 +34,18 @@ internal sealed class ReactiveAttribute : global::System.Attribute
2634

2735
/// <summary>
2836
/// Gets the InheritanceModifier of the property.
29-
/// </sumary>
37+
/// </summary>
3038
public InheritanceModifier Inheritance { get; init; }
3139

3240
/// <summary>
3341
/// Use Required attribute to indicate that the property is required.
3442
/// </summary>
3543
public bool UseRequired { get; init; }
44+
45+
/// <summary>
46+
/// Gets the AlsoNotify properties to raise change notifications for.
47+
/// </summary>
48+
public string[]? AlsoNotify { get; }
3649
}
3750
#nullable restore
38-
#pragma warning restore
51+
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ namespace ReactiveUI.SourceGenerators;
1616
[global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
1717
internal sealed class ReactiveAttribute : global::System.Attribute
1818
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="ReactiveAttribute"/> class.
21+
/// </summary>
22+
public ReactiveAttribute(params string[] alsoNotify)
23+
{
24+
AlsoNotify = alsoNotify;
25+
}
26+
1927
/// <summary>
2028
/// Gets the AccessModifier of the set property.
2129
/// </summary>
@@ -26,13 +34,18 @@ internal sealed class ReactiveAttribute : global::System.Attribute
2634

2735
/// <summary>
2836
/// Gets the InheritanceModifier of the property.
29-
/// </sumary>
37+
/// </summary>
3038
public InheritanceModifier Inheritance { get; init; }
3139

3240
/// <summary>
3341
/// Use Required attribute to indicate that the property is required.
3442
/// </summary>
3543
public bool UseRequired { get; init; }
44+
45+
/// <summary>
46+
/// Gets the AlsoNotify properties to raise change notifications for.
47+
/// </summary>
48+
public string[]? AlsoNotify { get; }
3649
}
3750
#nullable restore
38-
#pragma warning restore
51+
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#TestNs.TestVM.Properties.g.verified.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ public int Test3
1919
{
2020
get => _test3;
2121
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_test3")]
22-
set => this.RaiseAndSetIfChanged(ref _test3, value);
22+
set
23+
{
24+
this.RaiseAndSetIfChanged(ref _test3, value);
25+
}
2326
}
2427
}
2528
}

src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ namespace ReactiveUI.SourceGenerators;
1616
[global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
1717
internal sealed class ReactiveAttribute : global::System.Attribute
1818
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="ReactiveAttribute"/> class.
21+
/// </summary>
22+
public ReactiveAttribute(params string[] alsoNotify)
23+
{
24+
AlsoNotify = alsoNotify;
25+
}
26+
1927
/// <summary>
2028
/// Gets the AccessModifier of the set property.
2129
/// </summary>
@@ -26,13 +34,18 @@ internal sealed class ReactiveAttribute : global::System.Attribute
2634

2735
/// <summary>
2836
/// Gets the InheritanceModifier of the property.
29-
/// </sumary>
37+
/// </summary>
3038
public InheritanceModifier Inheritance { get; init; }
3139

3240
/// <summary>
3341
/// Use Required attribute to indicate that the property is required.
3442
/// </summary>
3543
public bool UseRequired { get; init; }
44+
45+
/// <summary>
46+
/// Gets the AlsoNotify properties to raise change notifications for.
47+
/// </summary>
48+
public string[]? AlsoNotify { get; }
3649
}
3750
#nullable restore
38-
#pragma warning restore
51+
#pragma warning restore

src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#TestNs.TestVM.Properties.g.verified.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ public int Test1
1717
{
1818
get => _test1;
1919
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_test1")]
20-
set => this.RaiseAndSetIfChanged(ref _test1, value);
20+
set
21+
{
22+
this.RaiseAndSetIfChanged(ref _test1, value);
23+
}
2124
}
2225
}
2326
}

src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ namespace ReactiveUI.SourceGenerators;
1616
[global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
1717
internal sealed class ReactiveAttribute : global::System.Attribute
1818
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="ReactiveAttribute"/> class.
21+
/// </summary>
22+
public ReactiveAttribute(params string[] alsoNotify)
23+
{
24+
AlsoNotify = alsoNotify;
25+
}
26+
1927
/// <summary>
2028
/// Gets the AccessModifier of the set property.
2129
/// </summary>
@@ -26,13 +34,18 @@ internal sealed class ReactiveAttribute : global::System.Attribute
2634

2735
/// <summary>
2836
/// Gets the InheritanceModifier of the property.
29-
/// </sumary>
37+
/// </summary>
3038
public InheritanceModifier Inheritance { get; init; }
3139

3240
/// <summary>
3341
/// Use Required attribute to indicate that the property is required.
3442
/// </summary>
3543
public bool UseRequired { get; init; }
44+
45+
/// <summary>
46+
/// Gets the AlsoNotify properties to raise change notifications for.
47+
/// </summary>
48+
public string[]? AlsoNotify { get; }
3649
}
3750
#nullable restore
38-
#pragma warning restore
51+
#pragma warning restore

0 commit comments

Comments
 (0)