Monumend.Net
2.0.3
dotnet add package Monumend.Net --version 2.0.3
NuGet\Install-Package Monumend.Net -Version 2.0.3
<PackageReference Include="Monumend.Net" Version="2.0.3" />
<PackageVersion Include="Monumend.Net" Version="2.0.3" />
<PackageReference Include="Monumend.Net" />
paket add Monumend.Net --version 2.0.3
#r "nuget: Monumend.Net, 2.0.3"
#:package Monumend.Net@2.0.3
#addin nuget:?package=Monumend.Net&version=2.0.3
#tool nuget:?package=Monumend.Net&version=2.0.3
Monumend.Net
A powerful .NET Standard 2.0 library providing configurable INotifyPropertyChanged implementation with extended event arguments that track both old and new values.
Features
? Configurable Behavior - Switch between API and Client modes
?? Extended Event Args - Track old and new property values
?? Thread-Safe - Safe event handler capture using Interlocked
?? Type-Safe - Generic implementation with compile-time type checking
? Performance Optimized - Conditional equality checking and event raising
?? Framework Agnostic - Works with WPF, Xamarin, MAUI, Avalonia, Blazor
Installation
dotnet add package Monumend.Net
Or via Package Manager Console:
Install-Package Monumend.Net
Quick Start
1. Configure on Startup
// In your application startup (e.g., App.xaml.cs for WPF)
using Monumend.Net.Models;
// For client applications (UI)
NotifyPropertyChangedExtensions.Configure(
NotifyPropertyChangedExtensions.ClientConfiguration);
// For API/server applications
NotifyPropertyChangedExtensions.Configure(
NotifyPropertyChangedExtensions.ApiConfiguration);
2. Create ViewModels
using Monumend.Net.Models;
public class PersonViewModel : VMO
{
private string? _name;
private int _age;
public string? Name
{
get => _name;
set => Set(value, ref _name); // Clean and intuitive!
}
public int Age
{
get => _age;
set
{
bool? changed = Set(value, ref _age);
if (changed == true)
{
// Age changed - notify dependent property with automatic property name capture
OnPropertyChanged(Age < 18, value >= 18); // propertyName is automatically captured
}
}
}
public bool IsAdult => Age >= 18;
}
Tip: If your application targets .NET 8+, you can use the C# 13
fieldkeyword for even cleaner code. See the "Using C# 13 Field Keyword" section below.
3. Subscribe to Changes
var person = new PersonViewModel();
// Standard PropertyChanged handler
person.PropertyChanged += (sender, e) =>
{
Console.WriteLine($"Property {e.PropertyName} changed");
};
// Extended handler with old/new values
person.PropertyChanged += (sender, e) =>
{
if (e is PropertyChangedExtendedEventArgs<string> args)
{
Console.WriteLine($"Name changed from '{args.OldValue}' to '{args.NewValue}'");
}
};
person.Name = "John"; // Triggers both handlers
Advanced Usage
Custom Configuration
var customConfig = new NotifyPropertyChangedExtensions.Configuration
{
RaiseEvents = true,
CheckForValueChange = true
};
NotifyPropertyChangedExtensions.Configure(customConfig);
Value Change Detection
// Traditional backing field approach
private int _score;
public int Score
{
get => _score;
set
{
bool? changed = Set(value, ref _score);
if (changed == true)
{
// Score actually changed - recalculate dependent properties
RecalculateRanking();
OnPropertyChanged(oldRank, newRank); // propertyName automatically captured
}
}
}
// Modern C# 13 field keyword approach
public int Score
{
get;
set
{
bool? changed = Set(value, ref field);
if (changed == true)
{
// Score actually changed
RecalculateRanking();
}
}
}
Triggering Dependent Property Changes
public class PersonViewModel : VMO
{
private string? _firstName;
private string? _lastName;
public string? FirstName
{
get => _firstName;
set
{
if (Set(value, ref _firstName) == true)
{
// When FirstName changes, notify that FullName also changed
NotifyPropertyChanged(nameof(FullName));
}
}
}
public string? LastName
{
get => _lastName;
set
{
if (Set(value, ref _lastName) == true)
{
NotifyPropertyChanged(nameof(FullName));
}
}
}
public string FullName => $"{FirstName} {LastName}";
}
// Or using C# 13 field keyword
public class ModernPersonViewModel : VMO
{
public string? FirstName
{
get;
set
{
if (Set(value, ref field) == true)
{
NotifyPropertyChanged(nameof(FullName));
}
}
}
public string? LastName
{
get;
set
{
if (Set(value, ref field) == true)
{
NotifyPropertyChanged(nameof(FullName));
}
}
}
// Important: Use property names, not backing fields
public string FullName => $"{FirstName} {LastName}";
}
Manual Property Notification
public class PersonViewModel : VMO
{
private string? _firstName;
private string? _lastName;
public string? FirstName
{
get => _firstName;
set
{
if (Set(value, ref _firstName) == true)
{
// Option 1: Simple notification (v2.0.2+)
OnPropertyChanged(nameof(FullName));
// Option 2: With old/new values (v2.0.1+)
// OnPropertyChanged(oldFullName, newFullName, nameof(FullName));
}
}
}
public string? LastName
{
get => _lastName;
set
{
if (Set(value, ref _lastName) == true)
{
// Simple notification when you don't need value tracking
OnPropertyChanged(nameof(FullName));
}
}
}
public string FullName => $"{FirstName} {LastName}".Trim();
// Batch refresh multiple computed properties
public void RefreshCalculatedProperties()
{
OnPropertyChanged(nameof(FullName));
OnPropertyChanged(nameof(DisplayName));
OnPropertyChanged(nameof(Summary));
}
}
When to use each OnPropertyChanged overload:
Version History
- v2.0.2 - Added protected OnPropertyChanged(string) overload for simple notifications
- v2.0.1 - Improved OnPropertyChanged API with CallerMemberName support
- v2.0.0 - Refactored API with improved parameter order
Set(value, ref backingField) - v1.1.0 - Cleaned up API, removed redundant methods
- v1.0.1 - Added package icon
- v1.0.0 - Initial release
| 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 was computed. 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 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 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| 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. |
-
.NETStandard 2.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.
v2.0.3 - Added comprehensive test suite with 49 tests providing 100% API coverage. Includes configuration tests, extended event args tests, client/API mode tests, VMO base class tests, thread safety tests, and integration tests. Enabled automated testing in CI/CD pipeline with code coverage reporting. All tests passing, execution under 2 seconds.