using System; using System.Collections.Generic; using Chernobyl.Event; using Chernobyl.Utility; using Chernobyl.Values; namespace Chernobyl.Collections.Generic.Event { /// /// Behaves like the method /// /// in that it projects each element of a sequence into a new form by /// incorporating the element's index and maintains that form even if the /// source changes. /// /// The type of the elements of source. /// The type of the value returned by selector. public class SelectEventEnumerable : DecoratingEventEnumerable { /// /// Initializes a new instance of the /// class. /// /// A sequence of values to invoke a transform /// function on. /// A transform function to apply to each source /// element. The second parameter represents the index of the source /// element. The third parameter is the callback to invoke to keep the /// transformed value up to date. /// The method that is invoked when updates to /// the transformed value are no longer needed. The second parameter /// represents the index of the source element. The third parameter is /// the callback to invoke to keep the transformed value up to date. public SelectEventEnumerable(IEventEnumerable source, Action>> selector, Action>> unselector) : this(source, selector, unselector, new Dictionary()) {} /// /// Initializes a new instance of the /// class. /// /// A sequence of values to invoke a transform /// function on. /// A transform function to apply to each source /// element. The second parameter represents the index of the source /// element. The third parameter is the callback to invoke to keep the /// transformed value up to date. /// The method that is invoked when updates to /// the transformed value are no longer needed. The second parameter /// represents the index of the source element. The third parameter is /// the callback to invoke to keep the transformed value up to date. /// The being /// decorated or extended with event capabilities. protected SelectEventEnumerable(IEventEnumerable source, Action>> selector, Action>> unselector, IDictionary items) : base(items.Values) { selector.ThrowIfNull("selector"); _selector = selector; unselector.ThrowIfNull("unselector"); _unselector = unselector; source.ThrowIfNull("source"); Source = source; items.ThrowIfNull("implementation"); _items = items; } /// /// A sequence of values to invoke a transform function on. /// public IEventEnumerable Source { get { return _source; } private set { // Unconfigure the old instance. If the ItemsNeeded is false then // the unconfiguration has already occured. if (_source != null && ItemsNeeded) UnreadyItems(); _source = value; // Configure the new instance but only if the the items from this // instance are needed. if (_source != null && ItemsNeeded) ReadyItems(); } } /// /// Invoked when the items in this instance need to be ready for /// iteration or events. This method is invoked when /// is changed to true. /// protected override void ReadyItems() { int index = 0; foreach (TSource source in _source) { var eventHandler = CreateOnValueChanged(index); _selector(source, index, eventHandler); ++index; } _source.ItemsAdded += OnSourceItemsAdded; _source.ItemsRemoved += OnSourceItemsRemoved; base.ReadyItems(); } /// /// Invoked when the items in this instance no longer need to be ready /// for iteration or events. This method is invoked when /// is changed to false. /// protected override void UnreadyItems() { // No longer need to know when new items are added. _source.ItemsAdded -= OnSourceItemsAdded; _source.ItemsRemoved -= OnSourceItemsRemoved; // No longer need to know when transformations change. int index = 0; foreach (TSource source in _source) { var eventHandler = _eventHandlers[index]; _unselector(source, index, eventHandler); ++index; } _items.Clear(); _eventHandlers.Clear(); base.UnreadyItems(); } /// /// An event handler that is invoked when items are added to /// . This method ensures that transformations are /// performed on the new items. /// /// The sender of the event. /// The instance /// containing the event data. void OnSourceItemsAdded(object sender, ItemsEventArgs e) { foreach (var item in e.Items) { int index = _source.IndexOf(item); var eventHandler = CreateOnValueChanged(index); _selector(item, index, eventHandler); } } /// /// An event handler that is invoked when items are removed from /// . This method ensures that transformations do /// not continue on the removed items. /// /// The sender of the event. /// The instance /// containing the event data. void OnSourceItemsRemoved(object sender, ItemsEventArgs e) { foreach (var item in e.Items) { int index = _source.IndexOf(item); var eventHandler = _eventHandlers[index]; _unselector(item, index, eventHandler); _eventHandlers.Remove(index); } } /// /// An event handler that is invoked when the transformation of an item /// in is changed. /// /// The /// instance containing the event data. /// The zero based location of the item that was /// changed. void OnValueChanged(ValueChangedEventArgs e, int index) { // Remove the old value of the item. _items.Remove(index); if (ItemsRemovedHandler != null) ItemsRemovedHandler(this, new ItemsEventArgs(e.OldValue)); // Add the new value of the item into the location in the list that // corresponds with the location in the Source. Both the source and // this list must correlate with regard to order since Enumerable.Select // correlates in this manner and differing would be confusing to those // using this code. _items.Add(index, e.NewValue); if(ItemsAddedHandler != null) ItemsAddedHandler(this, new ItemsEventArgs(e.NewValue)); } /// /// Creates and stores an event handler that invokes /// /// with the provided index. /// /// The index that is to be passed to /// . /// The event handler requested. EventHandler> CreateOnValueChanged(int index) { EventHandler> eventHandler = (valueChangeSender, valueChangeEventArgs) => OnValueChanged(valueChangeEventArgs, index); _eventHandlers.Add(index, eventHandler); return eventHandler; } /// /// A sequence of values to invoke a transform function on. /// IEventEnumerable _source; /// /// A transform function to apply to each source element. The second /// parameter represents the index of the source element. The third /// parameter is the callback to invoke to keep the transformed value up /// to date. /// readonly Action>> _selector; /// /// The method that is invoked when updates to the transformed value are /// no longer needed. The second parameter represents the index of the /// source element. The third parameter is the callback to invoke to /// keep the transformed value up to date. /// readonly Action>> _unselector; /// /// The instance that contains items selected from /// mapped to their index in . The items are mapped /// to this index because each item may be provided at a different time. /// readonly IDictionary _items; /// /// The instance that contains the event handlers that react to the /// providing of values by the instance contained in . /// Each method is mapped to an index within where /// the item the method reports on is located. /// readonly IDictionary>> _eventHandlers = new Dictionary>>(); } /// /// Extension and utility methods for the /// and its dependencies. /// public static class SelectEventEnumerable { /// /// Behaves like the method /// /// in that it projects each element of a sequence into a new form by /// incorporating the element's index and maintains that form even if the /// source changes. /// /// The type of the elements of source. /// The type of the value returned by selector. /// A sequence of values to invoke a transform function on. /// A transform function to apply to each source /// element; the second parameter of the function represents the index /// of the source element. /// An whose elements are the /// result of invoking the transform function on each element of source. public static IEventEnumerable Select( this IEventEnumerable source, Func selector) { return source.Select((value, i) => selector(value)); } /// /// Behaves like the method /// /// in that it projects each element of a sequence into a new form by /// incorporating the element's index and maintains that form even if the /// source changes. /// /// The type of the elements of source. /// The type of the value returned by selector. /// A sequence of values to invoke a transform function on. /// A transform function to apply to each source /// element; the second parameter of the function represents the index /// of the source element. /// An whose elements are the /// result of invoking the transform function on each element of source. public static IEventEnumerable Select( this IEventEnumerable source, Func selector) { return new SelectEventEnumerable(source, (value, index, handler) => handler(null, new ValueChangedEventArgs(default(TResult), selector(value, index))), (value, index, handler) => { /* no-op */ }); } /// /// Takes the internal values from a collection of /// and stores them in an and maintains /// that list so that added or removed are reflected. /// /// The type of items stored in the /// and in the returned /// . /// The instance that contains the /// instances whose internals are to be reflected /// in the return . /// The that reflects the /// internal values of the . public static IEventEnumerable AsValues(this IEventEnumerable> source) { return new SelectEventEnumerable, T>(source, (value, index, handler) => value.Provide += handler, (value, index, handler) => value.Provide -= handler); } } }