将泛型函数作为参数传递

时间:2012-02-23 11:48:33

标签: c# generics

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

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之前是专用的,那么这是不可能的。那么问题是为什么这种可能性受到限制。

5 个答案:

答案 0 :(得分:7)

这个答案并没有解释为什么的原因,只是如何来解决这个限制。

您可以传递具有此类函数的对象,而不是传递实际函数:

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

答案 1 :(得分:4)

单独使用仿制药是不可能要做的。编译器需要生成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);
}

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

答案 2 :(得分:1)

尝试以下签名:

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

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

答案 3 :(得分:1)

似乎答案是“不”。

直接拨打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表达式< / em>到方法没有转换,因此aAction参数会以某种方式具有“方法组”或“lambda表达式”的类型。然后,可以在呼叫站点A result1 = aAction<A>(aStringA);B result2 = aAction<B>(aStringB);上隐式转换为委托类型。当然,在这一点上,我们很好地融入了反事实和假设的世界。

我在午餐时提出的解决方案就是这样,假设函数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));
}

答案 4 :(得分:0)

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