将泛型函数作为参数传递

本文关键字:参数传递 函数 泛型 | 更新日期: 2023-09-27 18:25:44

我知道我正在做的事情可以用不同的方式完成,但我很好奇事情是如何运作的。下面是一个不编译的简化代码,但它应该显示我的目标。

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是从字符串到某个对象的一般转换(想想反序列化)。GeneralizedFunction使用了两种转换的特殊化:一种用于类型A,另一种用于B。我知道我可以通过多种其他方式来实现这一点(比如为对象的类型引入一个参数),但我正在寻找用泛型/lambdas实现这一目标是可能还是不可能的解释。如果Transform在作为参数传递给GeneralizedFunction之前是专门化的,那么这是不可能的。那么问题来了,为什么这种可能性会受到限制。

将泛型函数作为参数传递

这个答案并没有解释为什么,只是如何绕过限制。

您可以传递一个具有以下功能的对象,而不是传递一个实际的函数:

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

对于您的特定用例,它可以简化为:

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

您要做的是仅使用泛型是不可能的。编译器需要生成Transform函数的两个类型化版本:一个用于返回类型A,另一个用于类型B。编译器没有办法知道在编译时生成这个;只有运行代码,它才会知道A和B是必需的。

解决这个问题的一种方法是通过两个版本:

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

编译器确切地知道在这种情况下需要生成什么。

尝试以下签名:

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

(请注意,GeneralizedFunction必须是泛型的;编译器在调用该方法时会自动猜测类型参数)。

答案似乎是"否"。

当您直接调用Transform时,您必须指定一个类型参数:

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

因此,假设你可以像你想要的那样传递一个不完全构建的泛型函数,你也需要指定类型参数:

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
}

所以在我看来,如果C#有这样的语法,你可以假设这样做。

但是用例是什么?除了将字符串转换为任意类型的默认值之外,我认为这没有多大用处。如何定义一个函数,使用同一系列语句在两种不同类型中的任何一种类型中提供有意义的结果?

编辑

原因分析:

当您在代码中使用lambda表达式时,它会编译为委托或表达式树;在这种情况下,它是一个委托。你不能有一个"开放"泛型类型的实例;换句话说,要从泛型类型创建对象,必须指定所有类型参数。换言之,如果不为委托的所有类型参数提供参数,就无法获得委托的实例。

C#编译器的一个有用功能是隐式方法组转换,其中方法的名称("方法组")可以隐式转换为代表该方法重载之一的委托类型。类似地,编译器将lambda表达式隐式转换为委托类型。在这两种情况下,编译器都会发出代码来创建委托类型的实例(在这种情况下,将其传递给函数)。但是该委托类型的实例仍然需要为其每个类型参数都有一个类型参数。

要将泛型函数作为泛型函数传递,编译器似乎需要能够将方法组lambda表达式传递给方法,而无需转换。因此aAction参数将以某种方式具有"方法组"或"lambda表达式"的类型。然后,向委托类型的隐式转换可能发生在呼叫站点CCD_ 8和CCD_。当然,在这一点上,我们已经深入到了反事实和假设的宇宙中。

我在午餐时想出的解决方案是这样的,假设函数Deserialize<T>接受一个包含序列化数据的字符串,并返回一个类型为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);
}