-1

With reference to InvalidCastException on Generics, I have adapted the given answer for my own application. As an example:

public interface IData
{
    string getValue();
}

public interface IController<in T> where T : IData
{
    string formString(T data);
}

public class DataImplA : IData
{
    public string getValue()
    {
        return "DataImplA";
    }
}

public class ControllerImplA : IController<DataImplA>
{
    public string formString(DataImplA data)
    {
        return "ControllerImplA, " + data.getvalue();
    }
}

public class Program
{
    private static IController<IData> controller;

    public static void main(string[] args)
    {
        // Line with error
        Program.controller = (IController<IData>)new ControllerImplA();

        Console.WriteLine(Program.controller.formString(new DataImplA()));
    }
}

During runtime, the indicated line will trigger

InvalidCastException: Unable to cast object of type 'ControllerImplA' to type 'IController`1[IData].

I tried changing to Program.controller = (IController<IData>)new ControllerImplA<DataImplA>(); but this results in the compilation error The non-generic type 'ControllerImplA' cannot be used with type arguments

I then tried changing the class definition to public class ControllerImplA<DataImplA> : IController<DataImplA> but then the new compilation error is:

The type 'DataImplA' cannot be used as type parameter 'T' in the generic type or method 'IController T'. There is no boxing conversion or type parameter conversion from 'DataImplA' to 'IData'.

I don't want to use public class ControllerImplA<T> : IController<T> because the constraint is that ControllerImplA can only use DataImplA, ControllerImplB can only use DataImplB and so on so forth.

How should I correct my original code?

Community
  • 1
  • 1
thegreatjedi
  • 2,788
  • 4
  • 28
  • 49
  • In question you linked T in IController is covariant ("out"), in your example it's contravariant ("in") which is important in this case and which is why it does not work in this example. However you cannot just change in to out, because you use type T as parameter in formString. So in short - you cannot do what you want and there is good reason for that. – Evk Mar 03 '17 at 06:43
  • 1
    Imagine you was able to cast to IController somehow. Now you pass DataImpl**B** to `formString` (where `DataImplB` also implements `IData`). But ControllerImplA which you trying to cast only accepts DataImpl**A**. What should happen? That's why it is not allowed. – Evk Mar 03 '17 at 06:46
  • Right code: `IController a = new ControllerImplA(); a = controller;` – Slava Utesinov Mar 03 '17 at 06:47
  • @SlavaUtesinov Your code works but in my actual application, a configuration file will be read during initialisation to identify which exact class to instantiate and assign to the singleton `controller`. Different client machines will have different configurations. The number of implementing classes should also be expandable in future as each class corresponds to one COTS technology. How can I achieve this? – thegreatjedi Mar 03 '17 at 07:02
  • At case of contrvariation it is not possible. – Slava Utesinov Mar 03 '17 at 07:05
  • @Evk The whole reason I'm doing this is because the original code does not use generics i.e. `public interface IController` which leads to `ControllerImplA.someMethod(IData data) {...}`. I have several such methods per controller, so I had to manually downcast the parameter in each method. I use generics to avoid casting, hence the above. Using `public class ControllerImplA : IController` doesn't address the issue which is the whole reason I used generics in the first place. – thegreatjedi Mar 03 '17 at 07:21
  • Your question is just yet another variation of the usual "why can't I cast this generic type even though the type _parameters_ seem compatible" questions. You ask _"How should I correct my original code?"_ -- answer to that is, you can't. It's not safe. If the compiler allowed you to do the cast, or anything like that, then you would have an `IController` object, then you could pass any `IData` implementation to the `formString()` method, even implementations not of the type `DataImplA` that's _required_ by the `ControllerImplA` implementation of `IController`. – Peter Duniho Mar 03 '17 at 09:21

1 Answers1

0

In my opinion, Generic class is actually a placeholder for classes. By type "T", it creates different types/classes in runtime. Which means IController<IData> and IController<DataImplA> are totally different classes, they have no inheritance relationship.

Vincent Shen
  • 83
  • 1
  • 1
  • 8