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

sam*_*mis 1 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));
Run Code Online (Sandbox Code Playgroud)

被定义为

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

所以把lambda r => r.RecordId作为一个Func<TSource,?TResult>.

lambda的类型是如何确定的,一旦它被简化,它是否只是被转换为那种类型?

Eri*_*ert 7

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

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

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

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

records.Select(r => r.RecordId)
Run Code Online (Sandbox Code Playgroud)

哪里records是类型IEnumerable<Record>RecordId类型int.

第一个问题是"确实IEnumerable<Record>有任何适用的方法Select吗?不,它没有.我们因此转到扩展方法.

第二个问题是:"是否有任何静态类型具有适用于此调用的可访问扩展方法?"

SomeType.Select(records, r => r.RecordId)
Run Code Online (Sandbox Code Playgroud)

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

static IEnumerable<R> Select<A,?R>(IEnumerable<A>,?Func<A,?R>)
Run Code Online (Sandbox Code Playgroud)

第三个问题:我们可以推断出相应的输入参数的类型参数AR

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

现在我们问"我们完成了吗?" 不,我们还不知道R.

"还有什么推论吗?" 是.我们还没有检查过lambda.

"是否有任何矛盾或其他事实需要了解A?" 不.

因此,我们"修正" ARecord,继续前进.到目前为止我们知道什么?我们有:

static IEnumerable<R> Select<Record,?R>(IEnumerable<Record>,?Func<Record,?R>)
Run Code Online (Sandbox Code Playgroud)

然后我们说好,争论必定(Record r) => r.RecordId.我们能否知道这个lambda的返回类型?显然是的,确实如此int.所以我们R说一下它可能就是这么说的int.

我们完了吗?是.还有什么可以扣除的吗?没有.我们推断出所有类型参数吗?是.所以我们"修复" Rint,我们就完成了.

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

我们推断出这records.Select(r=>r.RecordId)意味着同样的事情Enumerable.Select<Record, int>(records, (Record r) => { return (int)r.RecordId; } ),这是对该方法的合法调用,所以我们已经完成了.

当你引入多个边界时,事情变得相当复杂.看看你是否可以弄清楚如何使用Join有四种类型参数来推断.