为什么不能推断这些泛型类型?

6

我有以下代码:

public static class CardView {
    public static object Column<TModel, TResult>(Func<TModel, TResult> field) {
        return null;
    }
}

public class Person 
{
    public string Name { get; set; }
    public bool Gender { get; set; }
}

void Main()
{
    var model = new Person() { Name = "Andre", Gender = true };

    var b = CardView.Column(model => model.Name); // ERROR
    // The type arguments for method 'UserQuery.CardView.Column<TModel,TResult>(System.Func<TModel,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
}

由于某种原因,它无法推断出Column方法的泛型类型。我需要知道为什么。我不能放弃类型推断并自己指定类型,因为这只是一个大问题的案例研究,在那里它将是不可或缺的。
编辑:
我拼错了代码=/已经修复

3
你如何期望它推断出 TModel?提供的 lambda 表达式对于任何具有 Name 属性的类都是“有效”的。 - Damien_The_Unbeliever
2
你是否期望.Column方法能够确定你的意思是将m表示为model - Marc
@Damien_The_Unbeliever,我认为这不是问题,因为你可以说:new List<Person>().Select(m => m.Name),这将为您提供一个IEnumerable<string>,而不依赖于Person对象。 - Ivaylo Slavov
@EricLippert,我想表达的是这个问题与“Person”类型无关。对于任何其他类型,仍然存在相同的问题,并且Linq示例适用于任何其他类型(假设lambda对所使用的类型具有有效的代码)。 - Ivaylo Slavov
@IvayloSlavov:我的评论是针对原帖作者而不是你。关于你对Damien的回复:你是错误的。在你的例子中,类型推断引擎有一个额外的事实,即编译器已知列表的类型为List<Person>。当推断调用Select的两个类型参数时,类型推断引擎确实依赖于知道集合元素的类型。 - Eric Lippert
显示剩余3条评论
4个回答

7
为了让编译器进行类型推断,必须提供某种类型信息。在这种情况下,唯一提供的信息是一个未标记类型的 lambda 表达式。编译器真的没有任何提示。有几种方法可以解决这个问题。最简单的方法是向 lambda 参数添加类型。
var b = CardView.Column((Person m) => m.Name);

换句话说,编译器不能将变量m的类型同时视为方法的泛型参数TModelFunc的参数,对吗? - Ivaylo Slavov
@IvayloSlavov 不,它可能有,但需要放在哪种类型上?这里没有要查看的类型。只有3个未分类项目:方法、lambda和lambda参数。 - JaredPar
我的意思是,没有任何东西将两个类型参数连接起来以使用相同的类型。如果我们有这样的代码:public static object Column<TModel, TResult>(Func<TModel, TResult> field)和这个调用:CardView.Column(m, p => p.Name);问题就不存在了。当然,"Column"的代码应该将第一个参数传递给lambda函数。 - Ivaylo Slavov

2
技术上的答案是“因为规范没有说明你可以这样做”。引用规范,第7.5.2节(类型推断):
“7.5.2.1 第一阶段
对于每个方法参数 Ei: 1. 如果 Ei 是匿名函数,则从 Ei 到 Ti 进行显式参数类型推断(§7.5.2.7)。 2. 否则,如果 Ei 具有类型 U,并且 xi 是值参数,则从 U 到 Ti 进行下界推断。 3. 否则,如果 Ei 具有类型 U,并且 xi 是 ref 或 out 参数,则从 U 到 Ti 进行精确推断。 4. 否则,不对此参数进行推断。”
以及:
7.5.2.7 显式参数类型推断
显式参数类型推断是从表达式 E 推断到类型 T 的一种方式,具体如下:
如果 E 是一个具有参数类型 U1…Uk 的显式类型的匿名函数,而 T 是具有参数类型 V1…Vk 的委托类型或表达式树类型,则对于每个 Ui,都会从 Ui 到相应的 Vi 进行精确推断(§7.5.2.8)。
由于你示例中的匿名函数没有显式类型,因此无法进行推断。这也告诉我们,要进行推断,可以通过指定函数的类型来帮助进行推断:
var b = CardView.Column((Person m) => m.Name); 

现在这明确是一个 Func<Person, string>,因此推断成功。

1

在该方法签名中没有TModel参数(或使用TModel的通用类型),因此编译器不知道m的类型。


0

你编辑的代码无法编译。你不能使用model作为lambda参数,因为你已经将其声明为局部变量。看起来你想通过使用该类型的已声明局部变量告诉编译器lambda参数是一个Person。但这样做行不通。

告知编译器类型的正确方法是在lambda中显式声明它,就像其他人所说的那样:

var b = CardView.Column((Person m) => m.Name); 

或者为泛型方法提供类型参数:

var b = CardView.Column<Person, string>(m => m.Name); 

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