Leonard
Leonard

Reputation: 3092

Cast an object to a class with generics

I've got an instance of class B, which is a specialisation of an abstract class A<TInput, TOutput>. There are several variants of class B, as I've implemented it with a variety of input and outputs.

TInput and TOutput are constrained to specific input and output classes, let's call them I and O.

I am instantiating B by using Activator.CreateInstance, but since it returns an object, I need to to cast it to A<I, O>. I expect this to work as I and O are base classes (in this case B<SpecialisationOfI, SpecalisationOfO>).

This is where it fails as this cast is apparently invalid.

Pseudo code:

abstract class I  { }
abstract class O { }

abstract class A<TInput, TOutput> 
  where TInput : I
  where TOutput : O
{ 
  abstract TOutput Foo(TInput bar);
}

class Input : I { }
class Output : O { }

class B : A<Input, Output> { }

A<I, O> instance = (A<I, O>)Activator.CreateInstance(typeOfB); // <-- fail
instance.Foo(input);

Is it possible to make this work? Thank you!

edit Based on the answer that I was given, I've solved this by restructuring the code significantly based on the covariance: I moved Foo from A to an interface:

interface IA<TResult> {
   TResult Foo(object input);
}

class A<TInput, TOutput> : IA<TOutput>
  where TInput : I
  where TOutput : O {

  public TOutput Foo(object input) {
     if (!(input is TInput)) {
       throw new ArgumentException("input");
     }

     return FooImpl(input as TInput);
  }

  protected abstract TOutput FooImpl(TInput input);
}

var instance = (IA<Output>) Activator.CreateInstance(type);
instance.Foo(input);

Thank you for sharing your insight with me!

Upvotes: 0

Views: 210

Answers (1)

Jon Skeet
Jon Skeet

Reputation: 1500075

This has nothing to do with how you're creating the instance - it's a problem with generic variance. What you're essentially trying to do is similar to this:

List<object> objects = new List<string>();

And the reason it's not valid is that the next line of code might (in general) be:

objects.Add(new object());

If that's trying to add to a list which is really of type List<string>, that's bad news.

Now if you change from an abstract class to an interface, and if you're using C# 4, you can use generic covariance and generic contravariance:

interface class A<in TInput, out TOutput> 
  where TInput : I
  where TOutput : O
{ 
  abstract TOutput Foo(TInput bar);
}

That's still not going to work for your code though, as you're trying to use both input and output covariantly. Your B class will have a method like this:

Output Foo(Input bar);

... in other words, it needs input of type Input. But if you've got an A<I, O> that should be able to work for any implementation of I:

A<I, O> x = new B();                // Invalid, as discussed, to prevent...
O output = x.Foo(new SomeOtherI()); // ... this line

Upvotes: 3

Related Questions