Injects code which raises the PropertyChanged
event, into property setters of classes which implement INotifyPropertyChanged.
This is an add-in for Fody
It is expected that all developers using Fody become a Patron on OpenCollective. See Licensing/Patron FAQ for more information.
See also Fody usage.
Install the PropertyChanged.Fody NuGet package and update the Fody NuGet package:
PM> Install-Package Fody
PM> Install-Package PropertyChanged.Fody
The Install-Package Fody
is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.
Add <PropertyChanged/>
to FodyWeavers.xml
<Weavers>
<PropertyChanged/>
</Weavers>
NOTE: All classes that implement INotifyPropertyChanged
will have notification code injected into property setters.
Before code:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string GivenNames { get; set; }
public string FamilyName { get; set; }
public string FullName => $"{GivenNames} {FamilyName}";
}
What gets compiled:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string givenNames;
public string GivenNames
{
get => givenNames;
set
{
if (value != givenNames)
{
givenNames = value;
OnPropertyChanged(InternalEventArgsCache.GivenNames);
OnPropertyChanged(InternalEventArgsCache.FullName);
}
}
}
string familyName;
public string FamilyName
{
get => familyName;
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged(InternalEventArgsCache.FamilyName);
OnPropertyChanged(InternalEventArgsCache.FullName);
}
}
}
public string FullName => $"{GivenNames} {FamilyName}";
protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
{
PropertyChanged?.Invoke(this, eventArgs);
}
}
internal static class InternalEventArgsCache
{
internal static PropertyChangedEventArgs FamilyName = new PropertyChangedEventArgs("FamilyName");
internal static PropertyChangedEventArgs FullName = new PropertyChangedEventArgs("FullName");
internal static PropertyChangedEventArgs GivenNames = new PropertyChangedEventArgs("GivenNames");
}
(the actual injected type and method names are different)
Starting with version 4 PropertyChanged.Fody ships with a C# code generator that can even more simplify your code by generating
the boilerplate of the basic INotifyPropertyChanged
implementation for you directly as source code.
Simply mark a class implementing INotifyPropertyChanged
or having the [AddINotifyPropertyChangedInterface]
attribute as partial
and the
generator will add the necessary event and event-invokers:
e.g. a class like this:
public partial class Class1 : INotifyPropertyChanged
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
will be complemented by the generator with this:
public partial class Class1
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
{
PropertyChanged?.Invoke(this, eventArgs);
}
}
- Only classes are supported, no records.
- For nested classes, all containing classes must be partial, too.
- Code generators only work correctly in SDK-style projects
You can configure the code generator via properties in your project file:
<PropertyGroup>
<PropertyChangedAnalyzerConfiguration>
<IsCodeGeneratorDisabled>false</IsCodeGeneratorDisabled>
<EventInvokerName>OnPropertyChanged</EventInvokerName>
</PropertyChangedAnalyzerConfiguration>
</PropertyGroup>
- IsCodeGeneratorDisabled: Set to
true
to switch off the code generator. - EventInvokerName: Change the name of the event invoker method from
OnPropertyChanged
to your favorite name.
WPF projects targeting multiple frameworks may fail during the compilation of the *_wpftmp.csproj
with
... error CS0111: Type 'SomeType' already defines a member called 'OnPropertyChanged' with the same parameter types
This can be fixed by adding this build target to your project:
<Target Name="RemoveDuplicateAnalyzers" BeforeTargets="CoreCompile">
<!-- see https://github.com/dotnet/wpf/pull/6680 -->
<RemoveDuplicates Inputs="@(Analyzer)">
<Output
TaskParameter="Filtered"
ItemName="FilteredAnalyzer"/>
</RemoveDuplicates>
<ItemGroup>
<Analyzer Remove="@(Analyzer)" />
<Analyzer Include="@(FilteredAnalyzer)" />
</ItemGroup>
</Target>
-
Dependent properties — In the above sample, the getter for
FullName
depends on the getters forGivenName
andFamilyName
. Therefore, when eitherGivenName
orFamilyName
is set,PropertyChanged
is raised forFullName
as well. This behavior can be configured manually using theAlsoNotifyFor
attribute on the source property, or theDependsOn
attribute on the target property). -
Intercepting the notification call
- Global interception
- Class-level interception — The
OnPropertyChanged
method will only be injected if there is no such existing method on the class; if there is such a method, then calls to that method will be injected into the setters — see here. - Property-level interception — For a given property, if there is a method of the form
On<PropertyName>Changed
, then that method will be called — see here.
-
To get the before / after values, use the following signature for
OnPropertyChanged
/On<PropertyName>Changed
:public void OnPropertyChanged(string propertyName, object before, object after)
-
To prevent a specific class from having the notification call injection, use the
DoNotNotify
attribute. -
To scope the rewriting only to specific classes, and not the whole Assembly, you can use the
FilterType
attribute. This changes the general behavior from from opt-out to opt-in. Example:[assembly: PropertyChanged.FilterType("My.Specific.OptIn.Namespace.")]
. The string is interpreted as a Regex, and you can use multiple filters. A class will be weaved, if any filter matches. -
The
INotifyPropertyChanged
interface can be automatically implemented for a specific class using theAddINotifyPropertyChangedInterfaceAttribute
attribute. Raising an issue about "this attribute does not behave as expected" will result in a RTFM and the issue being closed.- for partial methods this will be done via the code generator, so the implementation is available at compile time.
-
Behavior is configured via attributes, or via options in the
Weavers.xml
file.
For more information, see the wiki pages.
Icon courtesy of The Noun Project