Search code examples
c#genericscovariancecontravariance

How to cast child to parent class in c# generics


I have an classes:

class AClass : CClass
class BClass : CClass

And I need to use them in follow code:

The POJO class:

class SomeObject<T> where T : CClass
    T clazz;
    setClass(T clazz){
        this.clazz = clazz;
    .....

controller class:

class ObjectExecutor{   

    private SomeObject<CClass> someObject;

    public ObjectExecutor execute(SomeObject<CClass> so){
        someObject = so; //exception when so - is the instance of AClass 
        return this;
    }

    public void build(){
        ....
    }
}

so when I try to implement it:

SomeObject<AClass> soA = new....
soA.execute(soA) //exception point
    .build()

In result I get the exception: can't cast AClass to CClass

So I need use something like:

class ObjectExecutor{   

    private SomeObject someObject; //without <> signature
         ..... ....

but 'c#' compiler does not allow this way.

How to use Generic extension in 'C#'?

screenshots:

I have two classes that extends between:

Button extend of Adapter

Interface:

Interface

Implementing:

enter image description here

And point where issue is occurred: 1.

class

2.

enter image description here

Error:

Arg "1": the type conversion from "MyTest.Terkin.ActionElement<Ranorex.Button>" in "MyTest.Terkin.ActionElement<Ranorex.Adapter>" is impossible (CS1503) - C:\Users\Valeryi\Documents\Ranorex\RanorexStudio\MyTest\Recording1.UserCode.cs:60,23

Solution

  • It's the matter of variances and covariances

    Covariance

    After discussing your problem inside the comment section I've realized that you need the covariance here as you are assigning more derived class than originally specified. Pineapple.Given waits ActionElement<Adapter> but you are trying to pass ActionElement<Button> and Button is deriving from Adapter. It means we need covariance here. As I already told you in the comment section, you can achieve covariance with generics using out keyword. Note that only interface and delegate type parameters can be specified as variant so in order to achieve that we have to create an interface and use it whenever we want covarience to be applied. Here's the code (your code from screenshots that I slightly modified):

        class Actions { }
    
        class Adapter { }
        class Button : Adapter { }
    
        // covariant generic interface
        interface IActionElement<out T> where T : Adapter
        {
            T GetAdapter();
        }
        // covariant generic interface implementation
        class ActionElement<T> : IActionElement<T> where T : Adapter
        {
            T adapter;
            public T GetAdapter()
            {
                return adapter;
            }
    
            public ActionElement(T adapter)
            {
                this.adapter = adapter;
            }
        }
    
        class Pineapple
        {
            IActionElement<Adapter> actionElement;
            Queue<Actions> queue;
    
            // note that I'm using the IActionElement interface here. Not the class that implements the interface like you do
            public Pineapple Given(IActionElement<Adapter> adapter)
            {
                actionElement = adapter;
                queue = new Queue<Actions>();
                return this;
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                // now it works
                Pineapple pineapple = new Pineapple();
                var pineappleClone = pineapple.Given(new ActionElement<Button>(new Button()));
            }
        }
    

    Contravariance

    It's a vice versa case, when you need to assign less derived class than originally specified.

        class BaseClass { }
        class DerivedClassA : BaseClass { }
        class DerivedClassB : BaseClass { }
    
        interface ISomeObject<in T> where T : BaseClass
        {
            void SetClass(T clazz);
        }
    
        class SomeRealObject<T> : ISomeObject<T> where T : BaseClass
        {
            T obj;
    
            public void SetClass(T obj)
            {
                this.obj = obj;
            }
        }
    

    And then you can use it like you this:

    ISomeObject<DerivedClassA> soA = new SomeRealObject<BaseClass>();