{"id":19070661,"url":"https://github.com/bobobass84/bb84.notifications","last_synced_at":"2025-10-26T09:44:12.567Z","repository":{"id":202625988,"uuid":"707760219","full_name":"BoBoBaSs84/BB84.Notifications","owner":"BoBoBaSs84","description":"This repository contains relevant abstractions and implementations for one- and two-way binding of properties, for notifications about changed properties, for notifications about changed collections and for the Relay Command Pattern for synchronous and asynchronous operations.","archived":false,"fork":false,"pushed_at":"2025-04-09T15:10:33.000Z","size":30324,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-14T06:53:19.732Z","etag":null,"topics":["common","csharp","library","notification"],"latest_commit_sha":null,"homepage":"https://bobobass84.github.io/BB84.Notifications/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BoBoBaSs84.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-10-20T15:40:24.000Z","updated_at":"2025-04-05T12:24:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"5cad3da2-32f3-4673-a4c7-2927f2c57c11","html_url":"https://github.com/BoBoBaSs84/BB84.Notifications","commit_stats":null,"previous_names":["bobobass84/bb84.notifications"],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BoBoBaSs84%2FBB84.Notifications","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BoBoBaSs84%2FBB84.Notifications/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BoBoBaSs84%2FBB84.Notifications/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BoBoBaSs84%2FBB84.Notifications/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BoBoBaSs84","download_url":"https://codeload.github.com/BoBoBaSs84/BB84.Notifications/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248837281,"owners_count":21169374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["common","csharp","library","notification"],"created_at":"2024-11-09T01:19:54.320Z","updated_at":"2025-10-26T09:44:12.559Z","avatar_url":"https://github.com/BoBoBaSs84.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿# BB84.Notifications\n\n[![CI](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/ci.yml)\n[![CD](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/cd.yml/badge.svg?branch=main)](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/cd.yml)\n[![CodeQL](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/github-code-scanning/codeql)\n[![Dependabot](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/dependabot/dependabot-updates/badge.svg?branch=main)](https://github.com/BoBoBaSs84/BB84.Notifications/actions/workflows/dependabot/dependabot-updates)\n\n[![C#](https://img.shields.io/badge/C%23-13.0-239120)](https://github.com/BoBoBaSs84/BB84.Notifications)\n[![Issues](https://img.shields.io/github/issues/BoBoBaSs84/BB84.Notifications)](https://github.com/BoBoBaSs84/BB84.Notifications/issues)\n[![Commit](https://img.shields.io/github/last-commit/BoBoBaSs84/BB84.Notifications)](https://github.com/BoBoBaSs84/BB84.Notifications/commit/main)\n[![License](https://img.shields.io/github/license/BoBoBaSs84/BB84.Notifications)](https://github.com/BoBoBaSs84/BB84.Notifications/blob/main/LICENSE)\n[![RepoSize](https://img.shields.io/github/repo-size/BoBoBaSs84/BB84.Notifications)](https://github.com/BoBoBaSs84/BB84.Notifications)\n[![Release](https://img.shields.io/github/v/release/BoBoBaSs84/BB84.Notifications)](https://github.com/BoBoBaSs84/BB84.Notifications/releases/latest)\n\n## 🔎 Project Overview\n\n**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.\n\nThis library provides:\n\n- **Property Change Notifications**: Support for `INotifyPropertyChanged` and `INotifyPropertyChanging` with enhanced event args\n- **Collection Change Notifications**: Advanced collection change tracking with before/after events\n- **Reversible Properties**: Properties with undo/redo capabilities and value history\n- **Validation Support**: Built-in validation with `INotifyDataErrorInfo` implementation\n- **Command Patterns**: Synchronous and asynchronous command implementations\n- **Attribute-Based Notifications**: Declarative property notification chaining\n\n## ⚡ Features\n\n### 🔔 Enhanced Property Notifications\n\n- Generic property change event arguments with typed old/new values\n- Support for both changing (before) and changed (after) events\n- Automatic notification propagation via attributes\n- Reflection-optimized attribute processing\n\n### 📦 Advanced Collection Support\n\n- Collection change notifications with item-specific information\n- Before/after change events for collections\n- Support for standard collection operations (Add, Remove, Clear)\n- Integration with `INotifyCollectionChanged` pattern\n\n### ⏪ Reversible Properties\n\n- Properties with configurable value history\n- Navigate forward/backward through value changes\n- Undo/redo functionality built-in\n- Configurable history size\n\n### ✅ Validation Framework\n\n- Built-in validation using Data Annotations\n- `INotifyDataErrorInfo` implementation\n- Property-level and object-level validation\n- Error change notifications\n\n### ⚡ Command Pattern Implementation\n\n- Synchronous `ActionCommand` with optional `CanExecute` logic\n- Asynchronous `AsyncActionCommand` with exception handling\n- Generic and non-generic command variants\n- Automatic `CanExecuteChanged` notifications\n\n## 📦 Target Frameworks\n\n[![net20](https://img.shields.io/badge/netstandard2.0-5C2D91?logo=.NET\u0026labelColor=gray)](https://github.com/BoBoBaSs84/BB84.Notifications)\n[![net21](https://img.shields.io/badge/netstandard2.1-5C2D91?logo=.NET\u0026labelColor=gray)](https://github.com/BoBoBaSs84/BB84.Notifications)\n[![net462](https://img.shields.io/badge/net462-5C2D91?logo=.NET\u0026labelColor=gray)](https://github.com/BoBoBaSs84/BB84.Notifications)\n[![net481](https://img.shields.io/badge/net481-5C2D91?logo=.NET\u0026labelColor=gray)](https://github.com/BoBoBaSs84/BB84.Notifications)\n[![net80](https://img.shields.io/badge/net8.0-5C2D91?logo=.NET\u0026labelColor=gray)](https://github.com/BoBoBaSs84/BB84.Notifications)\n[![net90](https://img.shields.io/badge/net9.0-5C2D91?logo=.NET\u0026labelColor=gray)](https://github.com/BoBoBaSs84/BB84.Notifications)\n\nThe library supports multiple .NET framework versions:\n\n- **.NET Standard 2.0** - Maximum compatibility\n- **.NET Standard 2.1** - Enhanced performance features\n- **.NET Framework 4.6.2** - Legacy Windows applications\n- **.NET Framework 4.8.1** - Latest .NET Framework\n- **.NET 8.0** - Modern .NET with latest features\n- **.NET 9.0** - Cutting-edge .NET version\n\n## 💾 Installation\n\n[![NuGet](https://img.shields.io/nuget/v/BB84.Notifications.svg?logo=nuget\u0026logoColor=white)](https://www.nuget.org/packages/BB84.Notifications)\n\n### Package Manager Console\n\n```powershell\nInstall-Package BB84.Notifications\n```\n\n### .NET CLI\n\n```bash\ndotnet add package BB84.Notifications\n```\n\n### PackageReference (csproj)\n\n```xml\n\u003cPackageReference Include=\"BB84.Notifications\" Version=\"[latest_version]\" /\u003e\n```\n\n## 🥦 Core Components\n\n### 1. Notifiable Properties\n\n#### INotifiableProperty\u0026lt;T\u0026gt;\n\nThe foundation interface for properties that support change notifications.\n\n```csharp\npublic interface INotifiableProperty\u003cT\u003e : INotifyPropertyChanged, INotifyPropertyChanging\n{\n    T Value { get; set; }\n    bool IsDefault { get; }\n    bool IsNull { get; }\n}\n```\n\n#### NotifiableProperty\u0026lt;T\u0026gt;\n\nConcrete implementation of a notifiable property with implicit conversion support.\n\n**Key Features:**\n\n- Automatic change detection using `EqualityComparer\u003cT\u003e`\n- Generic event arguments with typed values\n- Implicit conversion operators\n- Thread-safe property updates\n\n### 2. Reversible Properties\n\n#### IReversibleProperty\u0026lt;T\u0026gt;\n\nExtends notifiable properties with history and navigation capabilities.\n\n```csharp\npublic interface IReversibleProperty\u003cT\u003e : INotifiableProperty\u003cT\u003e\n{\n    bool CanNavigateNext { get; }\n    bool CanNavigatePrevious { get; }\n    void NextValue();\n    void PreviousValue();\n}\n```\n\n#### ReversibleProperty\u0026lt;T\u0026gt;\n\nImplementation with configurable history size and value navigation.\n\n**Key Features:**\n\n- Configurable history size (default: 10 values)\n- Forward/backward navigation through value history\n- Automatic history management\n- Integration with notifiable property features\n\n### 3. Notifiable Objects\n\n#### INotifiableObject\n\nBase interface for objects that support property change notifications.\n\n#### NotifiableObject\n\nAbstract base class providing property change infrastructure.\n\n**Key Features:**\n\n- `SetProperty` method for automatic change detection\n- Attribute-based notification propagation\n- Reflection optimization with caching\n- Support for computed properties\n\n### 4. Collection Notifications\n\n#### INotifiableCollection\n\nComprehensive interface for collection change notifications.\n\n```csharp\npublic interface INotifiableCollection :\n    INotifyCollectionChanged,\n    INotifyCollectionChanging,\n    INotifyPropertyChanged,\n    INotifyPropertyChanging\n{\n}\n```\n\n#### NotifiableCollection\n\nAbstract base class for collections with change notifications.\n\n**Key Features:**\n\n- Before/after collection change events\n- Item-specific change information\n- Integration with standard collection interfaces\n- Support for batch operations\n\n### 5. Validation Support\n\n#### IValidatableObject\n\nInterface for objects that support validation and error notifications.\n\n#### ValidatableObject\n\nBase class with built-in validation using Data Annotations.\n\n**Key Features:**\n\n- Integration with `System.ComponentModel.DataAnnotations`\n- Property-level and object-level validation\n- `INotifyDataErrorInfo` implementation\n- Automatic error change notifications\n\n### 6. Command Patterns\n\n#### IActionCommand / IActionCommand\u0026lt;T\u0026gt;\n\nInterfaces for synchronous command execution.\n\n#### IAsyncActionCommand / IAsyncActionCommand\u0026lt;T\u0026gt;\n\nInterfaces for asynchronous command execution.\n\n**Implementations:**\n\n- `ActionCommand` / `ActionCommand\u003cT\u003e` - Synchronous commands\n- `AsyncActionCommand` / `AsyncActionCommand\u003cT\u003e` - Asynchronous commands\n\n## 🧰 Usage Guide\n\n### Basic Property Notification\n\n```csharp\npublic class PersonViewModel : NotifiableObject\n{\n    private string _firstName = string.Empty;\n    private string _lastName = string.Empty;\n\n    public string FirstName\n    {\n        get =\u003e _firstName;\n        set =\u003e SetProperty(ref _firstName, value);\n    }\n\n    public string LastName\n    {\n        get =\u003e _lastName;\n        set =\u003e SetProperty(ref _lastName, value);\n    }\n}\n```\n\n### Notifiable Properties\n\n```csharp\npublic class ProductViewModel\n{\n    public INotifiableProperty\u003cstring\u003e Name { get; set; } = new NotifiableProperty\u003cstring\u003e(\"Default Name\");\n    public INotifiableProperty\u003cdecimal\u003e Price { get; set; } = new NotifiableProperty\u003cdecimal\u003e(0m);\n    public INotifiableProperty\u003cint\u003e Quantity { get; set; } = new NotifiableProperty\u003cint\u003e(1);\n\n    public ProductViewModel()\n    {\n        // Subscribe to property changes\n        Name.PropertyChanged += (s, e) =\u003e Console.WriteLine($\"Name changed to: {Name.Value}\");\n        Price.PropertyChanged += (s, e) =\u003e Console.WriteLine($\"Price changed to: {Price.Value:C}\");\n    }\n}\n```\n\n### Reversible Properties with History\n\n```csharp\npublic class EditableDocument\n{\n    private const int HistorySize = 20;\n\n    public IReversibleProperty\u003cstring\u003e Content { get; set; } =\n        new ReversibleProperty\u003cstring\u003e(\"Initial Content\", HistorySize);\n\n    public void Undo()\n    {\n        if (Content.CanNavigatePrevious)\n            Content.PreviousValue();\n    }\n\n    public void Redo()\n    {\n        if (Content.CanNavigateNext)\n            Content.NextValue();\n    }\n}\n```\n\n### Collection Notifications\n\n```csharp\npublic class ObservableStringCollection : NotifiableCollection, ICollection\u003cstring\u003e\n{\n    private readonly Collection\u003cstring\u003e _items = new();\n\n    public int Count =\u003e _items.Count;\n    public bool IsReadOnly =\u003e false;\n\n    public void Add(string item)\n    {\n        // Notify before change\n        RaiseCollectionChanging(CollectionChangeAction.Add, item);\n\n        // Perform the change\n        _items.Add(item);\n\n        // Notify after change\n        RaiseCollectionChanged(CollectionChangeAction.Add, item);\n    }\n\n    public bool Remove(string item)\n    {\n        if (!_items.Contains(item))\n            return false;\n\n        RaiseCollectionChanging(CollectionChangeAction.Remove, item);\n        bool removed = _items.Remove(item);\n        RaiseCollectionChanged(CollectionChangeAction.Remove, item);\n\n        return removed;\n    }\n\n    public void Clear()\n    {\n        RaiseCollectionChanging(CollectionChangeAction.Refresh);\n        _items.Clear();\n        RaiseCollectionChanged(CollectionChangeAction.Refresh);\n    }\n\n    // ... implement other ICollection\u003cstring\u003e members\n}\n```\n\n### Validation with Data Annotations\n\n```csharp\npublic class UserRegistrationViewModel : ValidatableObject\n{\n    private string _email = string.Empty;\n    private string _password = string.Empty;\n    private int _age;\n\n    [Required(ErrorMessage = \"Email is required\")]\n    [EmailAddress(ErrorMessage = \"Invalid email format\")]\n    public string Email\n    {\n        get =\u003e _email;\n        set =\u003e SetValidatedProperty(ref _email, value);\n    }\n\n    [Required(ErrorMessage = \"Password is required\")]\n    [MinLength(8, ErrorMessage = \"Password must be at least 8 characters\")]\n    public string Password\n    {\n        get =\u003e _password;\n        set =\u003e SetValidatedProperty(ref _password, value);\n    }\n\n    [Range(18, 120, ErrorMessage = \"Age must be between 18 and 120\")]\n    public int Age\n    {\n        get =\u003e _age;\n        set =\u003e SetValidatedProperty(ref _age, value);\n    }\n\n    public bool CanSubmit =\u003e IsValid \u0026\u0026 !HasErrors;\n}\n```\n\n### Attribute-Based Property Notifications\n\n```csharp\npublic class ShoppingCartItemViewModel : NotifiableObject\n{\n    private int _quantity;\n    private decimal _unitPrice;\n\n    [NotifyChanged(nameof(TotalPrice))]\n    [NotifyChanging(nameof(TotalPrice))]\n    public int Quantity\n    {\n        get =\u003e _quantity;\n        set =\u003e SetProperty(ref _quantity, value);\n    }\n\n    [NotifyChanged(nameof(TotalPrice))]\n    [NotifyChanging(nameof(TotalPrice))]\n    public decimal UnitPrice\n    {\n        get =\u003e _unitPrice;\n        set =\u003e SetProperty(ref _unitPrice, value);\n    }\n\n    // This property will automatically receive notifications when Quantity or UnitPrice changes\n    public decimal TotalPrice =\u003e Quantity * UnitPrice;\n}\n```\n\n### Command Implementation\n\n```csharp\npublic class MainViewModel : NotifiableObject\n{\n    private string _searchText = string.Empty;\n    private bool _isSearching;\n\n    public string SearchText\n    {\n        get =\u003e _searchText;\n        set =\u003e SetProperty(ref _searchText, value);\n    }\n\n    public bool IsSearching\n    {\n        get =\u003e _isSearching;\n        private set =\u003e SetProperty(ref _isSearching, value);\n    }\n\n    // Synchronous command\n    public IActionCommand ClearCommand { get; }\n\n    // Asynchronous command with parameter\n    public IAsyncActionCommand\u003cstring\u003e SearchCommand { get; }\n\n    public MainViewModel()\n    {\n        ClearCommand = new ActionCommand(\n            execute: () =\u003e SearchText = string.Empty,\n            canExecute: () =\u003e !string.IsNullOrEmpty(SearchText)\n        );\n\n        SearchCommand = new AsyncActionCommand\u003cstring\u003e(\n            execute: async (query) =\u003e await PerformSearchAsync(query),\n            canExecute: (query) =\u003e !IsSearching \u0026\u0026 !string.IsNullOrWhiteSpace(query),\n            onException: (ex) =\u003e Console.WriteLine($\"Search failed: {ex.Message}\")\n        );\n\n        // Update command states when properties change\n        PropertyChanged += (s, e) =\u003e\n        {\n            if (e.PropertyName == nameof(SearchText))\n            {\n                ClearCommand.RaiseCanExecuteChanged();\n                SearchCommand.RaiseCanExecuteChanged();\n            }\n        };\n    }\n\n    private async Task PerformSearchAsync(string query)\n    {\n        IsSearching = true;\n        try\n        {\n            // Simulate async search\n            await Task.Delay(2000);\n            // Perform actual search logic here\n        }\n        finally\n        {\n            IsSearching = false;\n        }\n    }\n}\n```\n\n## 🎛️ Advanced Scenarios\n\n### Custom Event Arguments\n\nThe library provides strongly-typed event arguments that carry additional information:\n\n```csharp\npublic void HandlePropertyChanging(object sender, PropertyChangingEventArgs e)\n{\n    if (e is PropertyChangingEventArgs\u003cstring\u003e stringArgs)\n    {\n        string oldValue = stringArgs.OldValue;\n        string propertyName = stringArgs.PropertyName;\n        Console.WriteLine($\"Property '{propertyName}' changing from '{oldValue}'\");\n    }\n}\n\npublic void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)\n{\n    if (e is PropertyChangedEventArgs\u003cstring\u003e stringArgs)\n    {\n        string newValue = stringArgs.NewValue;\n        string propertyName = stringArgs.PropertyName;\n        Console.WriteLine($\"Property '{propertyName}' changed to '{newValue}'\");\n    }\n}\n```\n\n### Collection Change Handling\n\n```csharp\npublic void HandleCollectionChanges()\n{\n    var collection = new ObservableStringCollection();\n\n    collection.CollectionChanging += (sender, e) =\u003e\n    {\n        Console.WriteLine($\"Collection about to {e.Action}\");\n        if (e is CollectionChangingEventArgs\u003cstring\u003e typedArgs)\n        {\n            Console.WriteLine($\"Item: {typedArgs.Item}\");\n        }\n    };\n\n    collection.CollectionChanged += (sender, e) =\u003e\n    {\n        Console.WriteLine($\"Collection {e.Action} completed\");\n        if (e is CollectionChangedEventArgs\u003cstring\u003e typedArgs)\n        {\n            Console.WriteLine($\"Item: {typedArgs.Item}\");\n        }\n    };\n}\n```\n\n### Performance Considerations\n\n#### Attribute Caching\n\nThe library optimizes reflection-based attribute processing by caching property relationships:\n\n```csharp\n// Attributes are processed once during object construction\n// Subsequent property changes use cached information for optimal performance\npublic class OptimizedViewModel : NotifiableObject\n{\n    // Attribute metadata is cached automatically\n    [NotifyChanged(nameof(DependentProperty1), nameof(DependentProperty2))]\n    public string SourceProperty { get; set; }\n\n    public string DependentProperty1 =\u003e $\"Dependent on: {SourceProperty}\";\n    public string DependentProperty2 =\u003e $\"Also dependent on: {SourceProperty}\";\n}\n```\n\n#### Memory Management\n\nThe library is designed to minimize memory allocations:\n\n- Event argument objects are reused where possible\n- Collection change notifications use efficient data structures\n- Property change detection uses optimized equality comparisons\n\n## 📔 API Reference\n\n### Core Namespaces\n\n- **`BB84.Notifications`** - Main classes and implementations\n- **`BB84.Notifications.Interfaces`** - Core interfaces\n- **`BB84.Notifications.Components`** - Event argument classes and delegates\n- **`BB84.Notifications.Commands`** - Command pattern implementations\n- **`BB84.Notifications.Attributes`** - Notification attributes\n- **`BB84.Notifications.Extensions`** - Utility extensions\n\n### Key Classes\n\n| Class                   | Description                                | Key Features                             |\n| ----------------------- | ------------------------------------------ | ---------------------------------------- |\n| `NotifiableProperty\u003cT\u003e` | Generic property with change notifications | Implicit operators, typed events         |\n| `ReversibleProperty\u003cT\u003e` | Property with value history                | Undo/redo, configurable history          |\n| `NotifiableObject`      | Base class for notifiable objects          | SetProperty, attribute support           |\n| `NotifiableCollection`  | Base class for notifiable collections      | Before/after events, typed notifications |\n| `ValidatableObject`     | Base class with validation                 | Data annotations, error notifications    |\n| `ActionCommand`         | Synchronous command implementation         | CanExecute logic, parameter support      |\n| `AsyncActionCommand`    | Asynchronous command implementation        | Exception handling, cancellation         |\n\n### Event Types\n\n| Event Handler                    | Description                | Event Args                       |\n| -------------------------------- | -------------------------- | -------------------------------- |\n| `PropertyChangingEventHandler`   | Property about to change   | `PropertyChangingEventArgs\u003cT\u003e`   |\n| `PropertyChangedEventHandler`    | Property has changed       | `PropertyChangedEventArgs\u003cT\u003e`    |\n| `CollectionChangingEventHandler` | Collection about to change | `CollectionChangingEventArgs\u003cT\u003e` |\n| `CollectionChangedEventHandler`  | Collection has changed     | `CollectionChangedEventArgs\u003cT\u003e`  |\n\n## 🧰 Examples\n\n### Complete MVVM Example\n\n```csharp\n// Model\npublic class Person\n{\n    public string FirstName { get; set; } = string.Empty;\n    public string LastName { get; set; } = string.Empty;\n    public DateTime BirthDate { get; set; }\n}\n\n// ViewModel\npublic class PersonViewModel : ValidatableObject\n{\n    private string _firstName = string.Empty;\n    private string _lastName = string.Empty;\n    private DateTime _birthDate = DateTime.Today;\n\n    [Required(ErrorMessage = \"First name is required\")]\n    [NotifyChanged(nameof(FullName))]\n    public string FirstName\n    {\n        get =\u003e _firstName;\n        set =\u003e SetValidatedProperty(ref _firstName, value);\n    }\n\n    [Required(ErrorMessage = \"Last name is required\")]\n    [NotifyChanged(nameof(FullName))]\n    public string LastName\n    {\n        get =\u003e _lastName;\n        set =\u003e SetValidatedProperty(ref _lastName, value);\n    }\n\n    public DateTime BirthDate\n    {\n        get =\u003e _birthDate;\n        set =\u003e SetProperty(ref _birthDate, value);\n    }\n\n    public string FullName =\u003e $\"{FirstName} {LastName}\".Trim();\n\n    public IActionCommand SaveCommand { get; }\n    public IActionCommand ResetCommand { get; }\n\n    public PersonViewModel()\n    {\n        SaveCommand = new ActionCommand(\n            execute: Save,\n            canExecute: () =\u003e IsValid \u0026\u0026 !HasErrors\n        );\n\n        ResetCommand = new ActionCommand(Reset);\n\n        // Update command states when validation changes\n        ErrorsChanged += (s, e) =\u003e SaveCommand.RaiseCanExecuteChanged();\n    }\n\n    private void Save()\n    {\n        if (!IsValid) return;\n\n        var person = new Person\n        {\n            FirstName = FirstName,\n            LastName = LastName,\n            BirthDate = BirthDate\n        };\n\n        // Save logic here\n        Console.WriteLine($\"Saving: {person.FirstName} {person.LastName}\");\n    }\n\n    private void Reset()\n    {\n        FirstName = string.Empty;\n        LastName = string.Empty;\n        BirthDate = DateTime.Today;\n    }\n}\n```\n\n### Reactive Programming Pattern\n\n```csharp\npublic class ReactiveViewModel : NotifiableObject\n{\n    private readonly INotifiableProperty\u003cstring\u003e _searchTerm = new NotifiableProperty\u003cstring\u003e(string.Empty);\n    private readonly INotifiableProperty\u003cList\u003cstring\u003e\u003e _results = new NotifiableProperty\u003cList\u003cstring\u003e\u003e(new List\u003cstring\u003e());\n    private readonly INotifiableProperty\u003cbool\u003e _isLoading = new NotifiableProperty\u003cbool\u003e(false);\n\n    public INotifiableProperty\u003cstring\u003e SearchTerm =\u003e _searchTerm;\n    public INotifiableProperty\u003cList\u003cstring\u003e\u003e Results =\u003e _results;\n    public INotifiableProperty\u003cbool\u003e IsLoading =\u003e _isLoading;\n\n    public ReactiveViewModel()\n    {\n        // React to search term changes\n        _searchTerm.PropertyChanged += async (s, e) =\u003e\n        {\n            if (e is PropertyChangedEventArgs\u003cstring\u003e args \u0026\u0026 !string.IsNullOrEmpty(args.NewValue))\n            {\n                await PerformSearchAsync(args.NewValue);\n            }\n        };\n    }\n\n    private async Task PerformSearchAsync(string term)\n    {\n        _isLoading.Value = true;\n        try\n        {\n            // Simulate async search\n            await Task.Delay(500);\n            var results = Enumerable.Range(1, 10)\n                .Select(i =\u003e $\"{term} Result {i}\")\n                .ToList();\n\n            _results.Value = results;\n        }\n        finally\n        {\n            _isLoading.Value = false;\n        }\n    }\n}\n```\n\n## 💎 Testing\n\nThe library includes comprehensive unit tests covering all major functionality. The test suite uses MSTest framework and covers:\n\n- Property change notifications\n- Collection change events\n- Command execution and cancellation\n- Validation scenarios\n- Attribute-based notifications\n- Error handling and edge cases\n\n### Running Tests\n\n```bash\ndotnet test\n```\n\n### Test Coverage Areas\n\n- **Property Notifications**: All notification scenarios and event argument types\n- **Collection Operations**: Add, remove, clear, and batch operations\n- **Validation**: Data annotation validation and error propagation\n- **Commands**: Synchronous and asynchronous execution with various parameters\n- **Attributes**: Notification chaining and dependency tracking\n- **Edge Cases**: Null values, default values, threading scenarios\n\n## 🤝 Contributing\n\nContributions are welcome! Please follow these guidelines:\n\n1. **Fork the repository** and create a feature branch\n2. **Write tests** for new functionality\n3. **Follow coding standards** and maintain consistent style\n4. **Update documentation** for public API changes\n5. **Submit a pull request** with a clear description\n\n### Development Setup\n\n1. Clone the repository\n2. Open the solution in Visual Studio 2022 or VS Code\n3. Restore NuGet packages\n4. Build and run tests\n\n### Code Standards\n\n- Use C# 13.0 language features where appropriate\n- Follow Microsoft naming conventions\n- Include XML documentation for public APIs\n- Maintain high test coverage (\u003e90%)\n\n## ⚖️ License\n\nThis project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.\n\n## 📚 Support and Resources\n\n- **API Documentation**: [https://bobobass84.github.io/BB84.Notifications/](https://bobobass84.github.io/BB84.Notifications/)\n- **NuGet Package**: [https://www.nuget.org/packages/BB84.Notifications](https://www.nuget.org/packages/BB84.Notifications)\n- **GitHub Repository**: [https://github.com/BoBoBaSs84/BB84.Notifications](https://github.com/BoBoBaSs84/BB84.Notifications)\n- **Issues \u0026 Feature Requests**: [GitHub Issues](https://github.com/BoBoBaSs84/BB84.Notifications/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbobobass84%2Fbb84.notifications","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbobobass84%2Fbb84.notifications","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbobobass84%2Fbb84.notifications/lists"}