EventAggregator

CSharp · Console

/Program.cs

using System;
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,
        TAccumulate seed,
        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; }
    TAccumulate seed;
    Func<TAccumulate, TSource, TAccumulate> execute;
    Func<TAccumulate, TSource, TAccumulate> undo;
    Func<TAccumulate, bool> validate;
    ISet<string> propertySet;
    
    public EventAggregator(
        INotifyCollectionChanged collection,
        TAccumulate seed,
        Func<TAccumulate, TSource, TAccumulate> execute,
        Func<TAccumulate, TSource, TAccumulate> undo,
        Func<TAccumulate, bool> validate = null,
        ISet<string> propertySet = null
    )
    {
        collection.CollectionChanged += this.CollectionChanged;
        this.Value = seed;
        this.seed = seed;
        this.execute = execute;
        this.undo = undo;
        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");
            }
            Value = tempValue;
            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");
            }
            Value = tempValue;
            item.PropertyChanged -= PropertyChanged;
            item.PropertyChanging -= PropertyChanging;
        }
    }

    void CollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        switch (args.Action) {
        case NotifyCollectionChangedAction.Add:
            AddItems(args.NewItems);
            break;

        case NotifyCollectionChangedAction.Move:
            break;

        case NotifyCollectionChangedAction.Remove:
            RemoveItems(args.OldItems);
            break;

        case NotifyCollectionChangedAction.Replace:
            RemoveItems(args.OldItems);
            AddItems(args.NewItems);
            break;

        case NotifyCollectionChangedAction.Reset:
            Value = seed;
            break;

        default:
              throw new NotImplementedException();
        }
    }
    
    void PropertyChanging(object sender, PropertyChangingEventArgs e)
    {
        if (propertySet?.Contains(e.PropertyName) != true && propertySet != null) {
            return;
        }

        var item = sender as TSource;
        if (item == null) {
            throw new ArgumentException("sender");
        }

        Value = undo(Value, item);
    }

    void PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (propertySet?.Contains(e.PropertyName) != true && propertySet != null) {
            return;
        }

        var item = sender as TSource;
        if (item == null) {
            throw new ArgumentException("sender");
        }

        var tempValue = execute(Value, item);
        if (validate?.Invoke(tempValue) == false) {
            throw new Exception("PropertyChanged validation error");
        }

        Value = tempValue;
    }
}

public class Item : INotifyPropertyChanging, INotifyPropertyChanged
{
    private int h;

    public int H { 
        get => this.h;
        set
        {
            if (value != this.h) {
                NotifyPropertyChanging();
                this.h = value;
                NotifyPropertyChanged();
            }
        }
    }
    
    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 class Program
{
	public static void Main()
	{
		display("Init collection: ");
		var coll = new ObservableCollection<Item>();

		var sumAggr = EventAggregator.Create(
			coll,
			seed: 0,
			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(
			coll,
			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; }
		);
		
		display("Add item: ");
		var item0 = new Item() { H = 2 };
		coll.Add(item0);
		display(coll);
		display($"Value: {sumAggr.Value}");

		display("Add item: ");
		coll.Add(new Item() { H = 1 });
		display(coll);
		display($"Value: {sumAggr.Value}");

		display("Change item: ");
		item0.H = -1;
		display(coll);
		display($"Value: {sumAggr.Value}");

		display("Remove item: ");
		coll.Remove(item0);
		display(coll);
		display($"Value: {sumAggr.Value}");
		
		display("Log: ");
		display(logAggr.Value);

		display("Add item: ");
		coll.Add(new Item() { H = 10 });
		display(coll);
		display($"Value: {sumAggr.Value}");
	}
	
	public static void display(string str)
	{
		Console.WriteLine(str);
	}

	public static void display(IEnumerable list)
	{
		FiddleHelper.WriteTable(list);
	}
}
;