C#Lambda表达式是否安全,何时检查(编译时/运行时)?

时间:2015-04-01 20:55:25

标签: c# types lambda static-typing

我正在处理LINQ to XML查询,并使用过匿名函数和lambda表达式。一个简单的例子是IEnumerables上的select方法。

我理解LINQ查询是延迟执行,这有点类似于懒惰评估的概念,但是当VS2012的快速监视无法处理带有lambda表达式的语句时,会想到这个问题。

在C#中,Lambda表达式是否类型安全?

我无法找到对此的直接答案,或者可能是因为我不完全理解类型安全性。我知道OCaml和Java是类型安全的并且Python是弱类型的,我能想到的另一种方式是如果语言是类型安全的,那么该语言中的lambda表达式并不特别。 There is ambiguity in strong/weak typing但是在这里我引用它,好像具有错误类型的lambda表达式将通过编译器并允许在运行时执行。如果存在抛出异常的错误,它们只会在运行时被捕获吗?

什么时候检查?编译时或运行时

例如,在编译时检查OCaml类型,并且在解析类型之前不会执行。虽然Python不那么严格,而且是一种动态语言,即使在类型错误的情况下它也会编译和执行,只在运行时捕获错误。在这个意义上,C#如何处理lambda表达式?

在提出这个问题之前,我做过一些相关的研究:

  1. How are Java lambdas compiled
  2. This blog posts says LINQ is type-safe
  3. Tutorial on using lambda expressions from CodeProject
  4. Difference between C# Anonymous functions and Lambda Expressions

3 个答案:

答案 0 :(得分:3)

在C#中存在两种Lambda Expression

  

lambda表达式是一个匿名函数,可用于创建委托或表达式树类型。

第一种类型的lambda表达式是匿名函数的同步糖:

Func<int, int> myFunc = x => x + 1;

完全等同于:

Func<int, int> myFunc = delegate(int x) { return x + 1; };

因此它显然是类型安全的,因为它是具有不同构成的C#代码。

另一种类型的Lambda Expression是生成表达式树的那个:

Expression<Func<int, int>> myFunc = x => x + 1;

这是不同的。这不是编译为“代码”,而是编译为某种类型Expression的对象,它“描述”x => x + 1(甚至描述委托的类型)......它被编译为:< / p>

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表达式树是类型安全的,但表达式树通常不能简单地编译。程序倾向于操纵它们以获得有趣的结果。它们可用于各种任务...例如,提取属性或“方法”的“名称”,而不在代码中包含具有属性或方法名称的字符串,或转换为其他语言(实体框架) / LinqToSQL将表达式树转换为SQL)。表达式树是非常安全的(可以在运行时“手动构建”无效表达式,但是当您执行.Compile()时,您将获得异常,并且C#编译器接受的表达式树通常是安全的。编译),但如果表达式树用于其他事情,则可能发生错误,甚至连接到类型安全的错误。

我引用:Why the anonymous type instance cannot accept null values returned by the entity framework query?

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();

此处ActionDateDateTimeSampleDate也是action。此LINQ表达式将由编译器转换为大型Lambda表达式,并由Entity Framework SQL Server端执行。现在......问题是null可能会成为action.ActionDate,因此null可能是NullReferenceException(因为表达式不会在本地执行,所以不会一个null),当SampleDate放入InvalidCastException时,可能会抛出(将被抛出)异常(我认为是{{1}})。因此,虽然表达式是类型安全的,但库对它的作用会导致表达式生成非类型安全的代码(无效的转换)

答案 1 :(得分:2)

Lambda具有与任何其他C#代码一样多的静态类型检查。它构建在相同类型的系统上,并强制执行所有相同的编译时类型检查。您当然可以在lambda中关闭静态类型检查(例如,执行转换),就像在任何其他C#代码中一样。

如果将lambda编译成可执行代码(而不是Expression)并运行,则将执行完全相同的运行时检查,就好像您没有使用lambada一样。

事实上,如果您正在使用编译成可执行代码的lambdas,它将简单地转换为新的命名方法,即使它在原始代码中是匿名的,也可以在编译器的早期版本之一中进行。一旦转换为常规命名方法,它将通过所有相同类型检查任何其他代码。

答案 2 :(得分:1)

想象一下,你可以编写一个类似这样的类:

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表达式可以通过创建一个带有方法的匿名类来实现。 (FWIW,在Java中有lambda表达式之前,我经常使用静态私有内部类来模拟它们)。从语义上讲,它比这更复杂,因为变量是自由/绑定的,以及如何优雅地处理那个混乱(提示:Java不能处理它),但最终,C#中的lambda表达式是语法糖给你一个内联定义的委托,而没有像C#那样可以处理的类型推断,并且代理是强类型的。