Generics in .NET 2.0 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.

For example, consider a list of strings and a list of objects:

List<string> strings = new List<string>();
strings.Add( "hello" );
strings.Add( "goodbye" );
List<object> objects = new List<object>();
objects.AddRange( strings );

The final line in the code above generates a compiler error. But why? Since the ‘string’ class derives from the ‘object’ class, one would expect List<string> to also implicitly derive from List<object>. This capability is called generics variance, but C# currently does not support it.

Fortunately, you can brute force your way to a solution by creating a generic ConvertIEnumerable method:

/// <summary>
/// Converts between generic IEnumerable interfaces
/// where the derived generic type inherits from the base generic type.
/// </summary>
/// <typeparam name="D">Derived type.</typeparam>
/// <typeparam name="B">Base type.</typeparam>
/// <param name="list">List of objects.</param>
public static IEnumerable<B>
    ConvertIEnumerable<D, B>( IEnumerable<D> list )
    where D : B
{
    return new EnumerableWrapper<D, B>( list );
}

Then you explicitly call the conversion method, passing the derived and base types:

objects.AddRange(
    ConvertIEnumerable<string, object>( strings ) );

And here is the EnumerableWrapper class:

/// <summary>
/// Enumerable wrapper class.
/// </summary>
/// <typeparam name="D">Derived type.</typeparam>
/// <typeparam name="B">Base type.</typeparam>
private class EnumerableWrapper<D, B> : IEnumerable<B>
    where D : B
{
    #region CONSTRUCTOR
    /// <summary>
    /// Constructs a derived type enumerable wrapper.
    /// </summary>
    /// <param name="list">List of derived objects.</param>
    public EnumerableWrapper( IEnumerable<D> list )
    {
        this.m_List = list;
    }
    #endregion CONSTRUCTOR

    #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_List.GetEnumerator() );
    }
    #endregion TYPESAFE
    #endregion ENUMERATOR

    #region LIST
    /// <summary>
    /// Derived type list.
    /// </summary>
    private IEnumerable<D> m_List;
    #endregion LIST

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

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

        #region ENUMERATOR
        /// <summary>
        /// List of derived objects.
        /// </summary>
        private IEnumerator<D> m_List;
        #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_List.Current;
            }
        }
        #endregion INTERFACE

        #region TYPESAFE
        /// <summary>
        /// Gets the current base object referenced by the enumerator.
        /// </summary>
        public B Current
        {
            get
            {
                return this.m_List.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_List.MoveNext();
        }
        #endregion MOVE

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

For more complicated examples of generics variance, check out this MSDN article.

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