3

I'm working on LINQ to XML queries and have used anonymous functions as well as lambda expressions. A quick example would be the select method over IEnumerables.

I understand that LINQ queries are deferred execution, which is somewhat similar to the concept lazy evaluation, but this question came to mind when VS2012's quick watch cannot handle statements with lambda expressions.

Are Lambda Expressions type-safe in C#?

I couldn't find a direct answer to this, or maybe it's because I do not fully understand type safety. I know OCaml and Java is type safe and Python is weakly typed, another way I can think of this is if the language is type safe, then lambda expressions within that language are no special. There is ambiguity in strong/weak typing but here I refer to it as if lambda expressions with erroneous types will pass through the compiler and allowed to execute at runtime. If errors exist that throw exceptions are they only caught at run-time?

When are they checked? Compile-time or Run-time

As an example, OCaml types are checked at compile time, and will not execute until the types are resolved. Whereas Python is less strict and is a dynamic language, in which it will compile and execute even with type error, only catching the errors at run time. How does C# handle lambda expressions in this sense?

Some related research I've done before asking this question:

  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
Community
  • 1
  • 1
matrixanomaly
  • 6,627
  • 2
  • 35
  • 58
  • VS2012 cant handle lambdas as it needs to compile them into anonymous method\class, and it's not so each to do in runtime. As I know VS2015 already support this feature. – Sergey Litvinov Apr 01 '15 at 20:58

3 Answers3

4

In C# exist two types of Lambda Expression:

A lambda expression is an anonymous function that you can use to create delegates or expression tree types.

The fist type of lambda expression is synctatic sugar for an anonymous function:

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

is totally equivalent to:

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

so it is clearly type safe, because it is C# code with a different makeup.

The other type of Lambda Expression is the one that generates Expression Trees:

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

This is something different. This isn't compiled to "code" but is compiled to some object of type Expression that "describe" the x => x + 1 (and even describe the type of delegate)... it is compiled to:

ParameterExpression par = Expression.Parameter(typeof(int), "x");
Expression<Func<int, int>> myFunc2 = Expression.Lambda<Func<int, int>>(
       Expression.Add(par, Expression.Constant(1)), 
       par);

Now, this code can't be executed directly. It can be converted to executable code through the .Compile() method. In general a .Compile()d expression tree is type safe, but Expression Trees aren't normally made to be simply compiled. Programs tend to manipulate them to obtain funny result. They can be used for various tasks... For example to extract the "name" of properties or "methods" without including in the code a string with the name of the property or method, or to be converted to other languages (Entity Framework/LinqToSQL convert expression trees to SQL). An Expression Tree is quite safe (it is possible to "manually build" at runtime an invalid expression, but when you do the .Compile() you'll get an exception, and expression trees accepted by the C# compiler are normally safe to be compiled), but if the expression tree is used for other things, then errors could occur, even errors connected to type safety.

I'll quote from: 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();

Equivalent more or less to

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

Here ActionDate is DateTime, and so is SampleDate. This LINQ expression will be transformed by the compiler to a big Lambda Expression, and executed by Entity Framework SQL Server-side. Now... the problem is that action could become null, and so action.ActionDate could be null (because the expression won't be executed locally there won't be a NullReferenceException), and an exception could be thrown (will be thrown) when null is put in SampleDate (an InvalidCastException I think). So while the expression is type-safe, what the library does with it causes the expression to generate non-type-safe code (an invalid cast)

Community
  • 1
  • 1
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • All of the typical C# static typing rules are applied to the lambdas in question here in order to verify all of the guarantees that C#'s type system does make. As far as at runtime goes, *you have not generated executable code from that lambda*. It has generated an `Expression`. The code in the OP seems to be interested in lambdas resolving to delegates, not expressions, but even when discussing delegates, *they aren't executable* and thus any statements about their type safety are nonsensical. At that point it's just data. – Servy Apr 01 '15 at 21:09
  • @Servy Lambda expressions produced at compile time are useful only as "data" (or metadata). Producing at compile time an expression just to compile it is nearly totally useless. So we could say that expressions are type-safe data that is nearly useless unless it is manipulated in (sometime not type-safe) ways. – xanatos Apr 01 '15 at 21:14
  • @Servy In your response you pointed to the `.Compile()` of the lambda expression, while in mine I pointed more to the manipulations that could be done by the program to the expression. I think it's like asking if a pointer is safe or unsafe... a pointer that you don't use is perfectly safe :-) – xanatos Apr 01 '15 at 21:17
  • You seem to be assuming that all lambdas are resolved to `Expression` objects. That's not true at all. in fact, most aren't. That said, all lambdas, regardless of whether they are compiled into `Expression` objects or not are both statically typed, and both require their bodies to follow all of the static typing rules of C#. So the question of what static typing is performed is that simple, as I said in the opening sentence of my answer. You have exactly as much static typing as any other C# code elsewhere. That doesn't mean it won't throw an exception, or that it will do what you want. – Servy Apr 01 '15 at 21:17
  • I never once referred to the `Compile` method of `Expression` in my answer. Saying that you can take C# code that compiles and end up with a program that throws an exception or simply doesn't work isn't even specific to lambdas. You don't need lambdas at all to write C# code that throws an exception. Lambdas have no more, and no less, static typing than any other C# code. – Servy Apr 01 '15 at 21:18
  • @Servy and xanatos, thanks for both your answers. Both very helpful and I appreciate it. I'll accept xanatos' edited answer because I felt it helped me most (learnt more than I expected) as well as providing all the additional examples. You both clearly are very experienced on lambdas, anonymous functions, delegates and how the compiler deals with all these. – matrixanomaly Apr 01 '15 at 22:04
2

Lambdas have exactly as much static type checking as any other C# code does. It's built on the same type system, and enforces all of the same compile time type checks. You can of course turn off static type checks (by, say, performing a cast) in a lambda in just the same way that you can in any other C# code.

If a lambda is complied into executable code (instead of, say, an Expression) and is run, the exact same runtime checks will be performed as if you weren't using a lambada.

In fact, if you're using lambdas compiled into executable code, it will simply be transformed into a new named method, even though it is anonymous in your original code, in one of the earlier passes of the compiler. Once transformed into a regular named method, it then goes through all of the same type checking any other code would.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Thanks for the very clear and concise answer. Especially on the emphasis that lamdas are viewed as equal citizens by C#. upvoted! – matrixanomaly Apr 01 '15 at 22:05
1

Imagine that you could write a class that is something like this:

public class Foo {
    public Baz DoSomething(Bar b)
    {
        return new Baz(b);
    }
}

Clearly this is strongly typed at compile time. So now I could make a delegate declaration that is something like this:

public delegate Baz SomeDelegate(Bar b);

and then I could modify Foo and add a property:

...
public SomeDelegate MyCall { get { return DoSomething; } }
...

You need to ask youself how is it different to do this:

Bar b = new Bar();
Foo aFoo = new Foo();
var myDelegate = aFoo.MyCall;
Baz baz = myDelegate(b);

And

Bar b = new Bar();
var myDelegate = (Bar bar) => new Baz(bar);
Baz baz = myDelegate(b);

Because what happens under the hood is pretty darn close to this. A lambda expression can be implemented by creating an anonymous class with a method in it. (FWIW, before there were lambda expression in Java, I would often simulate them by using a static private inner class). Semantically, it's more complicated than this because of variables that are free/bound and how to handle that morass gracefully (hint: Java doesn't handle it), but ultimately, lambda expression in C# are syntactic sugar to give you a delegate defined inline without about as much type inference as C# can handle and delegates are strongly typed.

plinth
  • 48,267
  • 11
  • 78
  • 120
  • So to summarize you're saying that lambda expressions are pretty much delegates after desugaring, which are as type safe as everything else in C#, which is strongly typed. Which means they're typed checked at compile-time? – matrixanomaly Apr 01 '15 at 21:55