IEnumerable<T> as IEnumerable<BaseClassOfT>
- 📅
- 📝 525 words
- 🕙 3 minutes
- 📦 .NET
- 🏷️ C#
- 💬 10 responses
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<SubClass>
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 <T>
but a base-class or interface that <T>
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 to IEnumerable<T> as IEnumerable<BaseClassOfT>
Ah yes, System.Linq now has the
Cast<T>
extension method that does something very similar but bear in mind this article was created in 2006 long before .NET 3.5 was around.The code in the article isn’t necessary, just use the extension method .Cast e.g.
Yeah that’s a great solution :)
I did something a little different:
You can use it like this:
Simple, easy, and clean. Thank you, C# yield iterators. :-)
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 aLessGroovyFunctor<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.
“you should not need to cast it to a collection of Derived”
“should not need to hold a collection specifically of Derived”
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<Derived>
, 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.
The problem Damien has is that you can’t pass an
IEnumerable<Derived>
to for example a method that takes anIEnumerable<Base>
. He solves this by implementing a generic IEnumerable which returns anIEnumerator<Base>
for a givenIEnumerable<Derived>
.With this solution he can now pass objects that implement this technique to methods that take an
IEnumerable<Base>
like this:It doesn’t really have anything to do with casting or loops/iterators.
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.
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:
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.