As discussed in a previous article, Generics provides the ability to create strongly-typed collections in C#. Unfortunately, C# currently does not support generics variance, which would allow inheritance of generic types.

Generics Variance: Not Allowed

For example, imagine two classes:

public class A {}
public class B : A {}

Now imagine two generic List collections and ICollection interfaces:

List<A> collA = new List<A>();
List<B> collB = new List<B>();
ICollection<A> icollA = collA;
ICollection<B> icollB = collB;

It seems reasonable that you could convert a collection of derived objects to a collection of base objects:

ICollection<A> icollA2 = icollB;

And maybe even convert a collection of base objects to a collection of derived objects, with a proper cast:

ICollection<B> icollB2 = (ICollection<B>)icollA;

Unfortunately, neither of these is statements is allowed.

Derived to Base

Following is the code that will convert an ICollection of derived objects to an ICollection of base objects:

#region DERIVED TO BASE
#region CONVERT
/// <summary>
/// Converts between generic ICollection interfaces
/// from a derived generic type to a base type.
/// </summary>
/// <typeparam name="D">Derived type.</typeparam>
/// <typeparam name="B">Base type.</typeparam>
/// <param name="coll">Collection of objects.</param>
public static ICollection<B>
    ConvertDerivedToBase<D, B>( ICollection<D> coll )
    where D : B
{
    return new CollectionWrapperDerived<D, B>( coll );
}
#endregion CONVERT

#region COLLECTION WRAPPER
/// <summary>
/// ICollection wrapper class.
/// </summary>
/// <typeparam name="D">Derived type.</typeparam>
/// <typeparam name="B">Base type.</typeparam>
private class CollectionWrapperDerived<D, B> : ICollection<B>
    where D : B
{
    #region CONSTRUCTOR
    /// <summary>
    /// Constructs a derived type ICollection wrapper.
    /// </summary>
    /// <param name="collection">List of derived objects.</param>
    public CollectionWrapperDerived( ICollection<D> collection )
    {
        this.m_Coll = collection;
    }
    #endregion CONSTRUCTOR

    #region ADD
    /// <summary>
    /// Adds the item to this collection.
    /// </summary>
    /// <param name="item">Item to add. Ignored if null.</param>
    public void Add( B item )
    {
        D derivedItem = default( D );
        if (item != null && item is D)
        {
            derivedItem = (D)item;
            this.m_Coll.Add( derivedItem );
        }
    }
    #endregion ADD

    #region CLEAR
    /// <summary>
    /// Clears this collection.
    /// </summary>
    public void Clear()
    {
        this.m_Coll.Clear();
    }
    #endregion CLEAR

    #region COLLECTION
    /// <summary>
    /// Derived type collection.
    /// </summary>
    private ICollection<D> m_Coll;
    #endregion COLLECTION

    #region CONTAINS
    /// <summary>
    /// Gets whether the specified item is in this collection.
    /// </summary>
    /// <param name="item">Item to search for.</param>
    public bool Contains( B item )
    {
        bool contains = false;
        D derivedItem = default( D );
        if (item != null && item is D)
        {
            derivedItem = (D)item;
            contains = this.m_Coll.Contains( derivedItem );
        }
        return contains;
    }
    #endregion CONTAINS

    #region COPY TO
    /// <summary>
    /// Copies this collection to the specified array.
    /// </summary>
    /// <param name="array">Zero-based array into which to copy the items.</param>
    /// <param name="arrayIndex">Index in the array where to begin the copy.</param>
    public void CopyTo( B[] array, int arrayIndex )
    {
        foreach (D item in this.m_Coll)
        {
            array[arrayIndex++] = item;
        }
    }
    #endregion COPY TO

    #region COUNT
    /// <summary>
    /// Gets the number of items in this collection.
    /// </summary>
    public int Count
    {
        get
        {
            return this.m_Coll.Count;
        }
    }
    #endregion COUNT

    #region ENUMERATOR
    #region INTERFACE
    /// <summary>
    /// Used to satisfy the IEnumerable interface,
    /// but is essentially hidden by typesafe method below.
    /// </summary>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion INTERFACE

    #region TYPESAFE
    /// <summary>
    /// Returns a typesafe enumerator for this collection.
    /// Will not return null.
    /// </summary>
    public IEnumerator<B> GetEnumerator()
    {
        return new EnumeratorWrapper( this.m_Coll.GetEnumerator() );
    }
    #endregion TYPESAFE
    #endregion ENUMERATOR

    #region ENUMERATOR WRAPPER
    /// <summary>
    /// Base type enumerator.
    /// </summary>
    private class EnumeratorWrapper : IEnumerator<B>
    {
        #region CONSTRUCTOR
        /// <summary>
        /// Constructs a base type enumerator wrapper.
        /// </summary>
        /// <param name="collection">List of derived objects.</param>
        public EnumeratorWrapper( IEnumerator<D> collection )
        {
            this.m_Coll = collection;
        }
        #endregion CONSTRUCTOR

        #region DISPOSE
        /// <summary>
        /// Disposes the collection.
        /// </summary>
        public void Dispose()
        {
            this.m_Coll.Dispose();
        }
        #endregion DISPOSE

        #region ENUMERATOR
        /// <summary>
        /// List of derived objects.
        /// </summary>
        private IEnumerator<D> m_Coll;
        #endregion ENUMERATOR

        #region CURRENT
        #region INTERFACE
        /// <summary>
        /// Used to satisfy the IEnumerator interface,
        /// but is essentially hidden by typesafe method below.
        /// </summary>
        object IEnumerator.Current
        {
            get
            {
                return this.m_Coll.Current;
            }
        }
        #endregion INTERFACE

        #region TYPESAFE
        /// <summary>
        /// Gets the current base object referenced by the enumerator.
        /// </summary>
        public B Current
        {
            get
            {
                return this.m_Coll.Current;
            }
        }
        #endregion TYPESAFE
        #endregion CURRENT

        #region MOVE
        /// <summary>
        /// Advances the enumerator to the next element in the collection.
        /// </summary>
        public bool MoveNext()
        {
            return this.m_Coll.MoveNext();
        }
        #endregion MOVE

        #region RESET
        /// <summary>
        /// Resets the enumerator to its initial position.
        /// </summary>
        public void Reset()
        {
            this.m_Coll.Reset();
        }
        #endregion RESET
    }
    #endregion ENUMERATOR WRAPPER

    #region READ ONLY
    /// <summary>
    /// Gets whether the collection is read-only.
    /// </summary>
    public bool IsReadOnly
    {
        get
        {
            return this.m_Coll.IsReadOnly;
        }
    }
    #endregion READ ONLY

    #region REMOVE
    /// <summary>
    /// Removes the specified item from this collection.
    /// </summary>
    /// <param name="item">Item to remove. OK if null.</param>
    public bool Remove( B item )
    {
        bool removed = false;
        D derivedItem = default(D);
        if (item != null && item is D)
        {
            derivedItem = (D)item;
            removed = this.m_Coll.Remove( derivedItem );
        }
        return removed;
    }
    #endregion REMOVE
}
#endregion COLLECTION WRAPPER
#endregion DERIVED TO BASE

Base to Derived

Following is the code that will convert an ICollection of base objects to an ICollection of derived objects. The ConvertBaseToDerived method will retain nulls in the collection for each base object that could not convert to the derived object type. The ConvertBaseToDerivedSafe method will remove any object that could not be properly converted.

#region BASE TO DERIVED
#region CONVERT
/// <summary>
/// Converts between generic ICollection interfaces
/// from a base generic type to a derived type.
/// Note that some items in the collection may not be of the derived type.
/// </summary>
/// <typeparam name="B">Derived type.</typeparam>
/// <typeparam name="D">Base type.</typeparam>
/// <param name="coll">Collection of objects.</param>
public static ICollection<D>
    ConvertBaseToDerived<B, D>( ICollection<B> coll )
    where D : B
{
    return new CollectionWrapperBase<B, D>( coll );
}
#endregion CONVERT

#region CONVERT SAFE
/// <summary>
/// Converts between generic ICollection interfaces
/// from a base generic type to a derived type.
/// Ensures that every item can safely convert to the derived type.
/// </summary>
/// <typeparam name="B">Derived type.</typeparam>
/// <typeparam name="D">Base type.</typeparam>
/// <param name="coll">Collection of objects.</param>
public static ICollection<D>
    ConvertBaseToDerivedSafe<B, D>( ICollection<B> coll )
    where D : B
{
    List<D> list = new List<D>();
    foreach (B b in coll)
    {
        if (b is D)
            list.Add( (D)b );
    }
    return list;
}
#endregion CONVERT SAFE

#region COLLECTION WRAPPER
/// <summary>
/// ICollection wrapper class.
/// </summary>
/// <typeparam name="B">Derived type.</typeparam>
/// <typeparam name="D">Base type.</typeparam>
private class CollectionWrapperBase<B, D> : ICollection<D>
    where D : B
{
    #region CONSTRUCTOR
    /// <summary>
    /// Constructs a derived type ICollection wrapper.
    /// </summary>
    /// <param name="collection">List of derived objects.</param>
    public CollectionWrapperBase( ICollection<B> collection )
    {
        this.m_Coll = collection;
    }
    #endregion CONSTRUCTOR

    #region ADD
    /// <summary>
    /// Adds the item to this collection.
    /// </summary>
    /// <param name="item">Item to add. Ignored if null.</param>
    public void Add( D item )
    {
        B derivedItem = default( B );
        if (item != null && item is B)
        {
            derivedItem = (B)item;
            this.m_Coll.Add( derivedItem );
        }
    }
    #endregion ADD

    #region CLEAR
    /// <summary>
    /// Clears this collection.
    /// </summary>
    public void Clear()
    {
        this.m_Coll.Clear();
    }
    #endregion CLEAR

    #region COLLECTION
    /// <summary>
    /// Derived type collection.
    /// </summary>
    private ICollection<B> m_Coll;
    #endregion COLLECTION

    #region CONTAINS
    /// <summary>
    /// Gets whether the specified item is in this collection.
    /// </summary>
    /// <param name="item">Item to search for.</param>
    public bool Contains( D item )
    {
        bool contains = false;
        B derivedItem = default( B );
        if (item != null && item is B)
        {
            derivedItem = (B)item;
            contains = this.m_Coll.Contains( derivedItem );
        }
        return contains;
    }
    #endregion CONTAINS

    #region COPY TO
    /// <summary>
    /// Copies this collection to the specified array.
    /// </summary>
    /// <param name="array">Zero-based array into which to copy the items.</param>
    /// <param name="arrayIndex">Index in the array where to begin the copy.</param>
    public void CopyTo( D[] array, int arrayIndex )
    {
        foreach (B item in this.m_Coll)
        {
            if (item is D)
                array[arrayIndex++] = (D)item;
        }
    }
    #endregion COPY TO

    #region COUNT
    /// <summary>
    /// Gets the number of items in this collection.
    /// </summary>
    public int Count
    {
        get
        {
            return this.m_Coll.Count;
        }
    }
    #endregion COUNT

    #region ENUMERATOR
    #region INTERFACE
    /// <summary>
    /// Used to satisfy the IEnumerable interface,
    /// but is essentially hidden by typesafe method below.
    /// </summary>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion INTERFACE

    #region TYPESAFE
    /// <summary>
    /// Returns a typesafe enumerator for this collection.
    /// Will not return null.
    /// </summary>
    public IEnumerator<D> GetEnumerator()
    {
        return new EnumeratorWrapper( this.m_Coll.GetEnumerator() );
    }
    #endregion TYPESAFE
    #endregion ENUMERATOR

    #region ENUMERATOR WRAPPER
    /// <summary>
    /// Base type enumerator.
    /// </summary>
    private class EnumeratorWrapper : IEnumerator<D>
    {
        #region CONSTRUCTOR
        /// <summary>
        /// Constructs a base type enumerator wrapper.
        /// </summary>
        /// <param name="collection">List of derived objects.</param>
        public EnumeratorWrapper( IEnumerator<B> collection )
        {
            this.m_Coll = collection;
        }
        #endregion CONSTRUCTOR

        #region DISPOSE
        /// <summary>
        /// Disposes the collection.
        /// </summary>
        public void Dispose()
        {
            this.m_Coll.Dispose();
        }
        #endregion DISPOSE

        #region ENUMERATOR
        /// <summary>
        /// List of derived objects.
        /// </summary>
        private IEnumerator<B> m_Coll;
        #endregion ENUMERATOR

        #region CURRENT
        #region INTERFACE
        /// <summary>
        /// Used to satisfy the IEnumerator interface,
        /// but is essentially hidden by typesafe method below.
        /// </summary>
        object IEnumerator.Current
        {
            get
            {
                return this.m_Coll.Current;
            }
        }
        #endregion INTERFACE

        #region TYPESAFE
        /// <summary>
        /// Gets the current base object referenced by the enumerator.
        /// </summary>
        public D Current
        {
            get
            {
                D d = default(D);
                B b = this.m_Coll.Current;
                if (b is D)
                    d = (D)b;
                return d;
            }
        }
        #endregion TYPESAFE
        #endregion CURRENT

        #region MOVE
        /// <summary>
        /// Advances the enumerator to the next element in the collection.
        /// </summary>
        public bool MoveNext()
        {
            return this.m_Coll.MoveNext();
        }
        #endregion MOVE

        #region RESET
        /// <summary>
        /// Resets the enumerator to its initial position.
        /// </summary>
        public void Reset()
        {
            this.m_Coll.Reset();
        }
        #endregion RESET
    }
    #endregion ENUMERATOR WRAPPER

    #region READ ONLY
    /// <summary>
    /// Gets whether the collection is read-only.
    /// </summary>
    public bool IsReadOnly
    {
        get
        {
            return this.m_Coll.IsReadOnly;
        }
    }
    #endregion READ ONLY

    #region REMOVE
    /// <summary>
    /// Removes the specified item from this collection.
    /// </summary>
    /// <param name="item">Item to remove. OK if null.</param>
    public bool Remove( D item )
    {
        bool removed = false;
        B derivedItem = default( B );
        if (item != null && item is B)
        {
            derivedItem = (B)item;
            removed = this.m_Coll.Remove( derivedItem );
        }
        return removed;
    }
    #endregion REMOVE
}
#endregion COLLECTION WRAPPER
#endregion BASE TO DERIVED

Console Test Program

Here is a console program that tests this capability:

using System;
using System.Collections;
using System.Collections.Generic;

namespace ICollectionConversion
{
    class Program
    {
        static void Main( string[] args )
        {
            List<A> collectionA = new List<A>();
            collectionA.Add( new A( "Joe" ) );
            collectionA.Add( new A( "John" ) );
            collectionA.Add( new B( "Jim", 2 ) );
            List<B> collectionB = new List<B>();
            collectionB.Add( new B( "Jeff", 3 ) );
            collectionB.Add( new B( "Jake", 4 ) );
            ICollection<A> icollA = collectionA;
            ICollection<B> icollB = collectionB;

            ICollection<A> icollA2 = ConvertDerivedToBase<B,A>( icollB );
            ICollection<B> icollB2 = ConvertBaseToDerived<A, B>( icollA );
            ICollection<B> icollB3 = ConvertBaseToDerivedSafe<A, B>( icollA );

            Console.WriteLine( "A List:n------" );
            foreach (A a in icollA)
            {
                Console.WriteLine( a );
            }
            Console.WriteLine( "nB2 List:n--------" );
            foreach (B b in icollB2)
            {
                string output = b == null ? "null" : b.ToString();
                Console.WriteLine( output );
            }
            Console.WriteLine( "nB3 List:n--------" );
            foreach (B b in icollB3)
            {
                Console.WriteLine( b );
            }

            Console.WriteLine( "nnB List:n-------" );
            foreach (B b in icollB)
            {
                Console.WriteLine( b );
            }
            Console.WriteLine( "nA2 List:n--------" );
            foreach (A a in icollA2)
            {
                Console.WriteLine( a );
            }
            Console.ReadLine();
        }
    }
    public class A
    {
        public A( string name )
        {
            this.Name = name;
        }
        public string Name;
        public override string ToString()
        {
            return String.Concat( "A.Name=", this.Name );
        }
    }
    public class B : A
    {
        public B( string name, int id )
            : base( name )
        {
            this.ID = id;
        }
        public int ID;
        public override string ToString()
        {
            return String.Format( "B.Name={0}, ID={1}", this.Name, this.ID );
        }
    }
}

Test Program Output

And here is the console test program output:

A List:
——
A.Name=Joe
A.Name=John
B.Name=Jim, ID=2

B2 List:
——–
null
null
B.Name=Jim, ID=2

B3 List:
——–
B.Name=Jim, ID=2

B List:
——-
B.Name=Jeff, ID=3
B.Name=Jake, ID=4

A2 List:
——–
B.Name=Jeff, ID=3
B.Name=Jake, ID=4

Share and Enjoy:
  • Digg
  • Twitter
  • Facebook
  • Reddit
  • StumbleUpon
  • LinkedIn
  • Google Bookmarks
  • Slashdot