slimCODE

commUNITY
Welcome to slimCODE Sign in | Join | Help
in Search

slimCODE, aka Martin Plante

Reinventing the wheel

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?

 

Published Tuesday, January 09, 2007 4:08 PM by slimcode
Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

TommyCarlier said:

I implemented similar functionality a while ago, but with a lot less code. Since you're already using .NET 2.0, you can make use of iterators. Instead of your 2 classes, you can just have a single function:

public static class Utils

{

   public static IEnumerable<TOutput> CastCollection<TInput, TOutput>(IEnumerable<TInput> input) where TInput : TOutput

   {

       if (input == null) throw new ArgumentNullException("input");

       foreach(TInput item in input)

           yield return item;

   }

}

January 9, 2007 1:20 PM
 

slimcode said:

Wow, great! I must get used to this new "yield" thing. q;-)

Thanks!

January 9, 2007 4:25 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit