.NET:静态方法上的推断泛型类型

16
假设我有:

public static List<T2> Map<T,T2>(List<T> inputs, Func<T, T2> f)
{
    return inputs.ConvertAll((x) => f(x));
}

private int Square(int x) { return x*x; }

public void Run()
{
    var inputs = new List<Int32>(new int[]{2,4,8,16,32,64,128,256,512,1024,2048});

    // this does not compile
    var outputs = Map(inputs, Square); 

    // this is fine
    var outputs2 = Map<Int32,Int32>(inputs, Square);

    // this is also fine (thanks, Jason)
    var outputs2 = Map<Int32,Int32>(inputs, (x)=>x*x);

    // also fine
    var outputs2 = Map(inputs, (x)=>x*x);
}

为什么它不能编译?

编辑:错误是:

error CS0411:无法从使用中推断出方法“Namespace.Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)”的类型参数。尝试显式指定类型参数。

为什么我必须指定Map()函数的类型?它不能从传递的Func<T>(在我的情况下是Square)中推断出吗?


答案是否与
C# 3.0泛型类型推断 - 将委托作为函数参数传递相同?


有趣。你介意把编译器错误贴出来吗? - Esteban Araya
@tehMick:结果将是相同的。类型是推断出来的。 - Esteban Araya
错误 1 方法 'WindowsFormsApplication1.Class4.Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)' 的类型参数无法从用法中推断出来。请尝试显式指定类型参数。 - John K
第一种类型是根据您提供的第一个参数的类型推断出来的,就像Esteban在上面所说的那样。 - Eric Mickelsen
@tehMick:你说得对,第一个是推断的。我是在谈论它无法解析第二个类型参数T2与Square的能力。@Jason关于方法组的答案阐明了这一点。 - John K
显示剩余2条评论
5个回答

11

从您的错误信息中可以看到:

方法“[...].Map<T,T2>(System.Collections.Generic.List<T>,System.Func<T,T2>)”的类型参数不能从使用中推断出。尝试显式指定类型参数。

请注意,错误消息表示它无法解析其中一个类型参数 TT2。这是由于规范的§25.6.4(类型参数推断)部分引起的。 这是规范中处理推断泛型类型参数的部分。

如果以下任何一项为真,则不会从参数中推断任何内容(但类型推理成功):

[...]

参数是方法组。

因此,编译器无法使用 Square 的委托类型推断出 T2 的类型。 请注意,如果将声明更改为

public static List<T> Map<T>(List<T> inputs, Func<T, T> f) {
        return inputs.ConvertAll((x) => f(x));
}

那么

var outputs = Map(inputs, Square);

是合法的。在这种情况下,从inputsList<int>的事实可以得出Tint

现在,更深层次的问题是为什么上述是规范?也就是说,为什么方法组不在类型参数解析中起作用?我认为这是因为存在以下情况:

class Program {
    public static T M<T>(Func<T, T> f) {
        return default(T);
    }

    public static int F(int i) {
        return i;
    }

    public static float F(float f) {
        return f;
    }

    static void Main(string[] args) {
        M(F); // which F am I?
    }
}

1
为了进行多重验证,来自MSDN C#论坛的IanG似乎也说了同样的话,并深入探讨了规范为什么这样说:http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/a4847737-4a6b-4fcd-89f2-1b213aaf8422 我也觉得这有点启发性。 - John K
Map<T>(List<T> inputs, Func<T, T> f)?如果输入和输出是相同类型,那还谈什么映射函数呢?;) - Juliet
@朱丽叶:这里是“方法”而不是“映射”。 :-) - jason
@Jason,我认为Juliet是在评论Map函数,而不是你对于为什么没有通用类型推断的命名方法组的说明。@Juliet,拥有一个返回与输入相同类型的值的函数(例如Square)是显然合理的。Map的重点是将该函数应用于一系列输入,并生成一系列输出。即使输入和输出是相同类型的(在这种情况下是int),它仍然是一个Map函数。这可能是一个特殊情况,但它仍然是一个Map操作。 - Cheeso

2
推理过程无法推断委托的类型,而不是列表:
// this is also fine
var outputs3 = Map(inputs, new Func<int, int>(Square));

// more calls that compile correctly
var outputs4 = Map(inputs, x => Square(x));

var outputs5 = Map(inputs, x => x * x);

Func<int, int> t = Square;
var outputs6 = Map(inputs, t);

我不知道为什么,可能是因为从 Square 的签名到 Func<Int32, Int32> 没有隐式的类型转换?看起来很奇怪,Func<int, int> t = Square; 是有效的,但编译器却无法自动完成... 是bug吗?


阅读错误信息。方法“[...].Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)”的类型参数无法确定其泛型类型参数是什么。它可以解析T,因为inputs是一个List<int>,所以它知道Tint。因此,它无法解析T2。请参见我的答案。 - jason
我不明白为什么这个答案有赞成票,它是错误的。它没有在推断委托类型时失败,而是无法使用方法组来推断其中一个类型参数。 - jason
委托需要T2作为泛型参数,编译器无法推断出T2的类型(正如您所指出的那样,符合规范),因此编译器无法推断出委托的类型。虽然这不是根本原因 - 您的解释显然是正确的 - 但从技术上讲也不是错误的。 - Dathan

1
这个方法不起作用的原因是,为了让C#对一个方法进行类型推断,它必须知道转换另一端的委托类型。但是在此时此刻,目标委托类型仍然没有完全确定 - 只有T(int)是已知的,T2仍未解决。
Func<int, int> f = Square;
//works because we provided the destination type
//of the conversion from Square to delegate

Map(inputs, i => Square(i));
//works because the lambda follows the actual method call
//and determines its own return type

1

经过一番调查,我发现你对另一个答案的怀疑是正确的。以下是 C# 3.0 规范中的内容:

7.4.2.1 - 对于每个方法参数 Ei:如果 Ei 是匿名函数或方法组,则进行显式参数类型推断(7.4.2.7)... 7.4.2.7 - ...如果 E 是具有参数类型 U1…Uk 的显式类型匿名函数,而 T 是具有参数类型 V1…Vk 的委托类型,则对于每个 Ui,从 Ui 中进行精确推断(§7.4.2.8),以获得相应的 Vi。

换句话说,匿名方法和方法组(如 Square)只能 显式地 推断参数类型。我认为你所提到的答案末尾的解释已经很好地概括了这一点。由于无法从方法组中隐式地进行类型推断,编译器甚至不会尝试,这是规范要求的。


0
以下代码也可以正常工作,但我不知道原因:
var outputs = Map(inputs, i => Square(i));

2
因为与方法组不同,Lambda表达式在类型参数推断中确实发挥了作用。参见http://msdn.microsoft.com/en-us/library/ms364047(VS.80).aspx#cs3spec_topic4 - jason

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接