C# - Delegate with any amount of custom parameters

2019-03-05 05:35发布

问题:

This question already has an answer here:

  • How can I design a class to receive a delegate having an unknown number of parameters? 6 answers
  • C# How to call a method with unknown number of parameters 3 answers

I want a delegate that I can store in a variable for later use that has custom amounts of custom parameters. What I mean by that, is that I want to pus it different methods with different return types and different arguments. For example:

public double Sum (double a, double b) {return a + b;}
public char GetFirst (string a) {return a[0];}
public bool canFlipTable (object[] thingsOnIt) {return thingsOnIt.Length <= 3;}

DoTheThing<double> thing1 = new DoTheThing<double>(Sum);
DoTheThing<char> thing2 = new DoTheThing<char>(GetFirst);
DoTheThing<bool> thing3 = new DoTheThing<bool>(canFlipTable);

thing1.Call(10.3, 5.6);  //15.9
thing2.Call("Hello World");  //'H'
thing3.Call(new object[] {new Lamp(), new Laptop(), new CoffeMug()});  //true

I figured out the return value and the call method already, but I'm having a problem with storing the methods

If I use "public DoTheThing(Action method)" it says, that the arguments doesn't match I even tried with a delegate that had "params object[] p" as arguments, but it didn't work either

EDIT: I forgot to tell, the method WILL always have a return type and at least 1 parameter

EDIT 2: My goal is creating a wrapper class, that caches outputs from very expensive methods and if the same thing gets called again, it returns the cached value. Of course I could solve this with an interface, but I want to do this with classes that I can't simply edit and I want to make this felxible too, so having the cache at the same place where I call the method is not an option either.

My code sofar:

public class DoTheThing <T>
{
    public delegate T Method(params object[] parameters);

    Func<T> method;
    ParameterInfo[] pInfo;

    public DoTheThing (Method method)
    {
        this.method = method;
        Type type = typeof(Method);
        MethodInfo info = type.GetMethod ("Invoke");
        if (info.ReturnType != typeof(T)) {
            throw new Exception ("Type of DoTheThing and method don't match");
        }
        pInfo = info.GetParameters ();
    }

    public T Call (params object[] parameters) {
        if (parameters.Length != pInfo.Length) {
            throw new Exception ("Wrong number of arguments, " + parameters.Length + " instead of " + pInfo.Length);
            return default(T);
        }

        for (int i = 0; i < parameters.Length; i++) {
            if (pInfo[i].ParameterType != parameters[i].GetType()) {
                throw new Exception ("Wrong parameter: " + parameters [i].GetType () + " instead of " + pInfo [i].ParameterType + " at position: " + i);
                return default(T);
            }
        }

        return (T)method.DynamicInvoke (parameters);
    }
}

回答1:

Before trying to figure how to do it, I would really question the problem that leads me to have such a kind of delegate. I would bet if I knew the context better, there would be a solution that would eliminate your requirement.

Having that said, delegates are classes that inherit from MulticastDelegate. In fact, when you declare a delegate, you are creating a new class type with MulticastDelegate as its base class. That means the following code works:

    public static double Sum(double a, double b)
    {
        return a + b;
    }

    public static string SayHello()
    {
        return "Hello";
    }

    static void Main(string[] args)
    {
        MulticastDelegate mydel = new Func<double, double, double>(Sum);
        var ret = mydel.DynamicInvoke(1, 2);
        System.Console.WriteLine(ret);

        mydel = new Func<string>(SayHello);
        ret = mydel.DynamicInvoke();
        System.Console.WriteLine(ret);


        mydel = new Func<string, int, string> ((s, i) => { 
            return $"Would be {s}, {i} times";
         });
        ret = mydel.DynamicInvoke("Hello", 5);
        System.Console.WriteLine(ret);
    }

Because "mydel" variable is of the base class type (MulticastDelegate), we can actually use it with any kind of delegate and invoke it with arbitrary parameters. If they don't match the method being invoked, it will throw at runtime.



标签: c# delegates