Darrel Hoffman
Darrel Hoffman

Reputation: 4636

Get list of all items of a generic type from list which could contain other types

Okay, this is tricky. Say I have a list:

List<Foo> MyList;

Foo is a common abstract class which is used for all components in the system. Some of these component classes are generic:

public class Bar<T> : Foo
{
    public void MethodCommonToAllBar()
    {
        ...
    }
}

And some classes may extend the generic ones:

public class Baz : Bar<int>
public class Bat : Bar<string>

I'd like a way to extract from MyList a set of all objects of type Bar<anything>, so that I may call MethodCommonToAllBar() on each of those things. If it weren't for the generic here, I could use a line like:

foreach (Baz baz in MyList.Where(x => x is Baz))
{
    baz.MethodCommonToAllBar();
}
foreach (Bat bat in MyList.Where(x => x is Bat))
{
    bat.MethodCommonToAllBar();
}

or even

foreach (Bar<int> bar in MyList.Where(x => x is Bar<int>))
{
    bar.MethodCommonToAllBar();
}
foreach (Bar<string> bar in MyList.Where(x => x is Bar<string>))
{
    bar.MethodCommonToAllBar();
}

But there doesn't seem to be any way to get all objects of any type of Bar unless I treat each potential subtype separately. Is there a workaround for this? Assume that I can't change the class structure in any way. (I could, but it's a big project, and such a change would impact a lot of people, so it's unlikely I'd get it approved. Ideally, I'd add another non-generic class between Foo and Bar<T> in the hierarchy to store that method and any other such common things and just search for that. But it's a bit late in the game to go refactoring that now.) I can also see a workaround involving Reflection, but that seems like overkill, and would likely incur a performance hit to boot. Is there any other way to handle this cleanly?

Upvotes: 2

Views: 167

Answers (4)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112259

You must you be aware of the fact that the two types Bar<X> and Bar<Y> are not compatible! They are just two different types deriving from a common ancestor. The fact that they are both concrete implementations of the same open generic type makes no difference. And how do you want to process them? Assume that you retrieved an object of type Bar<?> of an unknown generic type from the list and that you have a method

void ProcessBar<T>() { ... }

Where do you know the right value of T from? Note that T is resolved at compile time, not at runtime! This is what generics are for: to provide certain flexibility by keeping type safety. And type safety can only be achieved at compile time.

One way of solving this problem is in fact to introduce a non-generic intermediate type in your inheritance hierarchy. If you cannot change the inheritance hierachy, let all Bar<T> implement a non-generic interface IBar

public interface IBar
{
    void MethodCommonToAllBar();
}

and simply filter your list with

foreach (IBar bar in MyList.OfType<IBar>())
{
    bar.MethodCommonToAllBar(); // Method defined in IBar.
}

Note that OfType<> does both, filter and cast to the required type.

Another way is to not use generics at all and to work with the object type in the Bar class for maximum dynamic flexibility.

Type safety and dynamic flexibility are opponents.

Upvotes: 3

Shay
Shay

Reputation: 1768

How about this?

MyList.OfType<Bar<string>>().ToList()
    .ForEach(item => item.MethodCommonToAllBar());

Upvotes: 0

Erti-Chris Eelmaa
Erti-Chris Eelmaa

Reputation: 26268

Well, there is such concepts as contravariance, covariance.

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments.Covariance preserves assignment compatibility and contravariance reverses it. (Eric Lippert)

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

It might or it might not help you. Basically, if your class implements IFoo<out T>, you can treat that class as a IFoo<object> without knowing the actual generic type.

Upvotes: 2

Dmitry
Dmitry

Reputation: 14059

If the Bar<T>.MethodCommonToAllBar method doesn't depend on the type T, you could define an interface to be common for all bars:

public interface IBar
{
    void MethodCommonToAllBar();
}

public class Bar<T> : Foo, IBar
{
    public void MethodCommonToAllBar()
    {
        ...
    }
}

Then you'll be able to cast any Bar<T> to this interface:

foreach (IBar bar in MyList.OfType<IBar>())
{
    bar.MethodCommonToAllBar();
}

Upvotes: 2

Related Questions