Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a generic function as a parameter

Tags:

c#

generics

I know that what I'm doing can be done in a different way, but I'm curious about how things work. The following is a simplified code which doesn't compile, but it supposed to show my goal.

private void Execute()
{
    GeneralizedFunction("1", "2", i => Transform(i));
}

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction)
{
    A result1 = aAction(aStringA);
    B result2 = aAction(aStringB);
    // Do something with A and B here
}

T Transform<T>(string aString)
{
    return default(T);
}

Transform is a generic convertion from string to some object (think deserialization). GeneralizedFunction uses two specializations of transform: one for type A and one for type B. I know I can do this in a number of other ways (say by introducing a parameter for the type of the object), but I'm looking for explanations of whether it is possible or impossible to do this with generics/lambdas. If Transform is specialized before it is passed as a parameter to GeneralizedFunction, then it's impossible. Then the question is why this possibility is restricted.

like image 663
Max Avatar asked Feb 23 '12 11:02

Max


3 Answers

This answer doesn't explain the reason why, just how to work around the limitation.

Instead of passing an actual function, you can pass an object that has such a function:

interface IGenericFunc
{
    TResult Call<TArg,TResult>(TArg arg);
}

// ... in some class:

void Test(IGenericFunc genericFunc)
{
    // for example's sake only:
    int x = genericFunc.Call<String, int>("string");
    object y = genericFunc.Call<double, object>(2.3);
}

For your specific use case, it can be simplified to:

interface IDeserializerFunc
{
    T Call<T>(string arg);
}

// ... in some class:
void Test(IDeserializerFunc deserializer)
{
    int x = deserializer.Call<int>("3");
    double y = deserializer.Call<double>("3.2");
}
like image 69
sinelaw Avatar answered Oct 23 '22 23:10

sinelaw


What you're asking to do isn't possible using generics alone. The compiler needs to generate two typed versions of your Transform function: one to return type A and one for type B. The compiler has no way of knowing to generate this at compile time; only by running the code would it know that A and B are required.

One way to solve it would be to pass in the two versions:

private void Execute()
{
    GeneralizedFunction("1", "2", i => Transform<A>(i), i => Transform<B>(i));
}

void GeneralizedFunction(string aStringA, string aStringB, Func<string, A> aAction,  Func<string, B> bAction)
{
    A result1 = aAction(aStringA);
    B result2 = bAction(aStringB);
}

The compiler knows exactly what it needs to generate in this case.

like image 41
Tim Rogers Avatar answered Oct 23 '22 21:10

Tim Rogers


Try the following signature:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction)

(Note that GeneralizedFunction has to be generic; the compiler will automatically guess the type parameter when calling the method).

like image 1
Matthias Avatar answered Oct 23 '22 22:10

Matthias