Injects code which raises the PropertyChanged
event, into property setters of classes which implement INotifyPropertyChanged.
See Milestones for release notes.
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);
}
}
You can configure the code generator via properties in your project file:
<PropertyGroup>
<PropertyChangedAnalyzerConfiguration>
<IsCodeGeneratorDisabled>false</IsCodeGeneratorDisabled>
<EventInvokerName>OnPropertyChanged</EventInvokerName>
</PropertyChangedAnalyzerConfiguration>
</PropertyGroup>
true
to switch off the code generator.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 for GivenName
and FamilyName
. Therefore, when either GivenName
or FamilyName
is set, PropertyChanged
is raised for FullName
as well. This behavior can be configured manually using the AlsoNotifyFor
attribute on the source property, or the DependsOn
attribute on the target property).
Intercepting the notification call
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.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 the AddINotifyPropertyChangedInterfaceAttribute
attribute. Raising an issue about "this attribute does not behave as expected" will result in a RTFM and the issue being closed.
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