Passing a generic method as a parameter in C#

2019-09-19 02:31发布

问题:

I am trying to write a function, that takes another function as a parameter (and the calls it later) I know that its doable if I know the signature of the function beforehand, but is this possible, if I dont? (like passing a function as a parameter in JavaScript)

For example:

// this should accept a function with any kind of signature and return value
void DelayedCall(?? functionToCallLater, float delayInSecs, params object[] values)
{
    //here do something like (just as an example)
    functionToCallLater.Invoke(values);
}

EDIT: I do not want to assume anything about functionToCallLater It can look like these for example:

    int MyFunc()
    void MyFunc2()
    void MyFunc3(int someParam) 
    string MyFunc4(int someParam, MyClass someOtherParam) 
    void MyFunc5<T>(params T[] values) 

回答1:

Do you mean like this? :

void DelayedCall<T>(Action<T> functionToCallLater, 
  float delayInSecs, params T[] values)
{
    //here do something like (just as an example)
    functionToCallLater(values);
}

You have object[] as your values, but I'm assuming that you want to specify their type as well using a generic. Also, since it didn't look like your method you are calling doesn't have a return type, I used Action<T> instead of Func<T>.

Based on the comment below,

If you want to accept different signatures, you really have 2 different options. The first like SLaks said is to use reflection. This can get extremely complicated though, as you're going to have to determine the position and types of the parameters in the functionToCallLater, and line those up with the incoming parameters from the parent function. There are a lot of semantics that really make this difficult (although it is possible) and really more trouble than it's worse when the problem is generalized to handle a large number of cases.

A second and more verifiable way of handling this scenario (although not necessarily less work), is to create overloads for the method in question:

void DelayedCall<T>(Func<T> functionToCallLater, 
      float delayInSecs, params T[] values)

void DelayedCall<T,R>(Func<T,R> functionToCallLater, 
      float delayInSecs, params T[] values)

void DelayedCall<T>(Func<T,X,R> functionToCallLater, 
      float delayInSecs, params T[] values)

etc.

Depending on how complicated the method to overload is, this may be slightly tedious to almost impossible, but it will work. The real question you want to ask yourself in this scenario is, "Is this the optimal way to handle this approach?" For example, if you don't need a return value in the calling method, why allow one at all? You can always force the caller to wrap the function in another method which adheres to the method signature like so:

void DelayedCall<T>(Action<T> functionToCallLater, 
      float delayInSecs, params T[] values)
....
 DelayledCallAction<int>(
    (i) => MyMethodWhichReturnsSomething(i), 
    floadDelayInSecs, 1,2,3);

This removes some of the scenarios and functionality the method needs to handle. By reducing the possible options the method signature needs to handle, the problem becomes simpler and more manageable.



回答2:

If you don't know anything about the function, you'll need to use Reflection (and pay a performance penalty).

Make it accept Delegate, then call .DynamicInvoke().



回答3:

I think this would be probihitely verbose in C# :/

Heres a solution that can accept a function that returns or does not return a value and accepts 0 - 2 parameters:

    // signature: void MyFunc()
    void DelayedCall(Action sdf, float delayInSecs)
    {
    }

    // signature: SomeClass MyFunc()
    void DelayedCall<T>(Func<T> sdf, float delayInSecs)
    {
    }

    // signature: void MyFunc(SomeClass param1)
    void DelayedCall<T>(Action<T> sdf, float delayInSecs, T values)
    {
    }

    // signature: SomeClass MyFunc(SomeClass param1)
    void DelayedCall<T, K>(Func<T, K> sdf, float delayInSecs, T values)
    {
    }

    // signature: void MyFunc(SomeClass param1, SomeClass2 param2)
    void DelayedCall<T, K>(Action<T, K> sdf, float delayInSecs, T values, K values2)
    {
    }

    // signature: SomeClass MyFunc(SomeClass param1, SomeClass2 param2)
    void DelayedCall<T, K, L>(Func<T, K, L> sdf, float delayInSecs, T values, K values2)
    {
    }

These accept for example the following:

    void MyFunc1() {  }
    int MyFunc2() { return 6; }
    void MyFunc3(int someParam) { }
    string MyFunc4(string someParam) { return "";  }
    void MyFunc5(int someParam, string someparam2) { }
    string MyFunc6(string someParam, int someparam2) { return "";  }