views:

473

answers:

1

I have a class called Client, which is a subclass of Configurable.

I have an ObservableCollection<Client> which I need to view as an ObservableCollection<Configurable>. This will allow me to databind to the list from some general layout generation code. It must also allow me to clear the list, and to add items to the list. Of course, when adding items to the list it must do a runtime type check to verify the general item (Configurable) being added is of the appropriate type (Client).

I am imagining a class called something like ObservableSurrogateCollection<T>. T is the general class (Configurable). You would construct it by handing it an ObservableCollection<T2>, where T2 is a subclass of T. You can databind to it, and all collection changed events on the wrapped list are correctly routed (both directions).

Does this exists? Is this not something I should be doing? I think I read that .NET 4.0 will support such a feature at the language level?

I have looked at these options:

  • ReadOnlyObservableCollection<T>. This is really close. However, because it is read-only I can't add or clear the items.
  • A non-generic ObservableCollection. I can't seem to find this, if it exists.

Thanks in advance for any help!

+1  A: 

No, that doesn't exist AFAIK, but shouldn't be hard to implement; it does, of course, involve the nuances of variance, so your collection must expect casting problems when assigning/adding. Note that arrays (T[]) already act like this (arrays of reference-types have variance support, with typecast checking etc), but you can't add to an array, and it doesn't fire change events.

To implement this would largely be an encapsulation job; object construction (when data-binding to new rows) would be the tricky bit, and may force you to add extra interfaces.

4.0 won't add anything here: What C# 4.0 covariance *doesn't* do

Untested, but:

public class ObservableCollection<TBase, TActual> : IList<TBase>, IBindingList, INotifyCollectionChanged
    where TBase : class
    where TActual : class, TBase
{

    private readonly ObservableCollection<TActual> innerList;

    public ObservableCollection()
        : this((IEnumerable<TActual>)null) {}
    public ObservableCollection(IEnumerable<TBase> data)
        : this(data == null ? null : data.Cast<TActual>()) {}
    public ObservableCollection(IEnumerable<TActual> data)
    {
        innerList = data == null ? new ObservableCollection<TActual>()
            : new ObservableCollection<TActual>(data);
        innerList.CollectionChanged += new NotifyCollectionChangedEventHandler(innerList_CollectionChanged);
    }

    void innerList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        ListChangedEventHandler handler = ListChanged;
        if(handler != null) {
            ListChangedEventArgs args = null;
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    args = new ListChangedEventArgs(ListChangedType.ItemAdded, e.NewStartingIndex);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    args = new ListChangedEventArgs(ListChangedType.ItemDeleted, e.OldStartingIndex);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    args = new ListChangedEventArgs(ListChangedType.Reset, -1);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    args = new ListChangedEventArgs(ListChangedType.ItemChanged, e.NewStartingIndex);
                    break;
                case NotifyCollectionChangedAction.Move:
                    args = new ListChangedEventArgs(ListChangedType.ItemMoved, e.NewStartingIndex, e.OldStartingIndex);
                    break;
            }
            if(args != null) handler(this, args);
        }
        NotifyCollectionChangedEventHandler handler2 = CollectionChanged;
        if (handler2 != null) handler2(this, e);
    }

    public void AddIndex(PropertyDescriptor property) {}

    public object AddNew()
    {
        TActual obj = (TActual)Activator.CreateInstance(typeof(TActual));
        Add(obj);
        return obj;
    }

    public bool AllowEdit { get { return !IsReadOnly; } }
    public bool AllowNew { get { return !IsFixedSize; } }
    public bool AllowRemove { get { return !IsFixedSize; } }

    public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
    {
        throw new System.NotImplementedException();
    }

    public int Find(PropertyDescriptor property, object key)
    {
        throw new System.NotImplementedException();
    }

    public bool IsSorted { get { return false; } }

    public event ListChangedEventHandler ListChanged;
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    public void RemoveIndex(PropertyDescriptor property) { }

    public void RemoveSort()
    {
        throw new System.NotImplementedException();
    }

    public ListSortDirection SortDirection
    {
        get { return ListSortDirection.Ascending; }
    }

    public PropertyDescriptor SortProperty
    {
        get { throw new System.NotImplementedException(); }
    }

    public bool SupportsChangeNotification { get { return true; } }
    public bool SupportsSearching { get { return false; } }
    public bool SupportsSorting { get { return false; } }

    int IList.Add(object value)
    {
        int index = innerList.Count;
        Add((TBase)value);
        return index;
    }
    public void Add(TBase value)
    {
        innerList.Add((TActual)value);
    }

    public void Clear()
    {
        innerList.Clear();
    }

    bool IList.Contains(object value)
    {
        return Contains((TBase)value);
    }
    public bool Contains(TBase value)
    {
        return innerList.Contains((TActual)value);
    }

    int IList.IndexOf(object value)
    {
        return IndexOf((TBase)value);
    }
    public int IndexOf(TBase value)
    {
        return innerList.IndexOf((TActual)value);
    }

    void IList.Insert(int index, object value)
    {
        Insert(index, (TBase)value);
    }
    public void Insert(int index, TBase value)
    {
        innerList.Insert(index, (TActual)value);
    }

    public bool IsFixedSize
    {
        get { return ((IList)innerList).IsFixedSize; }
    }

    public bool IsReadOnly
    {
        get { return ((IList)innerList).IsReadOnly; }
    }

    void IList.Remove(object value)
    {
        Remove((TBase)value);
    }
    public bool Remove(TBase value)
    {
        return innerList.Remove((TActual)value);
    }

    public void RemoveAt(int index)
    {
        innerList.RemoveAt(index);
    }

    object IList.this[int index]
    {
        get { return innerList[index]; }
        set { innerList[index] = (TActual)value; }
    }
    public TBase this[int index]
    {
        get { return innerList[index]; }
        set { innerList[index] = (TActual)value; }
    }

    void ICollection.CopyTo(System.Array array, int index)
    {
        ((IList)innerList).CopyTo(array, index);
    }
    public void CopyTo(TBase[] array, int index)
    {
        innerList.CopyTo((TActual[])array, index);
    }

    public int Count { get { return innerList.Count; } }

    public bool IsSynchronized
    {
        get { return ((IList)innerList).IsSynchronized; }
    }

    public object SyncRoot
    {
        get { return ((IList)innerList).SyncRoot; }
    }

    public IEnumerator<TBase> GetEnumerator()
    {
        return innerList.Cast<TBase>().GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return innerList.GetEnumerator();
    }
}
Marc Gravell