无法从具有多个返回的"选择"中的用法推断出C#类型参数

Lar*_*ann 12 c# linq generics inheritance

我不认为我做了太深奥的事情,但我没有看到任何其他问题.

下面的代码(我把它简化为要点)在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);
                    }
                });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 19

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

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

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

第7.5.2.14节是这样的:

在某些情况下,需要为一组表达式推断出通用类型.特别是,以这种方式可以找到隐式类型数组的元素类型和具有块体的匿名函数的返回类型.

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

Tr M<X>(X x1 … X xm)
Run Code Online (Sandbox Code Playgroud)

以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);
Run Code Online (Sandbox Code Playgroud)

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

我怀疑,如果你将你的任何一个返回值投射到A,它将起作用.


Chr*_*air 7

我猜这里有一个特定的C#规范条款.(编辑:Jon Skeet发现并将其发布在他的答案中)

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

在这种情况下,最好的选择是简单地转换每个返回语句以键入A或存储在临时变量中:

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

要么

A returnValue;

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

return returnValue;
Run Code Online (Sandbox Code Playgroud)

编辑:如果你没有推断类型,你可以显式调用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);
});
Run Code Online (Sandbox Code Playgroud)

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