Lambda Expressions vs. Anonymous Methods, Part Two

We interrupt the discussion of how the difference between lambda expression and anonymous method convertibility leads to potential performance problems in the compiler to answer a user question.

Within hours of yesterday's post readers Raymond Chen and Marc Brooks both asked me the same question. How do lambdas and anonymous methods interact with implicitly typed local variables?

Recall that C# 3.0 will support implicitly typed local variables. This:

var d = new Dictionary<string, List<int>>();

is a syntactic sugar for

Dictionary<string, List<int>> d = new Dictionary<string, List<int>>();

Again, I wish to emphasize that C# 3.0 is still statically typed, honest! The var keyword does not have the semantics of the JScript var or VBScript's Variant or any such thing. It simply means "the variable is the type of the right side of the declaration". We are committed to keeping C# 3.0 a statically typed language.

You will note that a key requirement there is that the right hand side actually has a type. Furthermore, it cannot be the null type or the void type, obviously. So then what should this do?

var f = i=>F(i);

Clearly this cannot be legal; since as I discussed yesterday the type of the formal parameter is determined from the target type, and we are trying to determine the target type, we have a chicken-and-egg problem where there is not enough information to determine the type.

What about this?

var f = (int i)=>i;

Here we know the type of the parameter, we can infer the type of the return easily enough, it's the same type as the parameter! We're set, right?

Not so fast.

Consider the following type declarations:

public delegate int D1(int i);
public delegate int D2(int i);
public delegate int D3<A>(A a);
public delegate R D4<A, R>(A a);

If the var were replaced by D1, D2, D3<int>, D4<int, int>, or for that matter, D4<int, double> or D4<int, object>, we'd have a legal program. So what is the type of the right hand side? How can we choose which of these is the best?

We can't. Delegate types are not even structurally equivalent; you can't even assign a variable of type D1 to a variable of type D2. We would always potentially choose wrong.

What this tells us is that it is more than just that the formal parameter types of an implicitly typed lambda expression flow from the target type. The type of the entire lambda expression flows from the target type, and therefore, lambda expressions themselves must have no type.

And in fact the C# 2.0 specification calls this out. Method group expressions and anonymous method expressions are typeless expressions in C# 2.0, and lambda expressions join them in C# 3.0. Therefore it is illegal for them to appear "naked" on the right hand side of an implicit declaration.

This is rather unfortunate because it makes it difficult to declare a variable of type "delegate which takes or returns an anonymous type". The whole point of the var feature in the first place is to make anonymous types work, and yet we seem to have a hole here. As it turns out, it is possible! Next time I'll describe a clever trick whereby we can use method type inference to make variable type inference work correctly on lambdas which take anonymous types.

Comments (7)
  1. Welcome to the eighteenth issue of Community Convergence. I’m Charlie Calvert, the C# Community PM, and

  2. Last time I said that I would describe a sneaky trick whereby you can get variable type inference out

  3. Richard Smith says:

    You wrote:

    "Delegate types are not even structurally equivalent; you can’t even assign a variable of type D1 to a variable of type D2."

    What’s the justification for this? I guess it’s to prevent this:

    delegate bool FooTester(Foo foo);

    delegate bool FooFrobber(Foo foo);

    bool IsPrickly(Foo foo) { return true; }

    bool MakePrickly(Foo foo) { foo.prickly = true; return true; }

    FooTester isPrickly = IsPrickly;

    FooFrobber makePrickly = MakePrickly;

    // Whoops, meant to pass in isPrickly, not makePrickly.

    bool allPrickly = fooColl.All(makePrickly);

    But this doesn’t really seem to help that much; I’d imagine it’s much more common to see either this:

    bool allPrickly = fooColl.All(MakePrickly);

    or this:

    bool allPrickly = fooColl.All(delegate(Foo f) { f.prickly = true; return true; });

    Following that argument through, I would allow conversions between delegate types where one can implicitly convert the argument and return types (with covariant return conversions and contravariant argument conversions permitted). Maybe there is potential for harm here, but I’m not seeing it. More likely I’ve misunderstood something about C#…

  4. Cameron Zwarich says:

    Why is there such a strong insistence in C# on not identifying structurally equivalent delegate types, especially since the introduction of anonymous types makes a completely nominative type system somewhat impossible? Is it just because you don’t want to have massive backwards compatibility problems?

    If you identified structurally equivalent delegate types, then you could allow anonymous delegate types, as C# requires that all structurally equivalent anonymous types in the same method are identified. This would be a pretty reasonable solution to your conundrum, but perhaps I am missing a subtlety here due to a lack of deep experience with the C# type system. And barring such a jarring change to existing C# features, couldn’t you just introduce anonymous delegate types regardless, with the usual anonymous type semantics of divining that the programmer intended all structurally equivalent anonymous delegate types in the same method to be identified?

  5. ASPInsiders says:

    NOTE: If you haven’t read the first post in this series, I would encourage you do to that first , or

  6. BabySmash says:

    NOTE: If you haven&#39;t read the first post in this series, I would encourage you do to that first

  7. Samuel Bronson says:

    Huh. I guess this is why Haskell doesn't have implicit conversions?

Comments are closed.

Skip to main content