BB84.Notifications 3.6.904

dotnet add package BB84.Notifications --version 3.6.904
                    
NuGet\Install-Package BB84.Notifications -Version 3.6.904
                    
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="BB84.Notifications" Version="3.6.904" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BB84.Notifications" Version="3.6.904" />
                    
Directory.Packages.props
<PackageReference Include="BB84.Notifications" />
                    
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 BB84.Notifications --version 3.6.904
                    
#r "nuget: BB84.Notifications, 3.6.904"
                    
#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 BB84.Notifications@3.6.904
                    
#: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=BB84.Notifications&version=3.6.904
                    
Install as a Cake Addin
#tool nuget:?package=BB84.Notifications&version=3.6.904
                    
Install as a Cake Tool

BB84.Notifications

CI CD CodeQL Dependabot

C# Issues Commit License RepoSize Release

🔎 Project Overview

BB84.Notifications is a comprehensive .NET library that provides robust abstractions and implementations for property change notifications, collection change notifications, and command patterns. The library is designed to facilitate one-way and two-way data binding scenarios, making it an excellent choice for MVVM (Model-View-ViewModel) applications, WPF, Xamarin, MAUI, and other data-binding frameworks.

This library provides:

  • Property Change Notifications: Support for INotifyPropertyChanged and INotifyPropertyChanging with enhanced event args
  • Collection Change Notifications: Advanced collection change tracking with before/after events
  • Reversible Properties: Properties with undo/redo capabilities and value history
  • Validation Support: Built-in validation with INotifyDataErrorInfo implementation
  • Command Patterns: Synchronous and asynchronous command implementations
  • Attribute-Based Notifications: Declarative property notification chaining

⚡ Features

🔔 Enhanced Property Notifications

  • Generic property change event arguments with typed old/new values
  • Support for both changing (before) and changed (after) events
  • Automatic notification propagation via attributes
  • Reflection-optimized attribute processing

📦 Advanced Collection Support

  • Collection change notifications with item-specific information
  • Before/after change events for collections
  • Support for standard collection operations (Add, Remove, Clear)
  • Integration with INotifyCollectionChanged pattern

⏪ Reversible Properties

  • Properties with configurable value history
  • Navigate forward/backward through value changes
  • Undo/redo functionality built-in
  • Configurable history size

✅ Validation Framework

  • Built-in validation using Data Annotations
  • INotifyDataErrorInfo implementation
  • Property-level and object-level validation
  • Error change notifications

⚡ Command Pattern Implementation

  • Synchronous ActionCommand with optional CanExecute logic
  • Asynchronous AsyncActionCommand with exception handling
  • Generic and non-generic command variants
  • Automatic CanExecuteChanged notifications

📦 Target Frameworks

net20 net21 net462 net481 net80 net90

The library supports multiple .NET framework versions:

  • .NET Standard 2.0 - Maximum compatibility
  • .NET Standard 2.1 - Enhanced performance features
  • .NET Framework 4.6.2 - Legacy Windows applications
  • .NET Framework 4.8.1 - Latest .NET Framework
  • .NET 8.0 - Modern .NET with latest features
  • .NET 9.0 - Cutting-edge .NET version

💾 Installation

NuGet

Package Manager Console

Install-Package BB84.Notifications

.NET CLI

dotnet add package BB84.Notifications

PackageReference (csproj)

<PackageReference Include="BB84.Notifications" Version="[latest_version]" />

🥦 Core Components

1. Notifiable Properties

INotifiableProperty<T>

The foundation interface for properties that support change notifications.

public interface INotifiableProperty<T> : INotifyPropertyChanged, INotifyPropertyChanging
{
    T Value { get; set; }
    bool IsDefault { get; }
    bool IsNull { get; }
}
NotifiableProperty<T>

Concrete implementation of a notifiable property with implicit conversion support.

Key Features:

  • Automatic change detection using EqualityComparer<T>
  • Generic event arguments with typed values
  • Implicit conversion operators
  • Thread-safe property updates

2. Reversible Properties

IReversibleProperty<T>

Extends notifiable properties with history and navigation capabilities.

public interface IReversibleProperty<T> : INotifiableProperty<T>
{
    bool CanNavigateNext { get; }
    bool CanNavigatePrevious { get; }
    void NextValue();
    void PreviousValue();
}
ReversibleProperty<T>

Implementation with configurable history size and value navigation.

Key Features:

  • Configurable history size (default: 10 values)
  • Forward/backward navigation through value history
  • Automatic history management
  • Integration with notifiable property features

3. Notifiable Objects

INotifiableObject

Base interface for objects that support property change notifications.

NotifiableObject

Abstract base class providing property change infrastructure.

Key Features:

  • SetProperty method for automatic change detection
  • Attribute-based notification propagation
  • Reflection optimization with caching
  • Support for computed properties

4. Collection Notifications

INotifiableCollection

Comprehensive interface for collection change notifications.

public interface INotifiableCollection :
    INotifyCollectionChanged,
    INotifyCollectionChanging,
    INotifyPropertyChanged,
    INotifyPropertyChanging
{
}
NotifiableCollection

Abstract base class for collections with change notifications.

Key Features:

  • Before/after collection change events
  • Item-specific change information
  • Integration with standard collection interfaces
  • Support for batch operations

5. Validation Support

IValidatableObject

Interface for objects that support validation and error notifications.

ValidatableObject

Base class with built-in validation using Data Annotations.

Key Features:

  • Integration with System.ComponentModel.DataAnnotations
  • Property-level and object-level validation
  • INotifyDataErrorInfo implementation
  • Automatic error change notifications

6. Command Patterns

IActionCommand / IActionCommand<T>

Interfaces for synchronous command execution.

IAsyncActionCommand / IAsyncActionCommand<T>

Interfaces for asynchronous command execution.

Implementations:

  • ActionCommand / ActionCommand<T> - Synchronous commands
  • AsyncActionCommand / AsyncActionCommand<T> - Asynchronous commands

🧰 Usage Guide

Basic Property Notification

public class PersonViewModel : NotifiableObject
{
    private string _firstName = string.Empty;
    private string _lastName = string.Empty;

    public string FirstName
    {
        get => _firstName;
        set => SetProperty(ref _firstName, value);
    }

    public string LastName
    {
        get => _lastName;
        set => SetProperty(ref _lastName, value);
    }
}

Notifiable Properties

public class ProductViewModel
{
    public INotifiableProperty<string> Name { get; set; } = new NotifiableProperty<string>("Default Name");
    public INotifiableProperty<decimal> Price { get; set; } = new NotifiableProperty<decimal>(0m);
    public INotifiableProperty<int> Quantity { get; set; } = new NotifiableProperty<int>(1);

    public ProductViewModel()
    {
        // Subscribe to property changes
        Name.PropertyChanged += (s, e) => Console.WriteLine($"Name changed to: {Name.Value}");
        Price.PropertyChanged += (s, e) => Console.WriteLine($"Price changed to: {Price.Value:C}");
    }
}

Reversible Properties with History

public class EditableDocument
{
    private const int HistorySize = 20;

    public IReversibleProperty<string> Content { get; set; } =
        new ReversibleProperty<string>("Initial Content", HistorySize);

    public void Undo()
    {
        if (Content.CanNavigatePrevious)
            Content.PreviousValue();
    }

    public void Redo()
    {
        if (Content.CanNavigateNext)
            Content.NextValue();
    }
}

Collection Notifications

public class ObservableStringCollection : NotifiableCollection, ICollection<string>
{
    private readonly Collection<string> _items = new();

    public int Count => _items.Count;
    public bool IsReadOnly => false;

    public void Add(string item)
    {
        // Notify before change
        RaiseCollectionChanging(CollectionChangeAction.Add, item);

        // Perform the change
        _items.Add(item);

        // Notify after change
        RaiseCollectionChanged(CollectionChangeAction.Add, item);
    }

    public bool Remove(string item)
    {
        if (!_items.Contains(item))
            return false;

        RaiseCollectionChanging(CollectionChangeAction.Remove, item);
        bool removed = _items.Remove(item);
        RaiseCollectionChanged(CollectionChangeAction.Remove, item);

        return removed;
    }

    public void Clear()
    {
        RaiseCollectionChanging(CollectionChangeAction.Refresh);
        _items.Clear();
        RaiseCollectionChanged(CollectionChangeAction.Refresh);
    }

    // ... implement other ICollection<string> members
}

Validation with Data Annotations

public class UserRegistrationViewModel : ValidatableObject
{
    private string _email = string.Empty;
    private string _password = string.Empty;
    private int _age;

    [Required(ErrorMessage = "Email is required")]
    [EmailAddress(ErrorMessage = "Invalid email format")]
    public string Email
    {
        get => _email;
        set => SetValidatedProperty(ref _email, value);
    }

    [Required(ErrorMessage = "Password is required")]
    [MinLength(8, ErrorMessage = "Password must be at least 8 characters")]
    public string Password
    {
        get => _password;
        set => SetValidatedProperty(ref _password, value);
    }

    [Range(18, 120, ErrorMessage = "Age must be between 18 and 120")]
    public int Age
    {
        get => _age;
        set => SetValidatedProperty(ref _age, value);
    }

    public bool CanSubmit => IsValid && !HasErrors;
}

Attribute-Based Property Notifications

public class ShoppingCartItemViewModel : NotifiableObject
{
    private int _quantity;
    private decimal _unitPrice;

    [NotifyChanged(nameof(TotalPrice))]
    [NotifyChanging(nameof(TotalPrice))]
    public int Quantity
    {
        get => _quantity;
        set => SetProperty(ref _quantity, value);
    }

    [NotifyChanged(nameof(TotalPrice))]
    [NotifyChanging(nameof(TotalPrice))]
    public decimal UnitPrice
    {
        get => _unitPrice;
        set => SetProperty(ref _unitPrice, value);
    }

    // This property will automatically receive notifications when Quantity or UnitPrice changes
    public decimal TotalPrice => Quantity * UnitPrice;
}

Command Implementation

public class MainViewModel : NotifiableObject
{
    private string _searchText = string.Empty;
    private bool _isSearching;

    public string SearchText
    {
        get => _searchText;
        set => SetProperty(ref _searchText, value);
    }

    public bool IsSearching
    {
        get => _isSearching;
        private set => SetProperty(ref _isSearching, value);
    }

    // Synchronous command
    public IActionCommand ClearCommand { get; }

    // Asynchronous command with parameter
    public IAsyncActionCommand<string> SearchCommand { get; }

    public MainViewModel()
    {
        ClearCommand = new ActionCommand(
            execute: () => SearchText = string.Empty,
            canExecute: () => !string.IsNullOrEmpty(SearchText)
        );

        SearchCommand = new AsyncActionCommand<string>(
            execute: async (query) => await PerformSearchAsync(query),
            canExecute: (query) => !IsSearching && !string.IsNullOrWhiteSpace(query),
            onException: (ex) => Console.WriteLine($"Search failed: {ex.Message}")
        );

        // Update command states when properties change
        PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(SearchText))
            {
                ClearCommand.RaiseCanExecuteChanged();
                SearchCommand.RaiseCanExecuteChanged();
            }
        };
    }

    private async Task PerformSearchAsync(string query)
    {
        IsSearching = true;
        try
        {
            // Simulate async search
            await Task.Delay(2000);
            // Perform actual search logic here
        }
        finally
        {
            IsSearching = false;
        }
    }
}

🎛️ Advanced Scenarios

Custom Event Arguments

The library provides strongly-typed event arguments that carry additional information:

public void HandlePropertyChanging(object sender, PropertyChangingEventArgs e)
{
    if (e is PropertyChangingEventArgs<string> stringArgs)
    {
        string oldValue = stringArgs.OldValue;
        string propertyName = stringArgs.PropertyName;
        Console.WriteLine($"Property '{propertyName}' changing from '{oldValue}'");
    }
}

public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e is PropertyChangedEventArgs<string> stringArgs)
    {
        string newValue = stringArgs.NewValue;
        string propertyName = stringArgs.PropertyName;
        Console.WriteLine($"Property '{propertyName}' changed to '{newValue}'");
    }
}

Collection Change Handling

public void HandleCollectionChanges()
{
    var collection = new ObservableStringCollection();

    collection.CollectionChanging += (sender, e) =>
    {
        Console.WriteLine($"Collection about to {e.Action}");
        if (e is CollectionChangingEventArgs<string> typedArgs)
        {
            Console.WriteLine($"Item: {typedArgs.Item}");
        }
    };

    collection.CollectionChanged += (sender, e) =>
    {
        Console.WriteLine($"Collection {e.Action} completed");
        if (e is CollectionChangedEventArgs<string> typedArgs)
        {
            Console.WriteLine($"Item: {typedArgs.Item}");
        }
    };
}

Performance Considerations

Attribute Caching

The library optimizes reflection-based attribute processing by caching property relationships:

// Attributes are processed once during object construction
// Subsequent property changes use cached information for optimal performance
public class OptimizedViewModel : NotifiableObject
{
    // Attribute metadata is cached automatically
    [NotifyChanged(nameof(DependentProperty1), nameof(DependentProperty2))]
    public string SourceProperty { get; set; }

    public string DependentProperty1 => $"Dependent on: {SourceProperty}";
    public string DependentProperty2 => $"Also dependent on: {SourceProperty}";
}
Memory Management

The library is designed to minimize memory allocations:

  • Event argument objects are reused where possible
  • Collection change notifications use efficient data structures
  • Property change detection uses optimized equality comparisons

📔 API Reference

Core Namespaces

  • BB84.Notifications - Main classes and implementations
  • BB84.Notifications.Interfaces - Core interfaces
  • BB84.Notifications.Components - Event argument classes and delegates
  • BB84.Notifications.Commands - Command pattern implementations
  • BB84.Notifications.Attributes - Notification attributes
  • BB84.Notifications.Extensions - Utility extensions

Key Classes

Class Description Key Features
NotifiableProperty<T> Generic property with change notifications Implicit operators, typed events
ReversibleProperty<T> Property with value history Undo/redo, configurable history
NotifiableObject Base class for notifiable objects SetProperty, attribute support
NotifiableCollection Base class for notifiable collections Before/after events, typed notifications
ValidatableObject Base class with validation Data annotations, error notifications
ActionCommand Synchronous command implementation CanExecute logic, parameter support
AsyncActionCommand Asynchronous command implementation Exception handling, cancellation

Event Types

Event Handler Description Event Args
PropertyChangingEventHandler Property about to change PropertyChangingEventArgs<T>
PropertyChangedEventHandler Property has changed PropertyChangedEventArgs<T>
CollectionChangingEventHandler Collection about to change CollectionChangingEventArgs<T>
CollectionChangedEventHandler Collection has changed CollectionChangedEventArgs<T>

🧰 Examples

Complete MVVM Example

// Model
public class Person
{
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public DateTime BirthDate { get; set; }
}

// ViewModel
public class PersonViewModel : ValidatableObject
{
    private string _firstName = string.Empty;
    private string _lastName = string.Empty;
    private DateTime _birthDate = DateTime.Today;

    [Required(ErrorMessage = "First name is required")]
    [NotifyChanged(nameof(FullName))]
    public string FirstName
    {
        get => _firstName;
        set => SetValidatedProperty(ref _firstName, value);
    }

    [Required(ErrorMessage = "Last name is required")]
    [NotifyChanged(nameof(FullName))]
    public string LastName
    {
        get => _lastName;
        set => SetValidatedProperty(ref _lastName, value);
    }

    public DateTime BirthDate
    {
        get => _birthDate;
        set => SetProperty(ref _birthDate, value);
    }

    public string FullName => $"{FirstName} {LastName}".Trim();

    public IActionCommand SaveCommand { get; }
    public IActionCommand ResetCommand { get; }

    public PersonViewModel()
    {
        SaveCommand = new ActionCommand(
            execute: Save,
            canExecute: () => IsValid && !HasErrors
        );

        ResetCommand = new ActionCommand(Reset);

        // Update command states when validation changes
        ErrorsChanged += (s, e) => SaveCommand.RaiseCanExecuteChanged();
    }

    private void Save()
    {
        if (!IsValid) return;

        var person = new Person
        {
            FirstName = FirstName,
            LastName = LastName,
            BirthDate = BirthDate
        };

        // Save logic here
        Console.WriteLine($"Saving: {person.FirstName} {person.LastName}");
    }

    private void Reset()
    {
        FirstName = string.Empty;
        LastName = string.Empty;
        BirthDate = DateTime.Today;
    }
}

Reactive Programming Pattern

public class ReactiveViewModel : NotifiableObject
{
    private readonly INotifiableProperty<string> _searchTerm = new NotifiableProperty<string>(string.Empty);
    private readonly INotifiableProperty<List<string>> _results = new NotifiableProperty<List<string>>(new List<string>());
    private readonly INotifiableProperty<bool> _isLoading = new NotifiableProperty<bool>(false);

    public INotifiableProperty<string> SearchTerm => _searchTerm;
    public INotifiableProperty<List<string>> Results => _results;
    public INotifiableProperty<bool> IsLoading => _isLoading;

    public ReactiveViewModel()
    {
        // React to search term changes
        _searchTerm.PropertyChanged += async (s, e) =>
        {
            if (e is PropertyChangedEventArgs<string> args && !string.IsNullOrEmpty(args.NewValue))
            {
                await PerformSearchAsync(args.NewValue);
            }
        };
    }

    private async Task PerformSearchAsync(string term)
    {
        _isLoading.Value = true;
        try
        {
            // Simulate async search
            await Task.Delay(500);
            var results = Enumerable.Range(1, 10)
                .Select(i => $"{term} Result {i}")
                .ToList();

            _results.Value = results;
        }
        finally
        {
            _isLoading.Value = false;
        }
    }
}

💎 Testing

The library includes comprehensive unit tests covering all major functionality. The test suite uses MSTest framework and covers:

  • Property change notifications
  • Collection change events
  • Command execution and cancellation
  • Validation scenarios
  • Attribute-based notifications
  • Error handling and edge cases

Running Tests

dotnet test

Test Coverage Areas

  • Property Notifications: All notification scenarios and event argument types
  • Collection Operations: Add, remove, clear, and batch operations
  • Validation: Data annotation validation and error propagation
  • Commands: Synchronous and asynchronous execution with various parameters
  • Attributes: Notification chaining and dependency tracking
  • Edge Cases: Null values, default values, threading scenarios

🤝 Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository and create a feature branch
  2. Write tests for new functionality
  3. Follow coding standards and maintain consistent style
  4. Update documentation for public API changes
  5. Submit a pull request with a clear description

Development Setup

  1. Clone the repository
  2. Open the solution in Visual Studio 2022 or VS Code
  3. Restore NuGet packages
  4. Build and run tests

Code Standards

  • Use C# 13.0 language features where appropriate
  • Follow Microsoft naming conventions
  • Include XML documentation for public APIs
  • Maintain high test coverage (>90%)

⚖️ License

This project is licensed under the MIT License - see the LICENSE file for details.

📚 Support and Resources

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in 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
3.6.904 315 9/4/2025
3.6.531 627 5/31/2025
3.6.409 797 4/9/2025
3.6.322 308 3/22/2025
3.6.319 233 3/19/2025
3.6.130 557 1/30/2025
3.5.1216 757 12/16/2024
3.5.916 1,125 9/16/2024
3.4.715 851 7/15/2024
3.4.713 168 7/13/2024
3.3.616 363 6/16/2024
3.3.606 223 6/6/2024
3.2.603 179 6/3/2024
3.2.520 359 5/20/2024
3.2.421 301 4/21/2024
3.1.421 177 4/21/2024
3.1.415 240 4/15/2024
3.0.0 328 3/25/2024
2.1.1 371 2/6/2024
2.1.0 400 1/14/2024
2.0.0 234 1/9/2024
1.8.2 294 1/1/2024
1.8.1 254 11/20/2023
1.8.0 177 11/13/2023
1.7.0 161 11/12/2023
1.6.1 159 11/12/2023
1.6.0 161 11/12/2023
1.5.0 189 11/6/2023
1.4.0 170 11/3/2023
1.3.1 180 11/1/2023
1.3.0 182 11/1/2023
1.2.1 197 10/25/2023
1.2.0 158 10/23/2023
1.1.0 184 10/21/2023
1.0.1 168 10/21/2023
1.0.0 174 10/21/2023