diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
index 1fee5b622..4ea9f15e1 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs
@@ -28,6 +28,10 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
/// Indicates whether the property is of a reference type or an unconstrained type parameter.
/// Indicates whether to include nullability annotations on the setter.
/// Indicates whether to annotate the setter as requiring unreferenced code.
+/// Indicates whether to generate property changing partial method.
+/// Indicates whether to generate property changed partial method.
+/// Indicates whether to call property changing method.
+/// Indicates whether to call property changed method.
/// The sequence of forwarded attributes for the generated property.
internal sealed record PropertyInfo(
SyntaxKind AnnotatedMemberKind,
@@ -47,4 +51,8 @@ internal sealed record PropertyInfo(
bool IsReferenceTypeOrUnconstrainedTypeParameter,
bool IncludeMemberNotNullOnSetAccessor,
bool IncludeRequiresUnreferencedCodeOnSetAccessor,
+ bool GenerateOnChanging,
+ bool GenerateOnChanged,
+ bool CallOnChanging,
+ bool CallOnChanged,
EquatableArray ForwardedAttributes);
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
index 2b2756ec4..0de8bbee9 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
@@ -242,6 +242,7 @@ public static bool TryGetInfo(
bool hasOrInheritsClassLevelNotifyDataErrorInfo = false;
bool hasAnyValidationAttributes = false;
bool isOldPropertyValueDirectlyReferenced = IsOldPropertyValueDirectlyReferenced(memberSymbol, propertyName);
+ bool generateOnChanging = true, generateOnChanged = true, callOnChanging = true, callOnChanged = true;
token.ThrowIfCancellationRequested();
@@ -282,6 +283,11 @@ public static bool TryGetInfo(
{
token.ThrowIfCancellationRequested();
+ if (TryGetGenerateOptional(attributeData, ref generateOnChanging, ref generateOnChanged, ref callOnChanging, ref callOnChanged))
+ {
+ continue;
+ }
+
// Gather dependent property and command names
if (TryGatherDependentPropertyChangedNames(memberSymbol, attributeData, in propertyChangedNames, in builder) ||
TryGatherDependentCommandNames(memberSymbol, attributeData, in notifiedCommandNames, in builder))
@@ -415,6 +421,10 @@ public static bool TryGetInfo(
isReferenceTypeOrUnconstrainedTypeParameter,
includeMemberNotNullOnSetAccessor,
includeRequiresUnreferencedCodeOnSetAccessor,
+ generateOnChanging,
+ generateOnChanged,
+ callOnChanging,
+ callOnChanged,
forwardedAttributes.ToImmutable());
diagnostics = builder.ToImmutable();
@@ -1140,7 +1150,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
{
getterFieldIdentifierName = "field";
getterFieldExpression = setterFieldExpression = IdentifierName(getterFieldIdentifierName);
- }
+ }
else if (propertyInfo.FieldName == "value")
{
// In case the backing field is exactly named "value", we need to add the "this." prefix to ensure that comparisons and assignments
@@ -1185,14 +1195,6 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
.WithInitializer(EqualsValueClause(setterFieldExpression)))));
}
- // Add the OnPropertyChanging() call first:
- //
- // OnChanging(value);
- setterStatements.Add(
- ExpressionStatement(
- InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changing"))
- .AddArgumentListArguments(Argument(IdentifierName("value")))));
-
// Optimization: if the previous property value is not being referenced (which we can check by looking for an existing
// symbol matching the name of either of these generated methods), we can pass a default expression and avoid generating
// a field read, which won't otherwise be elided by Roslyn. Otherwise, we just store the value in a local as usual.
@@ -1202,13 +1204,24 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
false => Argument(LiteralExpression(SyntaxKind.DefaultLiteralExpression, Token(SyntaxKind.DefaultKeyword)))
};
- // Also call the overload after that:
- //
- // OnChanging(, value);
- setterStatements.Add(
- ExpressionStatement(
- InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changing"))
- .AddArgumentListArguments(oldPropertyValueArgument, Argument(IdentifierName("value")))));
+ if (propertyInfo.CallOnChanging)
+ {
+ // Add the OnPropertyChanging() call first:
+ //
+ // OnChanging(value);
+ setterStatements.Add(
+ ExpressionStatement(
+ InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changing"))
+ .AddArgumentListArguments(Argument(IdentifierName("value")))));
+
+ // Also call the overload after that:
+ //
+ // OnChanging(, value);
+ setterStatements.Add(
+ ExpressionStatement(
+ InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changing"))
+ .AddArgumentListArguments(oldPropertyValueArgument, Argument(IdentifierName("value")))));
+ }
// Gather the statements to notify dependent properties
foreach (string propertyName in propertyInfo.PropertyChangingNames)
@@ -1248,21 +1261,24 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(propertyInfo.PropertyName))))));
}
- // Add the OnPropertyChanged() call:
- //
- // OnChanged(value);
- setterStatements.Add(
- ExpressionStatement(
- InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changed"))
- .AddArgumentListArguments(Argument(IdentifierName("value")))));
+ if (propertyInfo.CallOnChanged)
+ {
+ // Add the OnPropertyChanged() call:
+ //
+ // OnChanged(value);
+ setterStatements.Add(
+ ExpressionStatement(
+ InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changed"))
+ .AddArgumentListArguments(Argument(IdentifierName("value")))));
- // Do the same for the overload, as above:
- //
- // OnChanged(, value);
- setterStatements.Add(
- ExpressionStatement(
- InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changed"))
- .AddArgumentListArguments(oldPropertyValueArgument, Argument(IdentifierName("value")))));
+ // Do the same for the overload, as above:
+ //
+ // OnChanged(, value);
+ setterStatements.Add(
+ ExpressionStatement(
+ InvocationExpression(IdentifierName($"On{propertyInfo.PropertyName}Changed"))
+ .AddArgumentListArguments(oldPropertyValueArgument, Argument(IdentifierName("value")))));
+ }
// Gather the statements to notify dependent properties
foreach (string propertyName in propertyInfo.PropertyChangedNames)
@@ -1456,6 +1472,9 @@ private static SyntaxTokenList GetPropertyModifiers(PropertyInfo propertyInfo)
/// The generated instances for the OnPropertyChanging and OnPropertyChanged methods.
public static ImmutableArray GetOnPropertyChangeMethodsSyntax(PropertyInfo propertyInfo)
{
+ if (!propertyInfo.GenerateOnChanged && !propertyInfo.GenerateOnChanging)
+ return ImmutableArray.Empty;
+
// Get the property type syntax
TypeSyntax parameterType = IdentifierName(propertyInfo.TypeNameWithNullabilityAnnotations);
@@ -1466,7 +1485,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
// /// This method is invoked right before the value of is changed.
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// partial void OnChanging( value);
- MemberDeclarationSyntax onPropertyChangingDeclaration =
+ MemberDeclarationSyntax? onPropertyChangingDeclaration = propertyInfo.GenerateOnChanging ?
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier($"On{propertyInfo.PropertyName}Changing"))
.AddModifiers(Token(SyntaxKind.PartialKeyword))
.AddParameterListParameters(Parameter(Identifier("value")).WithType(parameterType))
@@ -1480,7 +1499,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
Comment($"/// Executes the logic for when is changing."),
Comment("/// The new property value being set."),
Comment($"/// This method is invoked right before the value of is changed.")), SyntaxKind.OpenBracketToken, TriviaList())))
- .WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) : null;
// Get the type for the 'oldValue' parameter (which can be null on first invocation)
TypeSyntax oldValueTypeSyntax = GetPropertyTypeForOldValue(propertyInfo);
@@ -1493,7 +1512,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
// /// This method is invoked right before the value of is changed.
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// partial void OnChanging( oldValue, newValue);
- MemberDeclarationSyntax onPropertyChanging2Declaration =
+ MemberDeclarationSyntax? onPropertyChanging2Declaration = propertyInfo.GenerateOnChanging ?
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier($"On{propertyInfo.PropertyName}Changing"))
.AddModifiers(Token(SyntaxKind.PartialKeyword))
.AddParameterListParameters(
@@ -1510,7 +1529,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
Comment("/// The previous property value that is being replaced."),
Comment("/// The new property value being set."),
Comment($"/// This method is invoked right before the value of is changed.")), SyntaxKind.OpenBracketToken, TriviaList())))
- .WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) : null;
// Construct the generated method as follows:
//
@@ -1519,7 +1538,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
// /// This method is invoked right after the value of is changed.
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// partial void OnChanged( value);
- MemberDeclarationSyntax onPropertyChangedDeclaration =
+ MemberDeclarationSyntax? onPropertyChangedDeclaration = propertyInfo.GenerateOnChanged ?
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier($"On{propertyInfo.PropertyName}Changed"))
.AddModifiers(Token(SyntaxKind.PartialKeyword))
.AddParameterListParameters(Parameter(Identifier("value")).WithType(parameterType))
@@ -1533,7 +1552,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
Comment($"/// Executes the logic for when just changed."),
Comment("/// The new property value that was set."),
Comment($"/// This method is invoked right after the value of is changed.")), SyntaxKind.OpenBracketToken, TriviaList())))
- .WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) : null;
// Construct the generated method as follows:
//
@@ -1543,7 +1562,7 @@ public static ImmutableArray GetOnPropertyChangeMethods
// /// This method is invoked right after the value of is changed.
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
// partial void OnChanged( oldValue, newValue);
- MemberDeclarationSyntax onPropertyChanged2Declaration =
+ MemberDeclarationSyntax? onPropertyChanged2Declaration = propertyInfo.GenerateOnChanged ?
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier($"On{propertyInfo.PropertyName}Changed"))
.AddModifiers(Token(SyntaxKind.PartialKeyword))
.AddParameterListParameters(
@@ -1560,13 +1579,28 @@ public static ImmutableArray GetOnPropertyChangeMethods
Comment("/// The previous property value that was replaced."),
Comment("/// The new property value that was set."),
Comment($"/// This method is invoked right after the value of is changed.")), SyntaxKind.OpenBracketToken, TriviaList())))
- .WithSemicolonToken(Token(SyntaxKind.SemicolonToken));
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) : null;
- return ImmutableArray.Create(
- onPropertyChangingDeclaration,
- onPropertyChanging2Declaration,
- onPropertyChangedDeclaration,
- onPropertyChanged2Declaration);
+ if (propertyInfo.GenerateOnChanged && propertyInfo.GenerateOnChanging)
+ {
+ return ImmutableArray.Create(
+ onPropertyChangingDeclaration!,
+ onPropertyChanging2Declaration!,
+ onPropertyChangedDeclaration!,
+ onPropertyChanged2Declaration!);
+ }
+ else if (propertyInfo.GenerateOnChanged)
+ {
+ return ImmutableArray.Create(
+ onPropertyChangedDeclaration!,
+ onPropertyChanged2Declaration!);
+ }
+ else
+ {
+ return ImmutableArray.Create(
+ onPropertyChangingDeclaration!,
+ onPropertyChanging2Declaration!);
+ }
}
///
@@ -1752,5 +1786,33 @@ public static string GetGeneratedPropertyName(ISymbol memberSymbol)
return $"{char.ToUpper(propertyName[0], CultureInfo.InvariantCulture)}{propertyName.Substring(1)}";
}
+
+ ///
+ /// Tries to gather generate and call optional from ObservablePropertyAttribute.
+ ///
+ /// The instance.
+ /// Field which indicates whether to generate property On{PropertyName}Changing partial method.
+ /// Field which indicates whether to generate property On{PropertyName}Changed partial method.
+ /// Field which indicates whether to call property On{PropertyName}Changing method.
+ /// Field which indicates whether to call property On{PropertyName}Changed method.
+ /// Whether or not was ObservablePropertyAttribute.
+ private static bool TryGetGenerateOptional(AttributeData attributeData, ref bool generateOnChanging, ref bool generateOnChanged, ref bool callOnChanging, ref bool callOnChanged)
+ {
+ bool value;
+ if (attributeData.AttributeClass?.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true)
+ {
+ if (attributeData.TryGetNamedArgument("GenerateOnChanging", out value))
+ generateOnChanging = value;
+ if (attributeData.TryGetNamedArgument("GenerateOnChanged", out value))
+ generateOnChanged = value;
+ if (attributeData.TryGetNamedArgument("CallOnChanging", out value))
+ callOnChanging = value;
+ if (attributeData.TryGetNamedArgument("CallOnChanged", out value))
+ callOnChanged = value;
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs
index 83311270c..81b93ea4e 100644
--- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs
+++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs
@@ -70,4 +70,23 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class ObservablePropertyAttribute : Attribute
{
+ ///
+ /// Indicates whether to generate property On{PropertyName}Changing partial method.
+ ///
+ public bool GenerateOnChanging { get; set; } = true;
+
+ ///
+ /// Indicates whether to generate property On{PropertyName}Changed partial method.
+ ///
+ public bool GenerateOnChanged { get; set; } = true;
+
+ ///
+ /// Indicates whether to call property On{PropertyName}Changing method.
+ ///
+ public bool CallOnChanging { get; set; } = true;
+
+ ///
+ /// Indicates whether to call property On{PropertyName}Changed method.
+ ///
+ public bool CallOnChanged { get; set; } = true;
}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
index d91dfe2b2..faf04a318 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
@@ -153,6 +153,450 @@ public string Name
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
}
+ [TestMethod]
+ public void ObservablePropertyWithoutGenerateOnChanging()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty(GenerateOnChanging = false)]
+ private string name;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithoutGenerateOnChanged()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty(GenerateOnChanged = false)]
+ private string name;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string? oldValue, string newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string? oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithoutGenerateOnChanging_WithoutCallOnChanging()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty(GenerateOnChanging = false, CallOnChanging = false)]
+ private string name;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnNameChanged(value);
+ OnNameChanged(default, value);
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when just changed.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string value);
+ /// Executes the logic for when just changed.
+ /// The previous property value that was replaced.
+ /// The new property value that was set.
+ /// This method is invoked right after the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanged(string? oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
+ [TestMethod]
+ public void ObservablePropertyWithoutGenerateOnChanged_WithoutCallOnChanged()
+ {
+ string source = """
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty(GenerateOnChanged = false, CallOnChanged = false)]
+ private string name;
+ }
+ """;
+
+#if NET6_0_OR_GREATER
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ [global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string? oldValue, string newValue);
+ }
+ }
+ """;
+#else
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ public string Name
+ {
+ get => name;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer.Default.Equals(name, value))
+ {
+ OnNameChanging(value);
+ OnNameChanging(default, value);
+ OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
+ name = value;
+ OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
+ }
+ }
+ }
+
+ /// Executes the logic for when is changing.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string value);
+ /// Executes the logic for when is changing.
+ /// The previous property value that is being replaced.
+ /// The new property value being set.
+ /// This method is invoked right before the value of is changed.
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ partial void OnNameChanging(string? oldValue, string newValue);
+ }
+ }
+ """;
+#endif
+
+ VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
+ }
+
[TestMethod]
public void ObservablePropertyWithNonNullableValueType_DoesNotEmitMemberNotNullAttribute()
{