TitanFx.PropertyChanged
1.0.1
dotnet add package TitanFx.PropertyChanged --version 1.0.1
NuGet\Install-Package TitanFx.PropertyChanged -Version 1.0.1
<PackageReference Include="TitanFx.PropertyChanged" Version="1.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="TitanFx.PropertyChanged" Version="1.0.1" />
<PackageReference Include="TitanFx.PropertyChanged"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add TitanFx.PropertyChanged --version 1.0.1
#r "nuget: TitanFx.PropertyChanged, 1.0.1"
#:package TitanFx.PropertyChanged@1.0.1
#addin nuget:?package=TitanFx.PropertyChanged&version=1.0.1
#tool nuget:?package=TitanFx.PropertyChanged&version=1.0.1
TitanFx.PropertyChanged
Easy source generator which reduces the boilerplate needed when implementing INotifyPropertyChanged and INotifyPropertyChanging
Usage
Enable support on a class-by-class basis
The autogenerated implementation can be requested for all partial properties of a class by using the [NotifyPropertyChanged] and [NotifyPropertyChanging] attributes.
Any partial properties on a type with one of these attributes can opt out by using a [NotifyPropertyChange(Skip = true)] attribute.
Enable support on a property-by-property basis
If enabling support for an entire class is not what is wanted, then it can instead be enabled on a per-property basis by annotating individual properties with the [NotifyPropertyChange] attribute, and adding one of the INotifyPropertyChanged or INotifyPropertyChanging interfaces.
Examples
Both of the following code blocks are equivalent and generate similar code:
- Class by class:
using TitanFx.PropertyChanged; namespace MyApp; [NotifyPropertyChanged] public partial class MyViewModel { public partial string? Name { get; set; } public partial Guid Id { get; set; } [NotifyPropertyChange(Affects = [nameof(Lifetime)])] public partial DateTimeOffset CreatedAt { get; set; } [NotifyPropertyChange(Affects = [nameof(Lifetime)])] public partial DateTimeOffset CurrentTimestamp { get; set; } public TimeSpan Lifetime => CurrentTimestamp - CreatedAt; [NotifyPropertyChange(Skip = true)] public partial string? ETag { get; set; } } - Property by property:
public partial class MyViewModel : INotifyPropertyChanged { [NotifyPropertyChange] public partial string? Name { get; set; } [NotifyPropertyChange] public partial Guid Id { get; set; } [NotifyPropertyChange(Affects = [nameof(Lifetime)])] public partial DateTimeOffset CreatedAt { get; set; } [NotifyPropertyChange(Affects = [nameof(Lifetime)])] public partial DateTimeOffset CurrentTimestamp { get; set; } public TimeSpan Lifetime => CurrentTimestamp - CreatedAt; public partial string? ETag { get; set; } }
Then the autogenerated code will function something similar to this:
namespace MyApp;
[NotifyPropertyChanged]
partial class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public partial string? Name { get; set => Set(ref field, value, "Name"); }
public partial Guid Id { get; set => Set(ref field, value, "Id"); }
public partial DateTimeOffset CreatedAt { get; set => Set(ref field, value, "CreatedAt", "Lifetime"); }
public partial DateTimeOffset CurrentTimestamp { get; set => Set(ref field, value, "CurrentTimestamp", "Lifetime"); }
private void Set<T>(ref T field, T value, params ReadOnlySpan<string> propertyNames)
{
if (!EqualityComparer<T>.Default.Equals(field, value)
{
field = value;
OnPropertyChanged(propertyNames);
}
}
private void OnPropertyChanged(params ReadOnlySpan<string> propertyNames)
{
Exception? exception = null;
foreach (var propertyName in propertyNames)
{
try
{
PropertyChanged?.Invoke(this, PropertyChangeEventArgs.Changed(propertyName));
}
catch (Exception ex)
{
exception = ex;
}
}
if (exception is not null)
{
ExceptionDispatchInfo.Throw(exception);
}
}
}
namespace TitanFx.PropertyChanged;
internal static class PropertyChangeEventArgs
{
private static readonly PropertyChangedEventArgs _nullChanged = new(propertyName: null);
private static readonly FrozenDictionary<string, PropertyChangedEventArgs> _lookupChanged = FrozenDictionary.ToFrozenDictionary(
new KeyValuePair<string, PropertyChangedEventArgs>[]
{
new("Name", new PropertyChangedEventArgs("Name")),
new("Id", new PropertyChangedEventArgs("Id")),
new("CreatedAt", new PropertyChangedEventArgs("CreatedAt")),
new("Lifetime", new PropertyChangedEventArgs("Lifetime")),
new("CurrentTimestamp", new PropertyChangedEventArgs("CurrentTimestamp")),
}
);
public static global::System.ComponentModel.PropertyChangedEventArgs Changed(global::System.String? propertyName)
{
if (propertyName is null)
{
return _nullChanged;
}
if (_lookupChanged.TryGetValue(propertyName, out result))
{
return result;
}
return new(propertyName);
}
}
Feature list
- Autogenerated event handlers
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangingEventHandler? PropertyChanging; - Autogenerated event triggers
private void OnPropertyChanged(params ReadOnlySpan<string> propertyNames); private void OnPropertyChanging(params ReadOnlySpan<string> propertyNames); - Autoimplemented properties
public partial MyType MyProp { get; set; } // generates: public partial MyType MyProp { get; set => Set(ref field, value); } [NotifyPropertyChange(Affects = [nameof(OtherProperty)])] public partial MyType MyProp { get; set; } // generates: public partial MyType MyProp { get; set => Set(ref field, value, "MyProp", "OtherProperty"); } public partial MyRefStruct MyProp { get; set; } // generates: public partial MyRefStruct MyProp { get; set => Set(field != value, ref field, value); } - Autogenerated
Sethelper methods// Usage: MyProp { get; set => Set(ref field, value); } private void Set<T>(ref T value, T value, [CallerMemberName]string? propertyName = null); // Usage: MyProp { get; set => Set(ref field, value, nameof(MyProp), nameof(Prop2), ...); } private void Set<T>(ref T value, T value, params ReadOnlySpan<string> propertyNames) // Usage: MyProp { get; set => Set(field != value, ref field, value); } private void Set<T>(bool condition, ref T field, T value, [CallerMemberName]string? propertyName = null) where T : allows ref struct; // Usage: MyProp { get; set => Set(field != value, ref field, nameof(MyProp), nameof(Prop2), ...); } private void Set<T>(bool condition, ref T field, T value, params ReadOnlySpan<string> propertyNames) where T : allows ref struct; // Usage: MyProp { get; set => Set(field != value, v => field = v, value); } private void Set<T>(bool condition, T value, Action<T> setter, [CallerMemberName]string? propertyName = null) where T : allows ref struct; // Usage: MyProp { get; set => Set(field != value, v => field = v, nameof(MyProp), nameof(Prop2), ...); } private void Set<T>(bool condition, T value, Action<T> setter, params ReadOnlySpan<string> propertyNames) where T : allows ref struct; // Usage: MyProp { get; set => Set(field != value, v => field = v, value); } private void Set<TState, TValue>(bool condition, in TState state, TValue value, Setter<TState, TValue> setter, [CallerMemberName]string? propertyName = null) where TState : allows ref struct where TValue : allows ref struct; // Usage: MyProp { get; set => Set(field != value, v => field = v, nameof(MyProp), nameof(Prop2), ...); } private void Set<TState, TValue>(bool condition, in TState state, TValue value, Setter<TState, TValue> setter, params ReadOnlySpan<string> propertyNames) where TState : allows ref struct where TValue : allows ref struct;
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp (>= 5.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.