A lightweight .NET library providing observable and trackable object models with support for INotifyPropertyChanged, reactive patterns with IObservable<T>, and comprehensive change tracking capabilities.
Install via NuGet Package Manager:
dotnet add package ObservableModel- Observable Objects: Automatic
INotifyPropertyChangedimplementation - Change Tracking: Track changes to properties with original value preservation
- Reactive Extensions: Built-in support for
IObservable<T>patterns - Observable Collections: Feature-rich observable lists with change notifications
- Property Dependencies: Automatic dependent property notifications
- Deferred Changes: Batch property change notifications
- MVVM Support: Perfect for WPF, Avalonia, and other MVVM frameworks
Create objects with automatic property change notifications:
public abstract class Person : ObservableObject
{
[ObservableProperty]
public virtual string Name { get; set; }
[ObservableProperty]
public virtual int Age { get; set; }
}
// Create an instance
var person = Observable<Person>.Create(x =>
{
x.Name = "John Doe";
x.Age = 30;
});
// Subscribe to property changes
person.PropertyChanged += (sender, e) =>
{
Console.WriteLine($"Property {e.PropertyName} changed");
};
person.Name = "Jane Doe"; // Triggers PropertyChanged eventvar people = new ObservableList<Person>();
// Subscribe to collection changes
people.CollectionChanged += (sender, e) =>
{
Console.WriteLine($"Collection changed: {e.Action}");
};
people.Add(Observable<Person>.Create(x =>
{
x.Name = "Alice";
x.Age = 25;
}));
// Sort with persistent sorting
people.SortBy(x => x.Age, persist: true);
// Aggregate values reactively
var averageAge = people.Aggregate(0.0, (sum, p) => sum + p.Age / people.Count);Use LINQ-style operators with observables:
var person = Observable<Person>.Create();
// Create a reactive property
var nameObservable = person.Observe(x => x.Name)
.DistinctUntilChanged()
.Select(name => name.ToUpper())
.ToProperty();
person.Name = "john"; // nameObservable.Value becomes "JOHN"Track changes to objects with original value preservation:
public abstract class Employee : Trackable
{
[TrackableProperty]
public virtual string Name { get; set; }
[TrackableProperty]
public virtual decimal Salary { get; set; }
}
var employee = Trackable<Employee>.Create(x =>
{
x.Name = "John";
x.Salary = 50000m;
});
employee.Salary = 55000m;
Console.WriteLine(employee.IsChanged); // True
Console.WriteLine(employee.GetOriginalValue<decimal>(nameof(Employee.Salary))); // 50000
// Revert changes
employee.RejectChanges();
Console.WriteLine(employee.Salary); // 50000
// Or accept changes
employee.Salary = 60000m;
employee.AcceptChanges();
Console.WriteLine(employee.IsChanged); // Falsevar team = new TrackableList<Employee>();
team.Reset(employees, initialize: true);
team[0].Salary = 65000m;
// Get changes
var changes = team.GetChanges();
foreach (var change in changes)
{
Console.WriteLine($"{change.Type}: {change.Item}");
}
// Revert all changes
team.RejectChanges();Automatically notify dependent properties:
public abstract class Person : ObservableObject
{
[ObservableProperty]
public virtual string FirstName { get; set; }
[ObservableProperty]
public virtual string LastName { get; set; }
[ObservablePropertyDependency(nameof(FirstName), nameof(LastName))]
public string FullName => $"{FirstName} {LastName}";
}
var person = Observable<Person>.Create();
person.PropertyChanged += (s, e) => Console.WriteLine(e.PropertyName);
person.FirstName = "John"; // Triggers: FirstName, FullNameBatch multiple property changes into a single notification:
var person = Observable<Person>.Create();
using (person.DeferPropertyChanges())
{
person.FirstName = "John";
person.LastName = "Doe";
person.Age = 30;
// No PropertyChanged events yet
}
// All PropertyChanged events fire herevar firstName = new BehaviorSubject<string>("John");
var lastName = new BehaviorSubject<string>("Doe");
var fullName = Observable.CombineLatest(
firstName,
lastName,
(first, last) => $"{first} {last}"
);
fullName.Subscribe(name => Console.WriteLine(name)); // "John Doe"
lastName.OnNext("Smith"); // "John Smith"var numbers = new ObservableList<int> { 1, 2, 3, 4, 5 };
var sum = numbers.Aggregate(0, (total, n) => total + n);
sum.PropertyChanges.Subscribe(change =>
{
Console.WriteLine($"Sum changed to: {sum.Value}");
});
numbers.Add(6); // Sum automatically updates to 21Prevent memory leaks with weak subscriptions:
observable.SubscribeWeak(observer);Select,Where,DistinctUntilChangedTake,SkipCombineLatestObserveOn(dispatcher support)FirstAsync
- .NET 9.0 or later
This project is licensed under the MIT License.
Contributions are welcome! Please feel free to submit issues or pull requests.