using System; using System.Collections.Generic; using System.Linq; using Chernobyl.Event; using Chernobyl.Utility; namespace Chernobyl.Collections.Generic.Event { /// /// Behaves like the method /// /// in that it projects each element of a sequence to an /// and flattens the resulting sequences /// into one sequence but this type continuously updates itself based on the /// changes to the original source sequence and the sequences returned by the /// provided selector method. /// /// The type of the elements of source. /// The type of the elements of the sequence /// returned by selector. public class SelectManyEventEnumerable : DecoratingEventEnumerable { /// /// Initializes a new instance of the /// class. /// /// A sequence of values to project. /// A transform function to apply to each element. public SelectManyEventEnumerable(IEventEnumerable source, Func> selector) : this(source, selector, new List()) {} /// /// Initializes a new instance of the /// class. /// /// A sequence of values to project. /// A transform function to apply to each element. /// The instance that will implement the /// side of this type. private SelectManyEventEnumerable(IEventEnumerable source, Func> selector, ICollection implementation) : base(implementation) { selector.ThrowIfNull("selector"); source.ThrowIfNull("source"); _implementation = implementation; _selector = selector; Source = source; } /// /// A sequence of values to project. /// public IEventEnumerable Source { get { return _source; } private set { IEventEnumerable previousValue = _source; _source = value; if (previousValue != null) { previousValue.ItemsAdded += OnSourcesAdded; previousValue.ItemsRemoved += OnSourcesRemoved; OnSourcesRemoved(this, new ItemsEventArgs(previousValue.ToArray())); } if (_source != null) { OnSourcesAdded(this, new ItemsEventArgs(_source.ToArray())); _source.ItemsAdded += OnSourcesAdded; _source.ItemsRemoved += OnSourcesRemoved; } } } /// /// An event handler that is invoked when new sources have been added to /// . This method applies the /// method to gather results and also ensures the addition/removal of /// results is reported later on. /// /// The sender of the event. /// The event data containing the items added. void OnSourcesAdded(object sender, ItemsEventArgs e) { foreach (TSource source in e.Items) { IEventEnumerable results = _selector(source); if(results.Any()) OnResultsAdded(this, new ItemsEventArgs(results.ToArray())); results.ItemsAdded += OnResultsAdded; results.ItemsRemoved += OnResultsRemoved; } } /// /// An event handler that is invoked when new sources have been removed from /// . This method applies the /// method to gather results that are to be removed and ensures results /// are no longer be listened to for additions/removals. /// /// The sender of the event. /// The event data containing the items removed. void OnSourcesRemoved(object sender, ItemsEventArgs e) { foreach (TSource source in e.Items) { IEventEnumerable results = _selector(source); results.ItemsAdded -= OnResultsAdded; results.ItemsRemoved -= OnResultsRemoved; if(results.Any()) OnResultsRemoved(this, new ItemsEventArgs(results.ToArray())); } } /// /// An event handler that is invoked when sources have added new /// instances. This method collects the /// new instances and reports them as having been added. /// /// The sender of the event. /// The event data containing the items added. void OnResultsAdded(object sender, ItemsEventArgs e) { _implementation.AddRange(e.Items); if (ItemsAddedHandler != null) ItemsAddedHandler(this, e); } /// /// An event handler that is invoked when sources have removed /// instances. This method removes the /// instances and reports them as having been removed. /// /// The sender of the event. /// The event data containing the items added. void OnResultsRemoved(object sender, ItemsEventArgs e) { _implementation.RemoveRange(e.Items); if (ItemsRemovedHandler != null) ItemsRemovedHandler(this, e); } /// /// The instance that will implement the /// side of this type. /// readonly ICollection _implementation; /// /// A transform function to apply to each element. /// readonly Func> _selector; /// /// The backing field to . /// IEventEnumerable _source; } /// /// Utility methods that work on or with /// . /// public static class SelectManyEventEnumerableExtensions { /// /// Behaves like the method /// /// in that it projects each element of a sequence to an /// and flattens the resulting sequences /// into one sequence but the instance returned by this method continuously /// updates itself based on the changes to the original source sequence /// and the sequences returned by the provided selector method. /// /// The type of the elements of source. /// The type of the elements of the sequence /// returned by selector. /// A sequence of values to project. /// A transform function to apply to each element. /// An whose elements are the /// result of invoking the one-to-many transform function on each element /// of the input sequence and will continuously update itself to remain /// that way. public static IEventEnumerable SelectMany( this IEventEnumerable source, Func> selector) { return new SelectManyEventEnumerable(source, selector); } } }