IEnumerable<T> as IEnumerable<BaseClassOfT>

A few weeks ago I touched on the substitutability of generic types and how Collection<BaseClass> is never substitutable for Collection<SubClass>. This is because while the read facilities would be substitutable the write ones would not be. A Collection can’t contain non-SubClass classes.

One approach for dealing with collections in general recommended in .NET Framework Design Guidelines is to expose IEnumerator or IEnumerable interfaces rather than the collection itself – especially if you intend on it being read-only.

But even IEnumerator<T> and IEnumerable<T> can’t help you if you need to return not but a base-class or interface that should be substitutable for. Unless the base-class you want is object in which case seeing IEnumerable<T> inherits from the non-generic IEnumerable solves your problem.

C#’s generics aren’t clever enough to realize that an instantiated generic class might be substitutable for another generic class although. Wilco points out that the .NET CLR supports “generic contravariance” but C# doesn’t yet expose it.

In the mean time trying to passing back IEnumerator<T> to something expecting IEnumerator<BaseClassOfT> will result in a compiler error. Try cast ing it and a run-time error awaits.

One option would be to create a whole new Collection<BaseClass> and copy each SubClass element into it but this is hardly efficient or elegant.

What would be really cool is if you could somehow wrap it like;

public IEnumerable<BaseClass> BaseClasses {
    return new BaseEnumerable<BaseClass, SubClass>(subClassCollection);
}

Of course you’d need BaseEnumerator and BaseEnumerable generic classes to do this and preferably they’d be able to enforce that SubClass is of BaseClass at compile time.

So here are two classes to do just that for your enumerable pleasure.

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

public class BaseEnumerable<TBase, TSub> : IEnumerable<TBase> where TSub : TBase {
    private IEnumerable<TSub> subEnumerable;

    public BaseEnumerable(IEnumerable<TSub> subEnumerable) {
        this.subEnumerable = subEnumerable;
    }

    public IEnumerator<TBase> GetEnumerator() {
        return new BaseEnumerator<TBase, TSub>(subEnumerable);
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return subEnumerable.GetEnumerator();
    }
}

public class BaseEnumerator<TBase, TSub> : IEnumerable<TBase> where TSub : TBase {
    private IEnumerator<TSub> subEnumerator;

    public BaseEnumerator(IEnumerable<TSub> subEnumerable) {
        subEnumerator = subEnumerable.GetEnumerator();
    }

    public BaseEnumerator(IEnumerable<TSub> subEnumerator) {
        this.subEnumerator = subEnumerator;
    }

    public TBase Current {
        get { return subEnumerator.Current; }
    }

    public bool MoveNext() {
        return subEnumerator.MoveNext();
    }

    public void Reset() {
        subEnumerator.Reset();
    }
}

They work clean and fast for me but I’m no expert on the responsibilities of implementing the Dispose pattern. Warranty is not included and your mileage may vary.

[)amien

10 responses

  1. Avatar for Steve

    I really don't understand why this is an issue.

    Surely you can just use a container using instances of the base type, iterate using that, and make your polymorphism kick in for each instance of the type? We have this situation all the time with C++/STL and it's never a problem:

    std::list<BaseType*> baseTypeList;
    std::list<BaseType*>::iterator i  = baseTypeList.begin();
    
    BaseType* baseType = *i;
    // virtual methods on object itself
    baseType->somePolymorphicMethod();
    // polymorphic non-member functions
    somePolymorphicFunction(baseType);
    

    You should never need to cast the container itself, all that matters is maintaining polymorphism on the elements it contains. I think the fact that C# makes / encourages you define a subtype rather than thinking of a generic usage like a typedef (type alias) makes you think you have to make your methods polymorphic on the container, which you do not.

    Steve 25 May 2006
  2. Avatar for Steve

    The visitor pattern is very popular in C++ and Java for dealing with class hierarchies in collections.

    Basically if generalising the collection contents is useful, then the processing abstraction given to you by the visitor pattern must also be useful - unless you've used a base class collection for the wrong reason of course. IMO, when you hit these kinds of problems your first thought should be about the the appropriateness of the design, not a language feature.

    Steve 25 May 2006
  3. Avatar for Wilco Bauwer

    The problem Damien has is that you can't pass an IEnumerable to for example a method that takes an IEnumerable. He solves this by implementing a generic IEnumerable which returns an IEnumerator for a given IEnumerable.

    With this solution he can now pass objects that implement this technique to methods that take an IEnumerable

    like this:

    Foo(list.BaseClasses);

    It doesn't really have anything to do with casting or loops/iterators.

    Wilco Bauwer 26 May 2006
  4. Avatar for Steve

    I know exactly what he's trying to do, my point was that the need to do something like that suggested a possible design issue.

    My point is that I question the need for IEnumerable, period. If Derived really is-a Base, and holding a collection of Base is useful, you should not need to cast it to a collection of Derived, unless your interfaces are written in a restrictive way that requires that, at which point I question the interface design, not the language feature.

    I've been using C++ with templates (generics) for 10 years with hierarchies of objects in base-type containers and have never once needed to do this kind of conversion, because polymorphism occurs on the contained types alone.

    Steve 26 May 2006
  5. Avatar for Steve

    "you should not need to cast it to a collection of Derived"
    should have read
    "should not need to hold a collection specifically of Derived"

    Steve 27 May 2006
  6. Avatar for Steve

    Another problem with the proposed substitutability feature is that it only makes sense in very simplistic generics cases like containers of one type. If you start using more powerful generics (I don't know if C# supports partial template specialisation like C++ does, but I know it supports generics with multiple type parametes) it gets very hairy indeed. Should you be able to pass a GroovyFunctor<Base1, Derived2, SubDerived3> to a LessGroovyFunctor<Base1, Base2, Base3>? I don't think you can make that assertion globally, their behaviour may be completely different.

    IMO generic / template instances should not track their basis type's class hierarchy, it undermines their purpose as specialised adaptions of containment or external function for specific points in a class hierarchy. Personally I am completely comfortable that generics are very strongly typed such that once instantiated with a given set of types, that generic is (effectively) unique as a type, just like any other class you may define. In C++ this can never be otherwise due to the ability to mix implicit instantiation of template functions, and explicit special cases. Being able to assign implicitly between generics with different (but related) basis types would make that sort of specialisation impossible since the result would be ambiguity.

    That's not to say there isn't a purpose for a strongly-typed container that can be interpreted as a container of a base type instead - but I think that's not for generics to solve for you.

    Steve 27 May 2006
  7. Avatar for Judah

    I did something a little different:

    public IEnumerable<TBase> As<TBase, TDerived>(IEnumerable<TDerived> items) {
        foreach(TDerived derived in items) yield return derived;
    

    You can use it like this:

    IEnumerable<Square> squares = ...;
    IEnumerable<Shape> shapes = As<Shape, Square>(squares);
    

    Simple, easy, and clean. Thank you, C# yield iterators. :-)

    Judah 15 June 2007
  8. Avatar for Damien Guard

    Yeah that's a great solution :)

    Damien Guard 17 June 2007
  9. Avatar for Howard

    The code in the article isn't necessary, just use the extension method .Cast e.g.

    IEnumerable<T1> data = [some query];
    // convert to baseclass T2
    IEnumberable <T2> converted = data.Cast<T2>();
    
    Howard 20 August 2008
  10. Avatar for Damien Guard

    Ah yes, System.Linq now has the Cast extension method that does something very similar but bear in mind this article was created in 2006 long before .NET 3.5 was around.

    Damien Guard 20 August 2008