C# Lambda 表达式类型安全吗?何时检查它们(complile time/runtime)?
Are C# Lambda Expressions Type Safe and when (complile time/runtime) are they checked?
我正在研究 LINQ to XML 查询,并使用过匿名函数和 lambda 表达式。一个简单的例子是 IEnumerables 上的 select 方法。
我理解LINQ查询是延迟执行的,这有点类似于lazy evaluation的概念,但是当VS2012的quick watch不能处理lambda表达式的语句时,我想到了这个问题。
Lambda 表达式在 C# 中是类型安全的吗?
我找不到对此的直接答案,或者可能是因为我不完全了解类型安全。我知道 OCaml 并且 Java 是类型安全的,而 Python 是弱类型的,我能想到的另一种方式是,如果语言是类型安全的,那么该语言中的 lambda 表达式并不特殊。 There is ambiguity in strong/weak typing 但在这里我提到它好像具有错误类型的 lambda 表达式将通过编译器并允许在 运行 时间执行。如果存在抛出异常的错误,它们是否仅在 运行 时被捕获?
他们什么时候检查的?编译时或 运行-time
例如,OCaml 类型在编译时进行检查,并且在解析类型之前不会执行。而 Python 不那么严格,是一种动态语言,即使出现类型错误,它也会编译和执行,仅在 运行 时捕获错误。从这个意义上讲,C# 是如何处理 lambda 表达式的?
在问这个问题之前我做了一些相关的研究:
- How are Java lambdas compiled
- This blog posts says LINQ is type-safe
- Tutorial on using lambda expressions from CodeProject
- Difference between C# Anonymous functions and Lambda Expressions
Lambda 的静态类型检查与任何其他 C# 代码一样多。它建立在相同的类型系统上,并强制执行所有相同的编译时类型检查。您当然可以在 lambda 中关闭静态类型检查(例如,通过执行转换),就像在任何其他 C# 代码中一样。
如果 lambda 被编译成可执行代码(而不是 Expression
)并且是 运行,将执行完全相同的 运行时间检查,就好像你没有使用 lambada。
事实上,如果您使用编译成可执行代码的 lambda,它会简单地转换成一个新的命名方法,即使它在您的原始代码中是匿名的,在编译器的早期阶段之一。一旦转换为常规命名方法,它就会通过所有相同的类型检查任何其他代码。
在 C# 中存在两种类型 Lambda Expression:
A lambda expression is an anonymous function that you can use to create delegates or expression tree types.
第一种lambda表达式是匿名函数的语法糖:
Func<int, int> myFunc = x => x + 1;
完全等同于:
Func<int, int> myFunc = delegate(int x) { return x + 1; };
所以它显然是类型安全的,因为它是具有不同构造的 C# 代码。
另一种类型的 Lambda 表达式是生成表达式树的类型:
Expression<Func<int, int>> myFunc = x => x + 1;
这是不同的东西。这不是编译为 "code",而是编译为 Expression
类型的某个对象 "describe" x => x + 1
(甚至描述委托的类型)......它是编译为:
ParameterExpression par = Expression.Parameter(typeof(int), "x");
Expression<Func<int, int>> myFunc2 = Expression.Lambda<Func<int, int>>(
Expression.Add(par, Expression.Constant(1)),
par);
现在,这段代码不能直接执行。可以通过.Compile()
方法转换为可执行代码。一般来说,.Compile()
d 表达式树是类型安全的,但表达式树通常不会被简单地编译。程序倾向于操纵它们以获得有趣的结果。它们可用于各种任务...例如提取属性的 "name" 或 "methods" 而无需在代码中包含具有 属性 或方法名称的字符串,或者转换为其他语言(实体 Framework/LinqToSQL 将表达式树转换为 SQL)。表达式树是非常安全的(可以在运行时 "manually build" 一个无效的表达式,但是当你执行 .Compile()
时你会得到一个异常,并且 C# 编译器接受的表达式树通常是安全的待编译),但如果表达式树用于其他用途,则可能会发生错误,甚至是与类型安全相关的错误。
我将引用:
var l = (from s in db.Samples
let action = db.Actions.Where(x => s.SampleID == x.SampleID && x.ActionTypeID == 1).FirstOrDefault()
where s.SampleID == sampleID
select new
{
SampleID = s.SampleID,
SampleDate = action.ActionDate,
}).ToList();
大致相当于
var l = db.Samples.Select(s => new
{
s = s,
action = db.Actions.Where(x => s.SampleID == x.SampleID && x.ActionTypeID == 1).FirstOrDefault()
}).Where(x => x.s.SampleID == sampleID).Select(x => new
{
SampleID = x.s.SampleID,
SampleDate = x.action.ActionDate
}).ToList();
这里ActionDate
是DateTime
,SampleDate
也是。这个 LINQ 表达式将被编译器转换为一个大的 Lambda 表达式,并由 Entity Framework SQL 服务器端执行。现在...问题是 action
可能变成 null
,所以 action.ActionDate
可能变成 null
(因为表达式不会在本地执行,所以不会a NullReferenceException
),当 null
被放入 SampleDate
时(我认为是 InvalidCastException
),可能会抛出(将被抛出)异常。因此,虽然表达式是类型安全的,但库对它所做的操作会导致表达式生成非类型安全代码(无效转换)
想象一下,你可以写一个 class 是这样的:
public class Foo {
public Baz DoSomething(Bar b)
{
return new Baz(b);
}
}
很明显,这是在编译时强类型化的。所以现在我可以做一个像这样的委托声明:
public delegate Baz SomeDelegate(Bar b);
然后我可以修改 Foo
并添加 属性:
...
public SomeDelegate MyCall { get { return DoSomething; } }
...
你需要问问自己这样做有什么不同:
Bar b = new Bar();
Foo aFoo = new Foo();
var myDelegate = aFoo.MyCall;
Baz baz = myDelegate(b);
和
Bar b = new Bar();
var myDelegate = (Bar bar) => new Baz(bar);
Baz baz = myDelegate(b);
因为幕后发生的事情与此非常接近。 lambda 表达式可以通过创建一个匿名 class 并在其中包含一个方法来实现。 (FWIW,在 Java 中有 lambda 表达式之前,我经常使用静态私有内部 class 来模拟它们)。从语义上讲,它比这更复杂,因为变量是 free/bound 以及如何优雅地处理这个烂摊子(提示:Java 不处理它),但最终,C# 中的 lambda 表达式是语法糖给你一个内联定义的委托,没有 C# 可以处理的尽可能多的类型推断,并且委托是强类型的。
我正在研究 LINQ to XML 查询,并使用过匿名函数和 lambda 表达式。一个简单的例子是 IEnumerables 上的 select 方法。
我理解LINQ查询是延迟执行的,这有点类似于lazy evaluation的概念,但是当VS2012的quick watch不能处理lambda表达式的语句时,我想到了这个问题。
Lambda 表达式在 C# 中是类型安全的吗?
我找不到对此的直接答案,或者可能是因为我不完全了解类型安全。我知道 OCaml 并且 Java 是类型安全的,而 Python 是弱类型的,我能想到的另一种方式是,如果语言是类型安全的,那么该语言中的 lambda 表达式并不特殊。 There is ambiguity in strong/weak typing 但在这里我提到它好像具有错误类型的 lambda 表达式将通过编译器并允许在 运行 时间执行。如果存在抛出异常的错误,它们是否仅在 运行 时被捕获?
他们什么时候检查的?编译时或 运行-time
例如,OCaml 类型在编译时进行检查,并且在解析类型之前不会执行。而 Python 不那么严格,是一种动态语言,即使出现类型错误,它也会编译和执行,仅在 运行 时捕获错误。从这个意义上讲,C# 是如何处理 lambda 表达式的?
在问这个问题之前我做了一些相关的研究:
- How are Java lambdas compiled
- This blog posts says LINQ is type-safe
- Tutorial on using lambda expressions from CodeProject
- Difference between C# Anonymous functions and Lambda Expressions
Lambda 的静态类型检查与任何其他 C# 代码一样多。它建立在相同的类型系统上,并强制执行所有相同的编译时类型检查。您当然可以在 lambda 中关闭静态类型检查(例如,通过执行转换),就像在任何其他 C# 代码中一样。
如果 lambda 被编译成可执行代码(而不是 Expression
)并且是 运行,将执行完全相同的 运行时间检查,就好像你没有使用 lambada。
事实上,如果您使用编译成可执行代码的 lambda,它会简单地转换成一个新的命名方法,即使它在您的原始代码中是匿名的,在编译器的早期阶段之一。一旦转换为常规命名方法,它就会通过所有相同的类型检查任何其他代码。
在 C# 中存在两种类型 Lambda Expression:
A lambda expression is an anonymous function that you can use to create delegates or expression tree types.
第一种lambda表达式是匿名函数的语法糖:
Func<int, int> myFunc = x => x + 1;
完全等同于:
Func<int, int> myFunc = delegate(int x) { return x + 1; };
所以它显然是类型安全的,因为它是具有不同构造的 C# 代码。
另一种类型的 Lambda 表达式是生成表达式树的类型:
Expression<Func<int, int>> myFunc = x => x + 1;
这是不同的东西。这不是编译为 "code",而是编译为 Expression
类型的某个对象 "describe" x => x + 1
(甚至描述委托的类型)......它是编译为:
ParameterExpression par = Expression.Parameter(typeof(int), "x");
Expression<Func<int, int>> myFunc2 = Expression.Lambda<Func<int, int>>(
Expression.Add(par, Expression.Constant(1)),
par);
现在,这段代码不能直接执行。可以通过.Compile()
方法转换为可执行代码。一般来说,.Compile()
d 表达式树是类型安全的,但表达式树通常不会被简单地编译。程序倾向于操纵它们以获得有趣的结果。它们可用于各种任务...例如提取属性的 "name" 或 "methods" 而无需在代码中包含具有 属性 或方法名称的字符串,或者转换为其他语言(实体 Framework/LinqToSQL 将表达式树转换为 SQL)。表达式树是非常安全的(可以在运行时 "manually build" 一个无效的表达式,但是当你执行 .Compile()
时你会得到一个异常,并且 C# 编译器接受的表达式树通常是安全的待编译),但如果表达式树用于其他用途,则可能会发生错误,甚至是与类型安全相关的错误。
我将引用:
var l = (from s in db.Samples
let action = db.Actions.Where(x => s.SampleID == x.SampleID && x.ActionTypeID == 1).FirstOrDefault()
where s.SampleID == sampleID
select new
{
SampleID = s.SampleID,
SampleDate = action.ActionDate,
}).ToList();
大致相当于
var l = db.Samples.Select(s => new
{
s = s,
action = db.Actions.Where(x => s.SampleID == x.SampleID && x.ActionTypeID == 1).FirstOrDefault()
}).Where(x => x.s.SampleID == sampleID).Select(x => new
{
SampleID = x.s.SampleID,
SampleDate = x.action.ActionDate
}).ToList();
这里ActionDate
是DateTime
,SampleDate
也是。这个 LINQ 表达式将被编译器转换为一个大的 Lambda 表达式,并由 Entity Framework SQL 服务器端执行。现在...问题是 action
可能变成 null
,所以 action.ActionDate
可能变成 null
(因为表达式不会在本地执行,所以不会a NullReferenceException
),当 null
被放入 SampleDate
时(我认为是 InvalidCastException
),可能会抛出(将被抛出)异常。因此,虽然表达式是类型安全的,但库对它所做的操作会导致表达式生成非类型安全代码(无效转换)
想象一下,你可以写一个 class 是这样的:
public class Foo {
public Baz DoSomething(Bar b)
{
return new Baz(b);
}
}
很明显,这是在编译时强类型化的。所以现在我可以做一个像这样的委托声明:
public delegate Baz SomeDelegate(Bar b);
然后我可以修改 Foo
并添加 属性:
...
public SomeDelegate MyCall { get { return DoSomething; } }
...
你需要问问自己这样做有什么不同:
Bar b = new Bar();
Foo aFoo = new Foo();
var myDelegate = aFoo.MyCall;
Baz baz = myDelegate(b);
和
Bar b = new Bar();
var myDelegate = (Bar bar) => new Baz(bar);
Baz baz = myDelegate(b);
因为幕后发生的事情与此非常接近。 lambda 表达式可以通过创建一个匿名 class 并在其中包含一个方法来实现。 (FWIW,在 Java 中有 lambda 表达式之前,我经常使用静态私有内部 class 来模拟它们)。从语义上讲,它比这更复杂,因为变量是 free/bound 以及如何优雅地处理这个烂摊子(提示:Java 不处理它),但最终,C# 中的 lambda 表达式是语法糖给你一个内联定义的委托,没有 C# 可以处理的尽可能多的类型推断,并且委托是强类型的。