简体   繁体   中英

Passing a generic function as a parameter

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.

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");
}

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.

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).

It seems the answer is "no".

When you call Transform directly, you have to specify a type parameter:

int i = Transform<int>("");

So hypothetically, if you could pass an incompletely-constructed generic function like you want to, you'd need to specify the type parameters as well:

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

So it seems to me that you could hypothetically do this, if C# had a syntax like that.

But what's the use case? Aside from transforming strings to the default value of an arbitrary type, I don't see much use for this. How could you define a function that would provide a meaningful result in either of two different types using the same series of statements?

EDIT

An analysis of why it's not possible:

When you use a lambda expression in your code, it is compiled into either a delegate or an expression tree; in this case, it's a delegate. You can't have an instance of an "open" generic type; in other words, to create an object from a generic type, all type parameters must be specified. In other words, there's no way to have an instance of a delegate without providing arguments for all of its type parameters.

One of the C# compiler's helpful features is implicit method group conversions, where the name of a method (a "method group") can be implicitly converted to a delegate type representing one of the overloads of that method. Similarly, the compiler implicitly converts a lambda expression to a delegate type. In both cases, the compiler emits code to create an instance of the delegate type (in this case, to pass it to the function). But the instance of that delegate type still needs to have a type argument for each of its type parameters.

To pass the generic function as a generic function , it seems, the compiler would need to be able to pass the method group or the lambda expression to the method without conversion , so the aAction parameter would somehow have a type of "method group" or "lambda expression." Then, the implicit conversion to a delegate type could happen at the call sites A result1 = aAction<A>(aStringA); and B result2 = aAction<B>(aStringB); . Of course, at this point, we are well into the universe of contrafactuals and hypotheticals.

The solution I came up with over lunch was this, assuming a function Deserialize<T> that takes a string containing serialized data and returns an object of type T :

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<T, string> stringGetter)
{
    A result1 = Deserialize<A>(stringGetter(aStringA));
    B result2 = Deserialize<B>(stringGetter(aStringB));
}

void Example(string serializedA, string serializedB, string pathToA, string pathToB, FileInfo a, FileInfo b)
{
    GeneralizedFunction(serializedA, serializedB, s => s);
    GeneralizedFunction(pathToA, pathToB, File.ReadAllText);
    GeneralizedFunction(a, b, fi => File.ReadAllText(fi.FullName));
}
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction)
{
    A result1 = aAction(aStringA);
    B result2 = aAction(aStringB);
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM