TitanFx.PropertyChanged 1.0.1

dotnet add package TitanFx.PropertyChanged --version 1.0.1
                    
NuGet\Install-Package TitanFx.PropertyChanged -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TitanFx.PropertyChanged" Version="1.0.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TitanFx.PropertyChanged" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="TitanFx.PropertyChanged">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TitanFx.PropertyChanged --version 1.0.1
                    
#r "nuget: TitanFx.PropertyChanged, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TitanFx.PropertyChanged@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TitanFx.PropertyChanged&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=TitanFx.PropertyChanged&version=1.0.1
                    
Install as a Cake Tool

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 Set helper 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;
    
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 144 6/2/2026
1.0.0 117 5/27/2026