我想知道编译器/运行时如何确定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的类型,一旦确定,它是否只是转换为该类型?
答案 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>)
第三个问题:我们能否推断出类型参数A
和R
对应的类型参数?
在第一轮类型推断中,我们考虑所有非lambda参数。我们只有一个。我们推断A
可能是Record
。但是,IEnumerable<T>
是协变的,因此我们注意它也可以是比Record
更通用的类型。它不能是比Record
更具体的类型。
现在我们问&#34;我们完成了吗?&#34;不,我们还不知道R
。
&#34;还有任何推论吗?&#34;是。我们还没有检查过lambda。
&#34;是否有任何关于A
的矛盾或其他事实需要了解?&#34;不。
因此我们&#34;修复&#34; A
到Record
继续前进。到目前为止我们知道什么?我们有:
static IEnumerable<R> Select<Record, R>(IEnumerable<Record>, Func<Record, R>)
然后我们说好,参数必须是(Record r) => r.RecordId
。我们能否知道这个lambda的返回类型?显然是的,它是int
。我们在R
上写了一条说明可能是int
的说明。
我们完成了吗?是。还有什么可以扣除的吗?没有。我们推断出所有类型参数吗?是。所以我们&#34;修复&#34; R
到int
,我们已经完成了。
现在我们进行最后一轮检查以确保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