14

我不认为我在做任何太深奥的事情,但我没有看到任何其他关于此的问题。

以下代码(我已将其简化为基本代码)在 C# 4 中生成编译器错误。但是,类型参数是什么应该是显而易见的 - 有一个最大公分母(“A 类”)也明确定义在方法“Frob”的返回类型。编译器不应该列出 lambda 表达式中的所有返回类型,创建一个祖先树来查找它们的共同祖先,然后将其与包含方法的预期返回类型相协调吗?

无法从用法中推断方法“System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)”的类型参数。尝试明确指定类型参数。

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Sample
{
    public abstract class A
    {
        private A(int index) { /* ... */ }

        public sealed class A1 : A
        {
            public A1(string text, int index)
                : base(index)
            { /* ... */ }
        }

        public sealed class A2 : A
        {
            public A2(int index)
                : base(index)
            { /* ... */ }
        }

        private static Regex _regex = new Regex(@"(to be)|(not to be)");
        public static IEnumerable<A> Frob(string frobbable)
        {
            return _regex.Matches(frobbable)
                .Cast<Match>()
                .Select((match, i) =>
                {
                    if (match.Groups[1].Success)
                    {
                        return new A1(match.Groups[1].Value, i);
                    }
                    else
                    {
                        return new A2(i);
                    }
                });
        }
    }
}
4

2 回答 2

21

这是 C# 4 规范的第 7.5.2.12 节:

在类型推断和重载决策期间使用匿名函数 F 的推断返回类型。推断的返回类型只能为所有参数类型已知的匿名函数确定,因为它们是显式给出的,通过匿名函数转换提供的,或者是在封闭泛型方法调用的类型推断期间推断的。推断的返回类型确定如下:

  • 如果 F 的主体是一个表达式,那么推断的 F 的返回类型就是该表达式的类型。
  • 如果 F 的主体是一个块,并且块的返回语句中的表达式集具有最佳公共类型 T(第 7.5.2.14 节),则推断的 F 的返回类型是 T。
  • 否则,无法为 E 推断返回类型。

第 7.5.2.14 节是这样的:

在某些情况下,需要为一组表达式推断一个公共类型。特别是隐式类型数组的元素类型和带有块体的匿名函数的返回类型都是通过这种方式找到的。

直观地说,给定一组表达式 E1…Em 这个推断应该等价于调用一个方法

Tr M<X>(X x1 … X xm)

以 Ei 作为论据。

更准确地说,推理从一个不固定的类型变量 X 开始。然后从每个 Ei 到 X 进行输出类型推理。最后,X 是固定的,如果成功,则生成的类型 S 是表达式的最佳通用类型。如果不存在这样的 S,则表达式没有最佳公共类型。

所以,假设我们有:

void M<X>(X x1, X x2) {}

A1 a1 = new A1();
A2 a2 = new A2();
M(a1, a2);

...这将无法确定 的类型参数X,因此返回值推断以同样的方式失败。

我怀疑如果您将任何一个返回值转换为A,它将起作用。

于 2012-07-20T22:14:41.127 回答
8

我猜想某处有一个特定的 C# 规范条款规定了这一点。(编辑:Jon Skeet 找到它并将它们发布在他的答案中)

通常,此类 lambda(或三元运算等)需要在每个阶段具有相同的确切返回类型以避免歧义。例如,在您的情况下,您是要返回类型A还是Object?当您将接口或多级继承混合在一起时,会更加有趣。

在这种情况下,最好的办法是简单地将每个 return 语句转换为类型A或将其存储在临时变量中:

if (match.Groups[1].Success)
    return (A)(new A1(match.Groups[1].Value, i));
else
    return (A)(new A2(i));

或者

A returnValue;

if (match.Groups[1].Success)
    returnValue = new A1(match.Groups[1].Value, i);
else
    returnValue = new A2(i);

return returnValue;

编辑:如果没有推断类型就可以了,您可以Select使用以下命令显式调用查询:

.Cast<Match>()
.Select<Match, A>((match, i) =>
{
    if (match.Groups[1].Success)
        return new A1(match.Groups[1].Value, i);
    else
        return new A2(i);
});

然后编译器将确保您的返回类型隐式兼容A(它们是)

于 2012-07-20T22:07:55.693 回答