Precise object change detection library - Automatically tracks property changes with zero intrusion.
DeltaTrack solves the pain points of object state change detection:
- Automatic Tracking: Just add
[Trackable]or[TrackableField]attributes - Smart Detection: Automatically captures all changes including property assignments, collection add/remove/modify
- Hierarchical Tracking: Supports deep change detection for nested objects and complex collections
- Real-time Feedback: Provides changed field list and change events
- Zero GC Pressure: Uses bitflag (
long) instead ofHashSet<string>for dirty marking, ideal for high-frequency scenarios like game servers
dotnet add package DeltaTrackMethod 1: Using [Trackable] Attribute
After marking the class, all private fields are automatically tracked (class must be partial):
[Trackable]
public partial class Order
{
private string _customerName = ""; // Auto-tracked
private decimal _amount; // Auto-tracked
private Address? _address; // Auto-tracked (nested object)
}
[Trackable]
public partial class Address
{
private string _city = ""; // Auto-tracked
private string _detail = ""; // Auto-tracked
}Method 2: Using [TrackableField] Attribute Only
No need for [Trackable], just add [TrackableField] to private fields (class must be partial):
public partial class Order
{
[TrackableField] private string _name; // Tracked
private int _count; // Not tracked
}The Analyzer automatically generates the ITrackable implementation - no manual code needed.
var order = new Order();
order.CustomerName = "John";
// Check if there are changes
order.HasChanges(); // True
// Get list of changed properties
order.GetChangedProperties(); // ["CustomerName"]
// Type-safe dirty flag check
order.GetDirtyFlags(); // Order.DirtyFlag.CustomerName
// Clear change records
order.MarkClean();The generator creates a [Flags] enum DirtyFlag : long for each trackable class, enabling type-safe bit operations:
// Mark specific fields as changed using type-safe flags
order.MarkChanged(Order.DirtyFlag.CustomerName | Order.DirtyFlag.Amount);
// Check specific dirty flags
var flags = order.GetDirtyFlags();
if (flags.HasFlag(Order.DirtyFlag.CustomerName))
{
// Handle customer name change
}
// String-based marking is also supported
order.MarkChanged("CustomerName");Nested trackable objects are automatically tracked, and changes propagate upward:
order.Address = new Address { City = "Shanghai" };
order.Address.Detail = "Nanjing Road 123";
order.HasChanges(); // True (Address changes propagate to Order)
// Recursively clean all nested objects
order.MarkClean(recursive: true);Using extension method (recommended):
using var subscription = order.SubscribeToChanges(() =>
{
Console.WriteLine("Object changed!");
});
// subscription.Dispose() automatically unsubscribesOr subscribe to the event directly:
order.OnChanged += () => Console.WriteLine("Changed!");
order.OnChanged -= handler; // Manual unsubscribeMarks a class as trackable, Analyzer automatically generates ITrackable implementation. All private fields are auto-tracked:
[Trackable]
public partial class MyClass
{
private string _name; // Auto-tracked
private int _count; // Auto-tracked
private List<int> _items; // Auto-tracked
}Generated code includes:
ITrackableinterface implementation (HasChanges(),GetChangedProperties(),MarkClean(),event OnChanged)[Flags] enum DirtyFlag : longwith one flag per tracked field- Property getter/setter for each private field
- Automatic
OnXxxChanged()call in setter using bitflag operations GetDirtyFlags(),MarkChanged(DirtyFlag),MarkChanged(string)helper methods
Can be used independently without [Trackable]. Marks private field as trackable (class must be partial):
public partial class MyClass
{
[TrackableField] private string _name; // Tracked
private int _internalState; // Not tracked
}Can also be used with [Trackable] for explicit tracking:
[Trackable]
public partial class MyClass
{
[TrackableField] private string _name; // Explicit (already auto-tracked)
}Ignore specific private fields in [Trackable] class:
[Trackable]
public partial class MyClass
{
private string _name; // Auto-tracked
[TrackIgnore]
private string _cachedValue; // Ignored, not tracked
}Add extra attributes to generated properties, supports constructor parameters:
using System.Text.Json.Serialization;
[Trackable]
public partial class MyClass
{
[AttachAttribute(typeof(JsonPropertyNameAttribute), "customer_name")]
private string _customerName; // Generate property with attribute
[AttachAttribute(typeof(RequiredAttribute))]
private string _email;
}Generated properties:
[JsonPropertyName("customer_name")]
public string CustomerName { get; set; }
[Required]
public string Email { get; set; }Multiple [AttachAttribute] supported:
[AttachAttribute(typeof(JsonPropertyNameAttribute), "name")]
[AttachAttribute(typeof(MaxLengthAttribute), 100)]
private string _name;DeltaTrack provides three trackable collections that automatically monitor element add/remove/modify operations.
Based on Collection<T>, tracks all list operations:
list.Add(item); // Triggers change
list.Insert(0, item); // Triggers change
list[0] = newItem; // Triggers change (SetItem)
list.RemoveAt(0); // Triggers change
list.Remove(item); // Triggers change
list.Clear(); // Triggers changeIf elements are ITrackable, automatically subscribes to their change events.
Implements IDictionary<TKey, TValue>, tracks all dictionary operations:
dict["key"] = value; // Triggers change (Add or Set)
dict.Add(key, value); // Triggers change
dict.Remove(key); // Triggers change
dict.Clear(); // Triggers change
// Query operations don't trigger change
dict.ContainsKey(key);
dict.TryGetValue(key, out var value);Implements ISet<T>, tracks all set operations:
set.Add(item); // Triggers change (only when actually added)
set.Remove(item); // Triggers change (only when actually removed)
set.Clear(); // Triggers change
// Bulk operations
set.UnionWith(other); // Triggers change (if new items added)
set.IntersectWith(other); // Triggers change (if items removed)
set.ExceptWith(other); // Triggers change (if items removed)
set.SymmetricExceptWith(other); // Triggers change (if any changes)
// Query operations don't trigger change
set.Contains(item);
set.SetEquals(other);
set.IsSubsetOf(other);ITrackable elements in collections are automatically tracked:
var addr = new Address();
order.Addresses.Add(addr);
addr.City = "Beijing"; // Triggers collection's onChange (change propagates up)
order.Addresses.Remove(addr); // Automatically unsubscribes from addrThe sole public contract for consumers. All generated trackable classes implement this interface:
public interface ITrackable
{
bool HasChanges(); // Whether there are changes
IReadOnlyList<string> GetChangedProperties(); // List of changed properties
void MarkClean(bool recursive = false); // Clear change records
event Action OnChanged; // Triggered when changed
}In addition to ITrackable, the generator produces these members on each trackable class:
// Type-safe dirty flag enum
[Flags]
public enum DirtyFlag : long
{
Name = 1L << 0,
Age = 1L << 1,
// ... one flag per tracked field
}
// Get current dirty flags
DirtyFlag GetDirtyFlags();
// Mark fields as changed using type-safe flags
void MarkChanged(DirtyFlag flags);
// Mark field as changed by name
void MarkChanged(string propertyName);// Subscribe to change events, returns IDisposable
IDisposable SubscribeToChanges(Action handler)Example:
// Direct interface usage
order.HasChanges();
order.GetChangedProperties();
order.MarkClean(recursive: true);
order.OnChanged += () => Console.WriteLine("Changed!");
// Using subscription (recommended, auto manages lifecycle)
using var sub = order.SubscribeToChanges(() => Console.WriteLine("Changed!"));DeltaTrack includes compile-time analyzers to catch issues early:
| Code | Severity | Description |
|---|---|---|
| TRACK001 | Error | Trackable class must be declared as partial |
| TRACK002 | Error | [TrackableField] must be on a private field |
| TRACK003 | Error | Trackable class exceeds 64 tracked fields (bitflag limit) |
| Scenario | Usage |
|---|---|
| Data Sync | Only sync fields returned by GetChangedProperties() |
| Form Validation | Real-time monitoring of user input changes, trigger validation |
| Cache Invalidation | Auto refresh cache when objects change |
| Audit Logging | Record changed fields from GetChangedProperties() |
| Database Updates | Only update fields with changes, reduce IO |
| UI Binding | SubscribeToChanges() to notify UI refresh |
| Distributed Systems | Precisely propagate changes to other nodes |
- Compile-time Generation - Based on Roslyn Source Generator, no runtime overhead
- Zero Intrusion - Only add attributes, no business code modification
- Zero Reflection - Generated code calls directly, excellent performance
- Zero GC Dirty Tracking - Uses
longbitflag instead ofHashSet<string>, no allocations on change - Type-Safe Flags - Generated
[Flags] enum DirtyFlag : longper class for compile-time checked operations - Smart Reference Counting - Correctly manages subscriptions when same object referenced multiple places, prevents memory leaks
- Nested Tracking - Auto-tracks nested objects and trackable elements in collections
- Compile-time Diagnostics - Analyzer catches common mistakes (non-partial class, >64 fields, etc.)
MIT License - XBlueC