@@ -111,6 +111,7 @@ public static bool TryGetInfo(
111
111
bool hasOrInheritsClassLevelNotifyPropertyChangedRecipients = false ;
112
112
bool hasOrInheritsClassLevelNotifyDataErrorInfo = false ;
113
113
bool hasAnyValidationAttributes = false ;
114
+ bool isOldPropertyValueDirectlyReferenced = IsOldPropertyValueDirectlyReferenced ( fieldSymbol , propertyName ) ;
114
115
115
116
// Track the property changing event for the property, if the type supports it
116
117
if ( shouldInvokeOnPropertyChanging )
@@ -263,6 +264,7 @@ public static bool TryGetInfo(
263
264
notifiedCommandNames . ToImmutable ( ) ,
264
265
notifyRecipients ,
265
266
notifyDataErrorInfo ,
267
+ isOldPropertyValueDirectlyReferenced ,
266
268
forwardedAttributes . ToImmutable ( ) ) ;
267
269
268
270
diagnostics = builder . ToImmutable ( ) ;
@@ -637,6 +639,38 @@ private static bool TryGetNotifyDataErrorInfo(
637
639
return false ;
638
640
}
639
641
642
+ /// <summary>
643
+ /// Checks whether the generated code has to directly reference the old property value.
644
+ /// </summary>
645
+ /// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
646
+ /// <param name="propertyName">The name of the property being generated.</param>
647
+ /// <returns>Whether the generated code needs direct access to the old property value.</returns>
648
+ private static bool IsOldPropertyValueDirectlyReferenced ( IFieldSymbol fieldSymbol , string propertyName )
649
+ {
650
+ // Check On<PROPERTY_NAME>Changing(<PROPERTY_TYPE> oldValue, <PROPERTY_TYPE> newValue) first
651
+ foreach ( ISymbol symbol in fieldSymbol . ContainingType . GetMembers ( $ "On{ propertyName } Changing") )
652
+ {
653
+ // No need to be too specific as we're not expecting false positives (which also wouldn't really
654
+ // cause any problems anyway, just produce slightly worse codegen). Just checking the number of
655
+ // parameters is good enough, and keeps the code very simple and cheap to run.
656
+ if ( symbol is IMethodSymbol { Parameters . Length : 2 } )
657
+ {
658
+ return true ;
659
+ }
660
+ }
661
+
662
+ // Do the same for On<PROPERTY_NAME>Changed(<PROPERTY_TYPE> oldValue, <PROPERTY_TYPE> newValue)
663
+ foreach ( ISymbol symbol in fieldSymbol . ContainingType . GetMembers ( $ "On{ propertyName } Changed") )
664
+ {
665
+ if ( symbol is IMethodSymbol { Parameters . Length : 2 } )
666
+ {
667
+ return true ;
668
+ }
669
+ }
670
+
671
+ return false ;
672
+ }
673
+
640
674
/// <summary>
641
675
/// Gets a <see cref="CompilationUnitSyntax"/> instance with the cached args for property changing notifications.
642
676
/// </summary>
@@ -683,15 +717,18 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
683
717
string name => IdentifierName ( name )
684
718
} ;
685
719
686
- // Store the old value for later. This code generates a statement as follows:
687
- //
688
- // <PROPERTY_TYPE> __oldValue = <FIELD_EXPRESSIONS>;
689
- setterStatements . Add (
690
- LocalDeclarationStatement (
691
- VariableDeclaration ( propertyType )
692
- . AddVariables (
693
- VariableDeclarator ( Identifier ( "__oldValue" ) )
694
- . WithInitializer ( EqualsValueClause ( fieldExpression ) ) ) ) ) ;
720
+ if ( propertyInfo . NotifyPropertyChangedRecipients || propertyInfo . IsOldPropertyValueDirectlyReferenced )
721
+ {
722
+ // Store the old value for later. This code generates a statement as follows:
723
+ //
724
+ // <PROPERTY_TYPE> __oldValue = <FIELD_EXPRESSIONS>;
725
+ setterStatements . Add (
726
+ LocalDeclarationStatement (
727
+ VariableDeclaration ( propertyType )
728
+ . AddVariables (
729
+ VariableDeclarator ( Identifier ( "__oldValue" ) )
730
+ . WithInitializer ( EqualsValueClause ( fieldExpression ) ) ) ) ) ;
731
+ }
695
732
696
733
// Add the OnPropertyChanging() call first:
697
734
//
@@ -701,13 +738,22 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
701
738
InvocationExpression ( IdentifierName ( $ "On{ propertyInfo . PropertyName } Changing") )
702
739
. AddArgumentListArguments ( Argument ( IdentifierName ( "value" ) ) ) ) ) ;
703
740
741
+ // Optimization: if the previous property value is not being referenced (which we can check by looking for an existing
742
+ // symbol matching the name of either of these generated methods), we can pass a default expression and avoid generating
743
+ // a field read, which won't otherwise be elided by Roslyn. Otherwise, we just store the value in a local as usual.
744
+ ArgumentSyntax oldPropertyValueArgument = propertyInfo . IsOldPropertyValueDirectlyReferenced switch
745
+ {
746
+ true => Argument ( IdentifierName ( "__oldValue" ) ) ,
747
+ false => Argument ( LiteralExpression ( SyntaxKind . DefaultLiteralExpression , Token ( SyntaxKind . DefaultKeyword ) ) )
748
+ } ;
749
+
704
750
// Also call the overload after that:
705
751
//
706
- // On<PROPERTY_NAME>Changing(__oldValue , value);
752
+ // On<PROPERTY_NAME>Changing(<OLD_PROPERTY_VALUE_EXPRESSION> , value);
707
753
setterStatements . Add (
708
754
ExpressionStatement (
709
755
InvocationExpression ( IdentifierName ( $ "On{ propertyInfo . PropertyName } Changing") )
710
- . AddArgumentListArguments ( Argument ( IdentifierName ( "__oldValue" ) ) , Argument ( IdentifierName ( "value" ) ) ) ) ) ;
756
+ . AddArgumentListArguments ( oldPropertyValueArgument , Argument ( IdentifierName ( "value" ) ) ) ) ) ;
711
757
712
758
// Gather the statements to notify dependent properties
713
759
foreach ( string propertyName in propertyInfo . PropertyChangingNames )
@@ -757,11 +803,11 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
757
803
758
804
// Do the same for the overload, as above:
759
805
//
760
- // On<PROPERTY_NAME>Changed(__oldValue , value);
806
+ // On<PROPERTY_NAME>Changed(<OLD_PROPERTY_VALUE_EXPRESSION> , value);
761
807
setterStatements . Add (
762
808
ExpressionStatement (
763
809
InvocationExpression ( IdentifierName ( $ "On{ propertyInfo . PropertyName } Changed") )
764
- . AddArgumentListArguments ( Argument ( IdentifierName ( "__oldValue" ) ) , Argument ( IdentifierName ( "value" ) ) ) ) ) ;
810
+ . AddArgumentListArguments ( oldPropertyValueArgument , Argument ( IdentifierName ( "value" ) ) ) ) ) ;
765
811
766
812
// Gather the statements to notify dependent properties
767
813
foreach ( string propertyName in propertyInfo . PropertyChangedNames )
0 commit comments