using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class EventAggregator
public static EventAggregator<TSource, TAccumulate> Create<TSource, TAccumulate>(
INotifyCollectionChanged collection,
Func<TAccumulate, TSource, TAccumulate> execute,
Func<TAccumulate, TSource, TAccumulate> undo,
Func<TAccumulate, bool> validate = null,
ISet<string> propertySet = null
) where TSource : class, INotifyPropertyChanging, INotifyPropertyChanged =>
new EventAggregator<TSource, TAccumulate>(collection, seed, execute, undo, validate, propertySet);
public class EventAggregator<TSource, TAccumulate> where TSource : class, INotifyPropertyChanging, INotifyPropertyChanged
public TAccumulate Value { get; private set; }
Func<TAccumulate, TSource, TAccumulate> execute;
Func<TAccumulate, TSource, TAccumulate> undo;
Func<TAccumulate, bool> validate;
ISet<string> propertySet;
INotifyCollectionChanged collection,
Func<TAccumulate, TSource, TAccumulate> execute,
Func<TAccumulate, TSource, TAccumulate> undo,
Func<TAccumulate, bool> validate = null,
ISet<string> propertySet = null
collection.CollectionChanged += this.CollectionChanged;
this.validate = validate;
this.propertySet = propertySet;
void AddItems(IList items)
foreach (TSource item in items) {
var tempValue = execute(Value, item);
if (validate?.Invoke(tempValue) == false) {
throw new Exception("AddItems validation error");
item.PropertyChanging += PropertyChanging;
item.PropertyChanged += PropertyChanged;
void RemoveItems(IList items)
foreach (TSource item in items) {
var tempValue = undo(Value, item);
if (validate?.Invoke(tempValue) == false) {
throw new Exception("RemoveItems validation error");
item.PropertyChanged -= PropertyChanged;
item.PropertyChanging -= PropertyChanging;
void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Remove:
RemoveItems(args.OldItems);
case NotifyCollectionChangedAction.Replace:
RemoveItems(args.OldItems);
case NotifyCollectionChangedAction.Reset:
throw new NotImplementedException();
void PropertyChanging(object sender, PropertyChangingEventArgs e)
if (propertySet?.Contains(e.PropertyName) != true && propertySet != null) {
var item = sender as TSource;
throw new ArgumentException("sender");
Value = undo(Value, item);
void PropertyChanged(object sender, PropertyChangedEventArgs e)
if (propertySet?.Contains(e.PropertyName) != true && propertySet != null) {
var item = sender as TSource;
throw new ArgumentException("sender");
var tempValue = execute(Value, item);
if (validate?.Invoke(tempValue) == false) {
throw new Exception("PropertyChanged validation error");
public class Item : INotifyPropertyChanging, INotifyPropertyChanged
NotifyPropertyChanging();
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanging([CallerMemberName] String propertyName = "")
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public override string ToString()
return $"Item {{ H = {this.H} }}";
public static void Main()
display("Init collection: ");
var coll = new ObservableCollection<Item>();
var sumAggr = EventAggregator.Create(
execute: (int accum, Item item) => accum + item.H,
undo: (int accum, Item item) => accum - item.H,
validate: accum => accum < 5,
propertySet: new HashSet<string>() { nameof(Item.H) }
display($"Value: {sumAggr.Value}");
var logAggr = EventAggregator.Create(
seed: new List<string>(),
execute: (List<string> accum, Item item) => { accum.Add($"Added item: {item}"); return accum; },
undo: (List<string> accum, Item item) => { accum.Add($"Removed item: {item}"); return accum; }
var item0 = new Item() { H = 2 };
display($"Value: {sumAggr.Value}");
coll.Add(new Item() { H = 1 });
display($"Value: {sumAggr.Value}");
display("Change item: ");
display($"Value: {sumAggr.Value}");
display("Remove item: ");
display($"Value: {sumAggr.Value}");
coll.Add(new Item() { H = 10 });
display($"Value: {sumAggr.Value}");
public static void display(string str)
public static void display(IEnumerable list)
FiddleHelper.WriteTable(list);