@@ -75,6 +75,7 @@ internal static class Execute
75
75
ImmutableArray < string > . Builder propertyChangingNames = ImmutableArray . CreateBuilder < string > ( ) ;
76
76
ImmutableArray < string > . Builder notifiedCommandNames = ImmutableArray . CreateBuilder < string > ( ) ;
77
77
ImmutableArray < AttributeInfo > . Builder validationAttributes = ImmutableArray . CreateBuilder < AttributeInfo > ( ) ;
78
+ bool alsoBroadcastChange = false ;
78
79
79
80
// Track the property changing event for the property, if the type supports it
80
81
if ( shouldInvokeOnPropertyChanging )
@@ -90,7 +91,8 @@ internal static class Execute
90
91
{
91
92
// Gather dependent property and command names
92
93
if ( TryGatherDependentPropertyChangedNames ( fieldSymbol , attributeData , propertyChangedNames , builder ) ||
93
- TryGatherDependentCommandNames ( fieldSymbol , attributeData , notifiedCommandNames , builder ) )
94
+ TryGatherDependentCommandNames ( fieldSymbol , attributeData , notifiedCommandNames , builder ) ||
95
+ TryGetIsBroadcastingChanges ( fieldSymbol , attributeData , builder , out alsoBroadcastChange ) )
94
96
{
95
97
continue ;
96
98
}
@@ -129,6 +131,7 @@ internal static class Execute
129
131
propertyChangingNames . ToImmutable ( ) ,
130
132
propertyChangedNames . ToImmutable ( ) ,
131
133
notifiedCommandNames . ToImmutable ( ) ,
134
+ alsoBroadcastChange ,
132
135
validationAttributes . ToImmutable ( ) ) ;
133
136
}
134
137
@@ -326,6 +329,48 @@ bool IsCommandNameValidWithGeneratedMembers(string commandName)
326
329
return false ;
327
330
}
328
331
332
+ /// <summary>
333
+ /// Checks whether a given generated property should also broadcast changes.
334
+ /// </summary>
335
+ /// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
336
+ /// <param name="attributeData">The <see cref="AttributeData"/> instance for <paramref name="fieldSymbol"/>.</param>
337
+ /// <param name="diagnostics">The current collection of gathered diagnostics.</param>
338
+ /// <param name="alsoBroadcastChange">Whether or not the resulting property should also broadcast changes.</param>
339
+ /// <returns>Whether or not the generated property for <paramref name="fieldSymbol"/> used <c>[AlsoBroadcastChange]</c>.</returns>
340
+ private static bool TryGetIsBroadcastingChanges (
341
+ IFieldSymbol fieldSymbol ,
342
+ AttributeData attributeData ,
343
+ ImmutableArray < Diagnostic > . Builder diagnostics ,
344
+ out bool alsoBroadcastChange )
345
+ {
346
+ if ( attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.AlsoBroadcastChangeAttribute" ) == true )
347
+ {
348
+ // If the containing type is valid, track it
349
+ if ( fieldSymbol . ContainingType . InheritsFromFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient" ) ||
350
+ fieldSymbol . ContainingType . HasOrInheritsAttributeWithFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute" ) )
351
+ {
352
+ alsoBroadcastChange = true ;
353
+
354
+ return true ;
355
+ }
356
+
357
+ // Otherwise just emit the diagnostic and then ignore the attribute
358
+ diagnostics . Add (
359
+ InvalidContainingTypeForAlsoBroadcastChangeFieldError ,
360
+ fieldSymbol ,
361
+ fieldSymbol . ContainingType ,
362
+ fieldSymbol . Name ) ;
363
+
364
+ alsoBroadcastChange = false ;
365
+
366
+ return true ;
367
+ }
368
+
369
+ alsoBroadcastChange = false ;
370
+
371
+ return false ;
372
+ }
373
+
329
374
/// <summary>
330
375
/// Gets a <see cref="CompilationUnitSyntax"/> instance with the cached args for property changing notifications.
331
376
/// </summary>
@@ -361,6 +406,33 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
361
406
{
362
407
ImmutableArray < StatementSyntax > . Builder setterStatements = ImmutableArray . CreateBuilder < StatementSyntax > ( ) ;
363
408
409
+ // Get the property type syntax (adding the nullability annotation, if needed)
410
+ TypeSyntax propertyType = propertyInfo . IsNullableReferenceType
411
+ ? NullableType ( IdentifierName ( propertyInfo . TypeName ) )
412
+ : IdentifierName ( propertyInfo . TypeName ) ;
413
+
414
+ // In case the backing field is exactly named "value", we need to add the "this." prefix to ensure that comparisons and assignments
415
+ // with it in the generated setter body are executed correctly and without conflicts with the implicit value parameter.
416
+ ExpressionSyntax fieldExpression = propertyInfo . FieldName switch
417
+ {
418
+ "value" => MemberAccessExpression ( SyntaxKind . SimpleMemberAccessExpression , ThisExpression ( ) , IdentifierName ( "value" ) ) ,
419
+ string name => IdentifierName ( name )
420
+ } ;
421
+
422
+ if ( propertyInfo . AlsoBroadcastChange )
423
+ {
424
+ // If broadcasting changes are required, also store the old value.
425
+ // This code generates a statement as follows:
426
+ //
427
+ // <PROPERTY_TYPE> __oldValue = <FIELD_EXPRESSIONS>;
428
+ setterStatements . Add (
429
+ LocalDeclarationStatement (
430
+ VariableDeclaration ( propertyType )
431
+ . AddVariables (
432
+ VariableDeclarator ( Identifier ( "__oldValue" ) )
433
+ . WithInitializer ( EqualsValueClause ( fieldExpression ) ) ) ) ) ;
434
+ }
435
+
364
436
// Add the OnPropertyChanging() call first:
365
437
//
366
438
// On<PROPERTY_NAME>Changing(value);
@@ -384,14 +456,6 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
384
456
IdentifierName ( propertyName ) ) ) ) ) ) ;
385
457
}
386
458
387
- // In case the backing field is exactly named "value", we need to add the "this." prefix to ensure that comparisons and assignments
388
- // with it in the generated setter body are executed correctly and without conflicts with the implicit value parameter.
389
- ExpressionSyntax fieldExpression = propertyInfo . FieldName switch
390
- {
391
- "value" => MemberAccessExpression ( SyntaxKind . SimpleMemberAccessExpression , ThisExpression ( ) , IdentifierName ( "value" ) ) ,
392
- string name => IdentifierName ( name )
393
- } ;
394
-
395
459
// Add the assignment statement:
396
460
//
397
461
// <FIELD_EXPRESSION> = value;
@@ -452,10 +516,20 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
452
516
IdentifierName ( "NotifyCanExecuteChanged" ) ) ) ) ) ;
453
517
}
454
518
455
- // Get the property type syntax (adding the nullability annotation, if needed)
456
- TypeSyntax propertyType = propertyInfo . IsNullableReferenceType
457
- ? NullableType ( IdentifierName ( propertyInfo . TypeName ) )
458
- : IdentifierName ( propertyInfo . TypeName ) ;
519
+ // Also broadcast the change, if requested
520
+ if ( propertyInfo . AlsoBroadcastChange )
521
+ {
522
+ // This code generates a statement as follows:
523
+ //
524
+ // Broadcast(__oldValue, value, "<PROPERTY_NAME>");
525
+ setterStatements . Add (
526
+ ExpressionStatement (
527
+ InvocationExpression ( IdentifierName ( "Broadcast" ) )
528
+ . AddArgumentListArguments (
529
+ Argument ( IdentifierName ( "__oldValue" ) ) ,
530
+ Argument ( IdentifierName ( "value" ) ) ,
531
+ Argument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( propertyInfo . PropertyName ) ) ) ) ) ) ;
532
+ }
459
533
460
534
// Generate the inner setter block as follows:
461
535
//
0 commit comments