Event Driven Classes - Collections

MetaProperties provides a utility library called MetaProperties.Collections which contains a number of interfaces and classes which are helpful when writing event driven code.

Interfaces

The following interfaces are defined as combinations of FCL interfaces, and add no extra members of their own:
  • IObservableEnumerable<T> : IEnumerable<T>, INotifyCollectionChanged
  • IObservableCollection<T> : ICollection<T>, IObservableEnumerable<T>
  • IObservableList<T> : IList<T>, IObservableCollection<T>

In addition to the above, IObservableKeyedCollection<TKey, TItem> provides a definition of a data binding compatible keyed list. It is defined as follows:

    public interface IObservableKeyedCollection<KeyT, ItemT> : IObservableList<ItemT>
    {
        ItemT[] ToArray();

        ItemT this[int index]
        {
            get;
            set;
        }

        ItemT this[KeyT identifier]
        {
            get;
        }

        void RemoveAt(int index);

        void Insert(int index, ItemT item);

        void AddRange(IEnumerable<ItemT> items);

        bool Contains(KeyT identifier);

        int IndexOf(KeyT identifier);

        bool TryGetValue(KeyT identifier, out ItemT value);

        bool Remove(KeyT identifier);

        int IndexOf(ItemT item);
    }

Classes

ObservableEnumerable.Empty`1()

This generic static method on a static class exposes a standard empty collection implementing IObservableEnumerable`1.

ObservableList`1

This class provides an implementation of IObservableList`1. This is a more complete observable collection than the FCL's ObservableCollection`1.

In addition, the FCL ObservableCollection`1 raises a CollectionChanged event of type CollectionChangeAction.Reset when the collection is cleared. This is inconvenient because it doesn't list the cleared items in the event arguments, which means you can't detatch from any events on those items or perform any other cleanup.

Therefore the MetaProperties ObservableList`1 instead raises a CollectionChanged event of type CollectionChangeAction.Remove when cleared and lists every item that was removed by the clear.

ObservableList`1 also implicitly implements IList making it compatable with WPF when initializing the list from XAML.

ObservableKeyedCollection`2

The FCL INotifyCollectionChanged interface relies on items in a collection having an index. This makes the standard dictionary unsuitable, as items are unordered.

MetaProperties provides ObservableKeyedCollection`2 which implements IObservableKeyedCollection`2 shown above. It uses a system where the key is derived from the item via a lamda expression, meaning the key doesn't have to be specified when adding items to the list.

This is not only easier to use, but along with implementing IList it makes it compatible with WPF when initializing the list from XAML.

It is used as follows:

    public class Part
    {
        private readonly Guid partId;

        public Part(Guid partId)
        {
            this.partId = partId;
        }

        public Guid PartId
        {
            get { return this.partId; }
        }

        public int Quantity
        {
            get; set;
        }
    }

    public void Example()
    {
        ObservableKeyedCollection<Guid, Part> parts
            = new ObservableKeyedCollection<Guid, Part>(p => p.PartId);

        Guid partId = Guid.NewGuid();

        Part part = new Part(partId)
        {
            Quantity = 4
        };

        parts.Add(part);

        Part fetchedPart1 = parts[partId];
        Part fetchedPart2 = parts[0];

        Assert.AreEqual(part, fetchedPart1);
        Assert.AreEqual(part, fetchedPart2);
    }


Internally the class is a combination of a dictionary and a list, which means although you benefit from fast random access times you don't get the same benefit when removing as the list still must be searched.

Also note that the key must not change after the class is added to the collection or it will be lost.

ReadOnlyObservableList`1, ReadOnlyObservableCollection`1 and ReadOnlyKeyedCollection`2

Read-only collections are backed by a writable collection and exposed instead of the writable collection when external code should not be able to add or remove items.

The CollectionChanged event raised by the read-only collection (when the backing collection changes) needs to have a sender, however this sender cannot be the internal writable collection or we break encapsulation. We also we cannot simply expose a transient read-only wrapper by calling an AsReadOnly method on the backing collection because if the sender is compared to the publicly exposed collection later it will not be the same reference.

Therefore MetaProperties provies a number of read-only wrapper collections that help solve this problem.
  • ReadOnlyObservableList`1 is a read-only wrapper around any IObservableList`1.
  • ReadOnlyObservableCollection`1 is a read-only wrapper around any IObservableCollection`1.
  • ReadOnlyObservableKeyedCollection`2 is a read-only wrapper around any IObservableKeyedCollection`2.

They are all used in much the same way, as shown below:

    public class Example
    {
        // The writable backing list.
        private readonly ObservableList<string> items = new ObservableList<string>();

        // The read-only list that will be exposed.
        private readonly ReadOnlyObservableList<string> readOnlyItems;

        public Example()
        {
            // Initialize the read-only list, specifying that it is backed by the writable list.
            this.readOnlyItems = new ReadOnlyObservableList<string>(this.items);

            // Add items to the writable list.
            this.items.Add("Apple");
            this.items.Add("Orange");
        }

        // Exposing the read-only list.
        public IObservableList<string> Items
        {
            get
            {
                return this.readOnlyItems;
            }
        }
    }

Last edited Mar 9, 2010 at 11:20 AM by jamesthurley, version 1

Comments

No comments yet.