BB84.Notifications
3.6.904
dotnet add package BB84.Notifications --version 3.6.904
NuGet\Install-Package BB84.Notifications -Version 3.6.904
<PackageReference Include="BB84.Notifications" Version="3.6.904" />
<PackageVersion Include="BB84.Notifications" Version="3.6.904" />
<PackageReference Include="BB84.Notifications" />
paket add BB84.Notifications --version 3.6.904
#r "nuget: BB84.Notifications, 3.6.904"
#:package BB84.Notifications@3.6.904
#addin nuget:?package=BB84.Notifications&version=3.6.904
#tool nuget:?package=BB84.Notifications&version=3.6.904
BB84.Notifications
🔎 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
andINotifyPropertyChanging
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 optionalCanExecute
logic - Asynchronous
AsyncActionCommand
with exception handling - Generic and non-generic command variants
- Automatic
CanExecuteChanged
notifications
📦 Target Frameworks
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
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 commandsAsyncActionCommand
/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 implementationsBB84.Notifications.Interfaces
- Core interfacesBB84.Notifications.Components
- Event argument classes and delegatesBB84.Notifications.Commands
- Command pattern implementationsBB84.Notifications.Attributes
- Notification attributesBB84.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:
- Fork the repository and create a feature branch
- Write tests for new functionality
- Follow coding standards and maintain consistent style
- Update documentation for public API changes
- Submit a pull request with a clear description
Development Setup
- Clone the repository
- Open the solution in Visual Studio 2022 or VS Code
- Restore NuGet packages
- 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
- API Documentation: https://bobobass84.github.io/BB84.Notifications/
- NuGet Package: https://www.nuget.org/packages/BB84.Notifications
- GitHub Repository: https://github.com/BoBoBaSs84/BB84.Notifications
- Issues & Feature Requests: GitHub Issues
Product | Versions 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. |
-
.NETFramework 4.6.2
- System.ComponentModel.Annotations (>= 5.0.0)
-
.NETFramework 4.8.1
- System.ComponentModel.Annotations (>= 5.0.0)
-
.NETStandard 2.0
- System.ComponentModel.Annotations (>= 5.0.0)
-
.NETStandard 2.1
- System.ComponentModel.Annotations (>= 5.0.0)
-
net8.0
- System.ComponentModel.Annotations (>= 5.0.0)
-
net9.0
- System.ComponentModel.Annotations (>= 5.0.0)
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 |