编译器/运行时如何确定lambda表达式的类型?

时间:2018-04-05 16:24:08

标签: c# linq lambda

我想知道编译器/运行时如何确定lambda表达式的类型?

例如,以下System.Linq Select扩展方法(非选择查询)

//var recordIds = new List<int>(records.Select(r => r?.RecordId ?? 0));
//var recordIds = new List<int>(records.Where(r => r != null).Select(r => r.RecordId));
var recordIds = new List<int>(records.Select(r => r.RecordId));

定义为

Enumerable.Select<TSource, TResult> Method (IEnumerable<TSource>, Func<TSource, TResult>)

所以将lambda r => r.RecordId作为Func<TSource, TResult>

如何确定lambda的类型,一旦确定,它是否只是转换为该类型?

2 个答案:

答案 0 :(得分:7)

  

我想知道编译器/运行时如何确定lambda表达式的类型?

这很复杂。实现这个功能是&#34;长杆&#34;对于发布C#3的Visual Studio版本 - 所以我每天都在按计划进行,这是VS会滑倒的一天! - 在C#4中引入协方差和逆变,这个特征变得更加复杂。

正如其他人所说,请参阅规范以获取确切的详细信息。您可能还会阅读我多年来撰写的有关它的各种文章。

我可以给你一个快速概述。假设我们有

records.Select(r => r.RecordId)

其中records的类型为IEnumerable<Record>,而RecordId的类型为int

第一个问题是&#34; IEnumerable<Record>是否有任何适用的方法Select?不,不是的。因此,我们转到扩展方法。

第二个问题是:&#34;是否存在任何适用于此呼叫的可访问扩展方法的静态类型?&#34;

SomeType.Select(records, r => r.RecordId)

我们假设Enumerable是唯一的此类型。它有两个版本的Select,其中一个采用带有两个参数的lambda。我们可以自动丢弃那个。正如你所注意到的,这让我们离开了:

static IEnumerable<R> Select<A, R>(IEnumerable<A>, Func<A, R>)

第三个问题:我们能否推断出类型参数AR对应的类型参数?

在第一轮类型推断中,我们考虑所有非lambda参数。我们只有一个。我们推断A可能是Record。但是,IEnumerable<T>是协变的,因此我们注意它也可以是比Record更通用的类型。它不能是比Record更具体的类型。

现在我们问&#34;我们完成了吗?&#34;不,我们还不知道R

&#34;还有任何推论吗?&#34;是。我们还没有检查过lambda。

&#34;是否有任何关于A的矛盾或其他事实需要了解?&#34;不。

因此我们&#34;修复&#34; ARecord继续前进。到目前为止我们知道什么?我们有:

static IEnumerable<R> Select<Record, R>(IEnumerable<Record>, Func<Record, R>)

然后我们说好,参数必须是(Record r) => r.RecordId。我们能否知道这个lambda的返回类型?显然是的,它是int。我们在R上写了一条说明可能是int的说明。

我们完成了吗?是。还有什么可以扣除的吗?没有。我们推断出所有类型参数吗?是。所以我们&#34;修复&#34; Rint,我们已经完成了。

现在我们进行最后一轮检查以确保Select<Record, int>不会产生任何错误;例如,如果Select具有<Record, int>违反的通用约束,我们会在此时拒绝它。

我们推断records.Select(r=>r.RecordId)Enumerable.Select<Record, int>(records, (Record r) => { return (int)r.RecordId; } )的含义相同,并且是对该方法的合法调用,因此我们已完成。

引入多个边界时,事情变得更加复杂。看看你是否可以弄清楚Join之类的东西是如何工作的,其中有四种类型参数可供推断。

答案 1 :(得分:2)

我认为寻找此类信息的最佳位置是语言规范:

第7.15节

  

匿名函数是表示“内联”的表达式   方法定义。 匿名函数没有值或类型   本身,但可以转换为兼容的代表或   表达式树类型。评估匿名函数   转换取决于转换的目标类型:如果是   委托类型,转换计算为委托值   引用匿名函数定义的方法。如果是   表达式树类型,转换求值为表达式   表示方法结构作为对象的树   结构

上面解释了lambda表达式的本质,基本上它们本身没有类型,不像是int文字5。这里需要注意的另一点是,lambda表达式不像你说的那样 casted 到委托类型。它是评估的,就像3 + 5被评估为int类型的方式一样。

7.5.2节中描述了如何计算出这些类型(类型推断)。

  

...假设泛型方法具有以下签名:

     

T r M < X 1 ... X n >(T 1   x 1 ... T m x m

     

使用M(E 1 ... E m )形式的方法调用   类型推断的任务是找到唯一的类型参数   对于每个类型参数,S 1 ... S n   X 1 ... X n 以便调用   M1内容S <子>名词&GT;(E <子> 1 ... E <子>米)变   有效的。

整个算法都有很好的文档记录,但在此处发布它有点长。所以这里是下载语言规范的链接。

https://msdn.microsoft.com/en-us/library/ms228593(v=vs.120).aspx