Search code examples
c#genericscastingaction

C# casting Action generics


I have my first class like

public abstract class A
{
    protected event Action<A> onCompleted;

    public void CompleteExecution()
    {
        // doing something...
        onCompleted?.Invoke(this);
    }
}

and another class like

public class B : A
{
    private Action<B> _callback;

    public void Initialize(Action<B> callback)
    {
        _callback = callback;
    }

    public void Execute()
    {
        onCompleted += (Action<A>)callback;
        // doing something specific.
    }
}

then I use B with something like:

public class Executor
{
    private B _inst;

    public Executor
    {
        _inst = new B();
        _inst.Initialize(Callback);
    }

    public void Execute()
    {
        _inst.Execute();
    }

    public void Finalize()
    {
        _inst.CompleteExecution();
    }

    private void Callback(B inst)
    {
        // completed !!!
    }
}

I thought I could cast Action if the arguments where related but I am getting an invalid cast exception at

onCompleted += (Action<A>)callback;

in the Execute method of the B class.

I solved this by replacing the line with

onCompleted += _ => callback?.Invoke(this);

but I find it very ugly.

Is there a better way for achieving the cast?


Solution

  • Is there a better way for achieving the cast?

    No. Action<T> is contravariant, meaning you can convert Action<A> to Action<B>. This works because B inherits from A, the same reason void MyMethodA(A a) can be called with an instance of B as the argument.

    But it is not covariant, so you cannot do the reverse. For the same reason void MyMethodB(B b) cannot be called with an instance of A as the argument.

    If your current workaround works you can just use that. You might also be able to do something with generics, see the Curiously recurring template pattern. But be warned that this can increase the complexity of your solution. Or you can declare an event with the same name in both classes, and use the new modifier to hide the inherited member.