0

I have classes B and C inheriting from A.

public class A { }

public class B : A { }

public class C : A { }

I have some functions that take objects B and C as arguments:

public void FunB(B obj) { }

public void FunC(C obj) { }

I want to create a function that accepts any of these functions as arguments. I try to use delegates, but I don't find the solution. I tried the this, but I get the following errors:

public class Test
{
    public void FunB(B obj) { }
    public void FunC(C obj) { }

    public delegate void Delegate(A obj);

    public static void Method(Delegate fun) { }

    public void Pain() 
    { 
        Delegate funB = FunB; // No overload for FunB matches the delegate Test.Delegate

        Delegate fun2 = (A obj) => { };

        fun2 += FunB; // No overload for FunB matches the delegate Test.Delegate

        Method(FunB); // Argument 1: cannot convert from 'method group' to 'Test.Delegate'
    }
}

I read the documentation (that's why I tried with the fun2 += FunB) but it's clear there is something I'm not understanding. Any idea how to fix it?

xavier
  • 1,860
  • 4
  • 18
  • 46
  • 2
    `FunB` has been written with the expectation that whatever it's passed is *at least* a `B` or something derived from it. If you could cast to your delegate, you'd be able to invoke it passing a `C`. The type system is saving you from this mistake. – Damien_The_Unbeliever Nov 11 '21 at 14:45
  • 2
    The variance works in the other direction. If you have a `FunA(A obj)` you should be able to assign that to a `Delegate(C obj)` because *that function can safely deal with any `A`, including obviously if it must be a `C`*. – Damien_The_Unbeliever Nov 11 '21 at 14:47
  • Right, now I see, thank you. I had read the contravariance in the wrong direction. Then, is there any way to create a method that accepts functions with arguments deriving from a parent class? – xavier Nov 11 '21 at 14:58
  • I changed the title of the question accordingly. I thought about using generics, but it can't be *any* generic, it should be an object with base class `A`... – xavier Nov 11 '21 at 15:03
  • Ok, I think doing something like `public static void Method(Foo fun) where T: A { }` would work... – xavier Nov 11 '21 at 15:06

1 Answers1

0

You can write a method like this to adapt an Action<B> to be an Action<A> (you should generally prefer to use these generic delegates anyway these days):

    public Action<TA> ActWithParent<TA, TB> (Action<TB> original) where TB: TA
    {
        return a =>
        {
            if(a is TB b)
            {
                original(b);
            }
        };
    }

    public void Pain()
    {

        Action<A> fun2 = ActWithParent<A, B>(FunB);
        fun2 += ActWithParent<A,C>(FunC);
    }

There's probably a better name for it, but notice that it only invokes the "child" method if it's actually got the matching type, which is the additional check you need to insert between caller and callee here. Note also that you may wish to specify different behaviour if there isn't a type match.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448