Sometimes, when coding around problems I'm encountering, I feel like I'm reinventing the wheel. It happened to me a few weeks ago, and I want to share with you the solution I came up with, so someone can tell me I've overseen something and I'm an idiot. If it does not happen, well, my solution may as well help you too.
First, the scenario
I have a base abstract class LaunchableItem, and a derived class PasswordLaunchableItem. I also have an interface ILaunchableItemSource (which may as well have been an abstract class, doesn't matter) which exposes an Items property of type IEnumerable<LaunchableItem>. Finally, I have a class implementing this interface, called SlimLaunchPasswordSource.
Second, the problem
My SlimLaunchPasswordSource class keeps its PasswordLaunchableItem instances in a List<PasswordLaunchableItem>, which is a common scenario. Now, it needs to implemfont-familyent the Items property. My first reflex, being a C++ developer who knows typecasts can save you (though they also can sink you), is to plainly return my List<PasswordLaunchableItem>. Isn't an IEnumerable<PasswordLaunchableItem> also an IEnumerable<LaunchableItem>?
I'm welcomed by the compiler with this error:
error CS0266: Cannot implicitly convert type 'System.Collections.Generic.List<SlimCode.SlimKeys.SlimLaunch.PasswordLaunchableItem>' to 'System.Collections.Generic.IEnumerable<SlimCode.SlimKeys.SlimLaunch.LaunchableItem>'. An explicit conversion exists (are you missing a cast?)
The C++ man in me expected this. After all, the compiler is giving me the clue I was waiting for: an explicit conversion exists! So I add a ( IEnumerable<LaunchableItem> ) cast to the returned list, compile (fine), and test my code:
System.InvalidCastException occurred
Message="Unable to cast object of type 'System.Collections.Generic.List`
[SlimCode.SlimKeys.SlimLaunch.PasswordLaunchableItem]'
to type 'System.Collections.Generic.IEnumerable`
[SlimCode.SlimKeys.SlimLaunch.LaunchableItem]'."
After juggling with the possible typecasts I could do, and thinking about how it could work (or not) based on what I know about C# templates, I conclude there is no way I can cast that beast. Argh... Am I really stuck with calling ConvertAll<LaunchableItem> on my list? It's frustrating to create a copy just for enumerating PasswordLaunchableItem objects as if they were LaunchableItem objects? Please tell me I'm missed something. I'm ready to look stupid now!
Third, my solution
Well, I was not about to call that ConvertAll<> method. It was unacceptable. Instead, I created an EnumerableAs<T,U> class with a "T : U" constraint, which implements IEnumerable<U> and accepts an IEnumerable<T> at construction.
public class EnumerableAs<T,U> : IEnumerable<U>
where T : U
{
public EnumerableAs( IEnumerable<T> enumerable )
{
if( enumerable == null )
throw new ArgumentNullException( "enumerable" );
m_enumerable = enumerable;
}
#region IEnumerable<U> Members
IEnumerator<U> IEnumerable<U>.GetEnumerator()
{
return new EnumeratorAs<T, U>( m_enumerable.GetEnumerator() );
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return m_enumerable.GetEnumerator();
}
#endregion
private IEnumerable<T> m_enumerable; // = null
}
In order to implement the IEnumerable<U>.GetEnumerator() method, it uses another class called EnumeratorAs<T,U> with the same "T : U" constraint, where the real meat is. This second class implements IEnumerator<U> and accepts an IEnumerator<T> at construction (see a pattern?). Implementing the IEnumerator<U>.Current property could not be easier! It just returns the IEnumerator<T>.Current value! Dah!
public class EnumeratorAs<T, U> : IEnumerator<U>
where T : U
{
public EnumeratorAs( IEnumerator<T> enumerator )
{
if( enumerator == null )
throw new ArgumentNullException( "enumerator" );
m_enumerator = enumerator;
}
#region IEnumerator<U> Members
U IEnumerator<U>.Current
{
get { return m_enumerator.Current; }
}
#endregion
#region IDisposable Members
void IDisposable.Dispose()
{
m_enumerator.Dispose();
}
#endregion
#region IEnumerator Members
object IEnumerator.Current
{
get { return m_enumerator.Current; }
}
bool IEnumerator.MoveNext()
{
return m_enumerator.MoveNext();
}
void IEnumerator.Reset()
{
m_enumerator.Reset();
}
#endregion
private IEnumerator<T> m_enumerator; // = null
}
So? What do you think? Was there a simpler solution?