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.
[…] discussed in a previous article, C# Generics provides the ability to create strongly-typed collections in C#. Unfortunately, […]
Wow, what’s with all the #regions??
Whats wrong with a simple .Cast() ?
public static System.Collections.Generic.IEnumerable Cast(this System.Collections.IEnumerable source)
Member of System.Linq.Enumerable
Re: Whats wrong with a simple .Cast() ?
System.Linq is not available in .NET 2.0.
Also coming soon in .NET 4.0: generics variance! So this will no longer be an issue.