BB84.Notifications
4.3.604
dotnet add package BB84.Notifications --version 4.3.604
NuGet\Install-Package BB84.Notifications -Version 4.3.604
<PackageReference Include="BB84.Notifications" Version="4.3.604" />
<PackageVersion Include="BB84.Notifications" Version="4.3.604" />
<PackageReference Include="BB84.Notifications" />
paket add BB84.Notifications --version 4.3.604
#r "nuget: BB84.Notifications, 4.3.604"
#:package BB84.Notifications@4.3.604
#addin nuget:?package=BB84.Notifications&version=4.3.604
#tool nuget:?package=BB84.Notifications&version=4.3.604
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
INotifyPropertyChangedandINotifyPropertyChangingwith 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
INotifyDataErrorInfoimplementation - 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
INotifyCollectionChangedpattern
⏪ 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
INotifyDataErrorInfoimplementation- Property-level and object-level validation
- Error change notifications
⚡ Command Pattern Implementation
- Synchronous
ActionCommandwith optionalCanExecutelogic - Asynchronous
AsyncActionCommandwith exception handling - Generic and non-generic command variants
- Automatic
CanExecuteChangednotifications
📦 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.7.2 - Legacy Windows applications
- .NET Framework 4.8.1 - Latest .NET Framework
- .NET 8.0 - Modern .NET with latest features
- .NET 10.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>or a customIEqualityComparer<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>
{
int Count { get; }
int Index { get; }
bool HasNextValue { get; }
bool HasPreviousValue { get; }
void NextValue();
void PreviousValue();
void Clear();
IReadOnlyList<T> Snapshot();
}
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
- Configurable overflow strategy (
EvictOldest,EvictNewest,Throw) Clear()resets history to current value only, preserving event subscriptionsSnapshot()returns a read-only view of all history entries- 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:
SetPropertymethod for automatic change detection (default or customIEqualityComparer<T>)- 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
NotifiableCollection<T>
Ready-to-use generic observable list that wraps List<T>.
Key Features:
- Implements
IList<T>andIReadOnlyList<T> - Full
CollectionChanging/CollectionChangednotifications on all mutating operations (Add,Insert,Remove,RemoveAt,Clear, indexer set) - Constructor overload accepting an existing
IEnumerable<T> - Integrates naturally with LINQ and data-binding frameworks
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
INotifyDataErrorInfoimplementation- 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 with optionalCancellationTokensupport,Cancel()method, and bindableCancelCommand
🧰 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<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}");
}
}
Custom Equality Comparer
Supply a custom IEqualityComparer<T> to control when a change is considered meaningful:
// NotifiableProperty<T> — case-insensitive string comparison
INotifiableProperty<string> tag = new NotifiableProperty<string>("hello", StringComparer.OrdinalIgnoreCase);
tag.Value = "HELLO"; // no event raised — considered equal
// NotifiableObject — per-property comparer in SetProperty
public class DocumentViewModel : NotifiableObject
{
private string _title = string.Empty;
public string Title
{
get => _title;
// case-insensitive: "Hello" and "hello" are the same title
set => SetProperty(ref _title, value, StringComparer.OrdinalIgnoreCase);
}
}
// ValidatableObject — same pattern for SetPropertyAndValidate
public class UserViewModel : ValidatableObject
{
private string _email = string.Empty;
[Required, EmailAddress]
public string Email
{
get => _email;
set => SetPropertyAndValidate(ref _email, value, StringComparer.OrdinalIgnoreCase);
}
}
Reversible Properties with History
public class EditableDocument
{
private const int HistorySize = 20;
// Default overflow: EvictOldest (circular buffer)
public IReversibleProperty<string> Content { get; set; } =
new ReversibleProperty<string>("Initial Content", HistorySize);
public void Undo()
{
if (Content.HasPreviousValue)
Content.PreviousValue();
}
public void Redo()
{
if (Content.HasNextValue)
Content.NextValue();
}
public void ResetHistory()
{
// Clears history to just the current value; all event subscriptions are preserved
Content.Clear();
}
public IReadOnlyList<string> GetHistory() => Content.Snapshot();
}
// Throw strategy — useful when a hard cap must not be silently exceeded
var strict = new ReversibleProperty<string>("v1", size: 3, overflow: OverflowStrategy.Throw);
// EvictNewest strategy — new values are silently discarded when full
var capped = new ReversibleProperty<string>("v1", size: 3, overflow: OverflowStrategy.EvictNewest);
Generic Collection
// Ready-to-use generic observable list
var items = new NotifiableCollection<string>();
items.CollectionChanging += (sender, e) =>
{
Console.WriteLine($"Collection about to {e.Action}");
if (e is CollectionChangingEventArgs<string> typed)
Console.WriteLine($"Item: {typed.Item}");
};
items.CollectionChanged += (sender, e) =>
{
Console.WriteLine($"Collection {e.Action} completed");
if (e is CollectionChangedEventArgs<string> typed)
Console.WriteLine($"Item: {typed.Item}");
};
items.Add("Hello"); // raises CollectionChanging + CollectionChanged
items.Insert(0, "World"); // raises CollectionChanging + CollectionChanged
items.Remove("Hello"); // raises CollectionChanging + CollectionChanged
items.Clear(); // raises CollectionChanging + CollectionChanged
Custom Collection (Abstract Base)
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)
);
// Cancellable async command — receives a CancellationToken automatically
SearchCommand = new AsyncActionCommand<string>(
execute: async (query, ct) => await PerformSearchAsync(query, ct),
canExecute: (query) => !string.IsNullOrWhiteSpace(query),
action: (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, CancellationToken ct)
{
IsSearching = true;
try
{
await Task.Delay(2000, ct);
// Perform actual search logic here
}
finally
{
IsSearching = false;
}
}
}
Cancelling an Async Command
// Non-generic variant
IAsyncActionCommand loadCommand = new AsyncActionCommand(
execute: async ct => await LoadDataAsync(ct),
canExecute: () => true
);
// Bind CancelCommand directly in the UI (e.g., a Cancel button)
// CancelCommand.CanExecute() returns true only while loadCommand is executing
IActionCommand cancelButton = loadCommand.CancelCommand;
// Programmatic cancellation
loadCommand.Cancel();
// Check whether cancellation was requested
bool wasCancelled = loadCommand.IsCancellationRequested;
// Generic variant with parameter
IAsyncActionCommand<string> searchCommand = new AsyncActionCommand<string>(
execute: async (query, ct) => await SearchAsync(query, ct)
);
await searchCommand.ExecuteAsync("hello"); // uses internal CTS
await searchCommand.ExecuteAsync("hello", myToken); // linked with external token
Weak-Reference Event Subscriptions
// Prevents the long-lived model from keeping a short-lived subscriber alive.
// The subscription is automatically pruned when the subscriber is collected.
INotifyPropertyChanged model = new PersonViewModel();
// Subscribe — handler stored as a weak reference
IDisposable token = model.WeakSubscribe((s, e) =>
Console.WriteLine($"{e.PropertyName} changed"));
// Works identically for INotifyPropertyChanging
IDisposable token2 = ((INotifyPropertyChanging)model).WeakSubscribe((s, e) =>
Console.WriteLine($"{e.PropertyName} changing"));
// Dispose the token to remove the subscription eagerly (optional)
token.Dispose();
🎛️ 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
WeakSubscribeextension methods prevent long-lived sources from keeping short-lived subscribers alive
📔 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 (TaskExtensions,WeakEventExtensions)
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 |
NotifiableCollection<T> |
Generic ready-to-use observable list | IList<T>, IReadOnlyList<T>, full 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 was computed. 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 is compatible. 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 was computed. net463 was computed. net47 was computed. net471 was computed. net472 is compatible. 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.7.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)
-
net10.0
- System.ComponentModel.Annotations (>= 5.0.0)
-
net8.0
- No dependencies.
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 |
|---|---|---|
| 4.3.604 | 44 | 6/4/2026 |
| 4.2.417 | 564 | 4/17/2026 |
| 4.1.122 | 733 | 1/22/2026 |
| 4.0.1205 | 423 | 12/5/2025 |
| 3.6.1026 | 533 | 10/26/2025 |
| 3.6.904 | 577 | 9/4/2025 |
| 3.6.531 | 712 | 5/31/2025 |
| 3.6.409 | 947 | 4/9/2025 |
| 3.6.322 | 397 | 3/22/2025 |
| 3.6.319 | 329 | 3/19/2025 |
| 3.6.130 | 648 | 1/30/2025 |
| 3.5.1216 | 854 | 12/16/2024 |
| 3.5.916 | 1,216 | 9/16/2024 |
| 3.4.715 | 985 | 7/15/2024 |
| 3.4.713 | 255 | 7/13/2024 |
| 3.3.616 | 482 | 6/16/2024 |
| 3.3.606 | 305 | 6/6/2024 |
| 3.2.603 | 271 | 6/3/2024 |
| 3.2.520 | 446 | 5/20/2024 |
| 3.2.421 | 391 | 4/21/2024 |