ObjectTracker 1.0.2
dotnet add package ObjectTracker --version 1.0.2
NuGet\Install-Package ObjectTracker -Version 1.0.2
<PackageReference Include="ObjectTracker" Version="1.0.2" />
<PackageVersion Include="ObjectTracker" Version="1.0.2" />
<PackageReference Include="ObjectTracker" />
paket add ObjectTracker --version 1.0.2
#r "nuget: ObjectTracker, 1.0.2"
#:package ObjectTracker@1.0.2
#addin nuget:?package=ObjectTracker&version=1.0.2
#tool nuget:?package=ObjectTracker&version=1.0.2
ObjectTracker
A lightweight .NET library for tracking objects and detecting changes. ObjectTracker uses a builder pattern to create reusable trackers that capture snapshots of your objects and compare them to detect what has changed.
Features
- Reusable Trackers - Configure once, track multiple objects for optimal performance
- Simple Fluent API - Chain configuration methods for intuitive setup
- Property Tracking - Track individual properties and detect changes
- Collection Tracking - Track collections with item-level change detection (additions, removals, modifications)
- Nested Tracking - Recursively track properties of collection items
- Custom Difference Types - Define your own difference representations
- Contextual Differences - Access the target object in difference factories for rich context
- Type-Safe - Fully generic implementation with compile-time type safety
- Optimized Performance - Efficient collection matching algorithms with O(n) complexity
Installation
dotnet add package ObjectTracker
Or via NuGet Package Manager:
Install-Package ObjectTracker
Quick Start
using ObjectTracker.Builders;
// 1. Build a reusable tracker (configure once)
var trackerBuilder = new TrackerBuilder<Person, string>()
.TrackProperty(
selector: p => p.Name,
differenceFactory: (person, oldValue, newValue) =>
$"Name changed from {oldValue} to {newValue}")
.TrackProperty(
selector: p => p.Age,
differenceFactory: (person, oldValue, newValue) =>
$"Age changed from {oldValue} to {newValue}");
var tracker = trackerBuilder.Build();
// 2. Track an object (captures snapshot)
var person = new Person { Name = "John", Age = 30 };
var trackedPerson = tracker.Track(person);
// 3. Modify the object
person.Name = "Jane";
person.Age = 31;
// 4. Get the differences
var differences = trackedPerson.GetDifferences();
foreach (var diff in differences)
{
Console.WriteLine(diff);
}
// Output:
// Name changed from John to Jane
// Age changed from 30 to 31
How It Works
- Build a Tracker - Use
TrackerBuilderto configure what properties and collections to track. Build it once to create a reusableTrackerinstance. - Track Objects - Call
tracker.Track(object)to capture a snapshot. Returns aTrackedObjectwith the frozen state. - Make Changes - Modify your object as needed.
- Compare - Call
trackedObject.GetDifferences()to detect changes, ortrackedObject.Compare(anotherObject)to compare against a different object.
The tracker is reusable - configure once and track thousands of objects efficiently. Each tracked object maintains its own immutable snapshot.
Why Use TrackerBuilder?
Performance Optimization: The builder pattern separates configuration from state capture. Build your tracker once and reuse it:
var tracker = new TrackerBuilder<Person, Difference>()
.TrackProperty(p => p.Name, (p, old, newVal) => new Difference("Name", old, newVal))
.Build();
// Track multiple objects efficiently - no need to rebuild the configuration
var tracked1 = tracker.Track(person1);
var tracked2 = tracker.Track(person2);
var tracked3 = tracker.Track(person3);
Tracking Properties
Track any property using a selector and a factory function. The factory receives the target object for contextual information:
var tracker = new TrackerBuilder<Person, Difference>()
.TrackProperty(
selector: p => p.Email,
differenceFactory: (person, oldEmail, newEmail) =>
new Difference($"Email changed for {person.Id}", oldEmail, newEmail))
.Build();
var trackedPerson = tracker.Track(person);
person.Email = "newemail@example.com";
var differences = trackedPerson.GetDifferences();
You can track computed values:
var tracker = new TrackerBuilder<Person, Difference>()
.TrackProperty(
selector: p => p.Name.Length,
differenceFactory: (person, oldLength, newLength) =>
new Difference("NameLength", oldLength, newLength))
.Build();
Tracking Collections
Track collections of items with add/remove/modify detection:
var tracker = new TrackerBuilder<ShoppingCart, string>()
.TrackCollection(
itemsSelector: c => c.Items,
matchingPredicate: (item1, item2) => item1.Id == item2.Id,
addedFactory: (src, tgt, item) => $"Added: {item.Name}",
removedFactory: (src, tgt, item) => $"Removed: {item.Name}",
configureItemTracker: itemBuilder => itemBuilder)
.Build();
var cart = new ShoppingCart
{
Items = new List<Product>
{
new Product { Id = 1, Name = "Apple", Price = 1.50m },
new Product { Id = 2, Name = "Banana", Price = 0.80m }
}
};
var trackedCart = tracker.Track(cart);
// Modify the collection
cart.Items.Add(new Product { Id = 3, Name = "Orange", Price = 1.20m });
cart.Items.RemoveAt(0); // Remove Apple
var changes = trackedCart.GetDifferences();
// Changes will contain:
// - "Removed: Apple"
// - "Added: Orange"
Nested Collection Tracking
Track properties of collection items using configureItemTracker:
var tracker = new TrackerBuilder<ShoppingCart, string>()
.TrackCollection(
itemsSelector: c => c.Items,
matchingPredicate: (item1, item2) => item1.Id == item2.Id,
addedFactory: (src, tgt, item) => $"Added: {item.Name}",
removedFactory: (src, tgt, item) => $"Removed: {item.Name}",
configureItemTracker: itemBuilder => itemBuilder
// Track price changes for matched items
.TrackProperty(
selector: p => p.Price,
differenceFactory: (product, oldPrice, newPrice) =>
$"Product {product.Id}: Price changed from {oldPrice:C} to {newPrice:C}")
// Track name changes for matched items
.TrackProperty(
selector: p => p.Name,
differenceFactory: (product, oldName, newName) =>
$"Product {product.Id}: Name changed from '{oldName}' to '{newName}'"))
.Build();
Custom Difference Types
Define your own types to represent differences:
public interface IDifference { }
public class PropertyChanged : IDifference
{
public string PropertyName { get; }
public object? OldValue { get; }
public object? NewValue { get; }
public PropertyChanged(string propertyName, object? oldValue, object? newValue)
{
PropertyName = propertyName;
OldValue = oldValue;
NewValue = newValue;
}
}
public class ItemAdded : IDifference
{
public object Item { get; }
public ItemAdded(object item)
{
Item = item;
}
}
// Use in tracker
var tracker = new TrackerBuilder<Person, IDifference>()
.TrackProperty(
selector: p => p.Name,
differenceFactory: (person, old, newVal) =>
new PropertyChanged("Name", old, newVal))
.Build();
API Reference
TrackerBuilder<TType, TDiff>
Builder for configuring reusable trackers.
Constructor
new TrackerBuilder<TType, TDiff>()
Creates a new tracker builder.
TrackProperty<TValue>(selector, differenceFactory)
Configures tracking for a property or computed value.
Parameters:
selector- Function to select the value to track from the objectdifferenceFactory- Function to create a difference. Receives(targetObject, oldValue, newValue)
Returns: TrackerBuilder<TType, TDiff> (for chaining)
TrackCollection<TItem>(itemsSelector, matchingPredicate, addedFactory, removedFactory, configureItemTracker)
Configures tracking for a collection of items.
Parameters:
itemsSelector- Function to select the collection from the objectmatchingPredicate- Function to match items between source and target collectionsaddedFactory(optional) - Function to create a difference for added items. Receives(source, target, addedItem)removedFactory(optional) - Function to create a difference for removed items. Receives(source, target, removedItem)configureItemTracker- Function to configure tracking for matched items. Receives aTrackerBuilder<TItem, TDiff>
Returns: TrackerBuilder<TType, TDiff> (for chaining)
Build()
Creates a reusable tracker from the configuration.
Returns: ITracker<TType, TDiff>
ITracker<TType, TDiff>
Reusable tracker that can snapshot multiple objects.
Track(source)
Captures a snapshot of the source object.
Parameters:
source- The object to track
Returns: ITrackedObject<TType, TDiff> - An object containing the snapshot and comparison methods
ITrackedObject<TType, TDiff>
A tracked object with a captured snapshot.
Properties
Source- The original object that was tracked
GetDifferences()
Compares the source object against its original snapshot (detects if the source was mutated).
Returns: IReadOnlyCollection<TDiff> - Collection of detected differences
Compare(target)
Compares the original snapshot against a different target object.
Parameters:
target- The object to compare against
Returns: IReadOnlyCollection<TDiff> - Collection of detected differences
Use Cases
- Audit Logging - Track changes to domain objects for compliance
- Undo/Redo Systems - Identify what changed to implement undo functionality
- Data Synchronization - Detect differences before syncing to databases or APIs
- Change Notifications - Generate user-friendly change descriptions with full context
- State Management - Track application state changes in UI frameworks
- Form Validation - Detect which fields have been modified
- API Request Optimization - Only send changed fields in PATCH requests
- Batch Processing - Configure once, efficiently track thousands of objects
Performance
ObjectTracker is optimized for both single-object and high-volume tracking scenarios:
- Reusable Configuration: Build tracker once, reuse for unlimited objects
- Optimized Collection Matching: O(n) algorithm with HashSet-based lookups
- Single Enumeration: Target collections materialized once to avoid repeated iteration
- Early Exit: Matching stops at first match for better average-case performance
- Memory Efficient: Two-tier design (configuration vs state) minimizes per-object overhead
Requirements
- .NET Standard 2.0 or higher
- Compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+, .NET 6+, .NET 7+, .NET 8+
License
MIT License - see LICENSE file for details
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Repository
| 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.
Set GenerateDocumentationFile to true so that nugets will contain documentation comments