共用方式為


Lambda Expressions vs. Anonymous Methods, Part Three

Last time I said that I would describe a sneaky trick whereby you can get variable type inference out of a lambda by using method type inference.

First off, I want to again emphasize that the reason we are adding so many type inferencing features to C# 3.0 is not just because it is convenient to reduce the redundancy currently required. That's a nice-to-have feature, but certainly not a must-have feature. No, the reason why we are adding so many type inferencing features is because we are adding anonymous types. (We are adding anonymous types because they make writing queries so much more pleasant.) Since an anonymous type is, by definition, nameless, you need to be able to infer the type of anything which would otherwise have to be declared with a type name.

But if the type of a lambda expression comes from its target type, and the target type has to be named in the declaration, how can you create a lambda expression which, say, returns an anonymous type?

var f = (Customer c)=>new {c.Name, c.Age, c.Address};

We have no way of saying in any type declaration what the type of the return is.  The best we can do is the incredibly weak

Func<Customer, object> f = (Customer c)=>new {c.Name, c.Age, c.Address};

Yuck, all the type information about the tuple has been lost. We can do better than this.

The trick here is that we have extended the type inferencing algorithm on methods so that generic method type variables can be inferred from the return types of lambdas. Suppose we have this little helper identity function:

Func<A, R> MakeFunction<A, R>(Func<A, R> f) { return f; }

Now look what happens when we say

var f = MakeFunction((Customer c)=>new {c.Name, c.Age, c.Address});

The method type inferencing engine says ok, we have an actual argument which is a lambda from Customer to some anonymous tuple type. We have a formal parameter which is a delegate from A to R. Therefore A is Customer, R is the anonymous tuple type. Now we know the return type of this generic function, and therefore abracadabra, the right hand side of the declaration has a type, so it is legal! (And it's not even particularly unperformant, since the jitter will likely optimize away the identity function. Even if it doesn't, it's tiny compared to the cost of creating the delegate object.)

What if the lambda has an anonymous type for its parameter? Suppose we want a function from the anonymous tuple type above which returns a string. This is a little kludgier but we can still do it:

Func<A, R> MakeFunction<A, R>(Func<A, R> f, A a) { return f; }

var f = MakeFunction(c=>c.Name, new {Name="", Age=0, Address=""} );

Now the type inference engine can't infer anything from the lambda parameter, since it is untyped. But it can infer the parameter type from the second actual argument. Once it knows the type of A, it can infer R from the return type of the lambda.

Pretty neat, eh?

Comments

  • Anonymous
    January 12, 2007
    So just to clarify things. This wouldn't be a problem if C# supported polymorphic functions?

  • Anonymous
    January 12, 2007
    The comment has been removed

  • Anonymous
    January 12, 2007
    The comment has been removed

  • Anonymous
    January 12, 2007
    kfarmer: Thanks -- I noticed that a moment after posting. One solution to this problem would be to invent a type T<A, R> to which a lambda taking A as an argument and returning R can be converted (as is deduced by MakeFunction), then allowing T<A,R> to convert to any delegate for which the argument type converts to A and R converts to the result type. This type could then be used as both the type of "delegate(int x) { return x; }" and of "(int)x=>x", and would be the type deduced for y in "var y = (int)x=>x;". The current situation essentially seems to be:  var y = WorkAroundCSharpDeficiency((int)x => x); (for some value of WorkAroundCSharpDeficiency), and that seems like a poor language choice to me. But maybe introducing a new (presumably anonymous) type to remove this wart complicates matters too much?

  • Anonymous
    January 12, 2007
    The comment has been removed

  • Anonymous
    January 12, 2007
    @Stuart, This is possible today: var originalDelegate = expression.Compile(); var three = originalDelegate.Invoke(2);

  • Anonymous
    January 14, 2007
    The comment has been removed

  • Anonymous
    January 23, 2007
    Welcome to the nineteenth Community Convergence. I'm Charlie Calvert, the C# Community PM, and this is

  • Anonymous
    January 24, 2007
    Interesting stuff, Eric. Thanks for the post, looking forward to C# 3!

  • Anonymous
    February 06, 2007
    Hi Eric while I think this trick is quite smart, I'm afraid it acutally affirms a point dynamic language advocates have always been making: the type checking just gets in your way. One of the reasons for type inferrence in C# was to reduce the boiler plate code programmers would have to write to satisfy the compiler, right? Then again, I'm still a static language guy, so I could not resist playing with it. I don't like cast-by-example for several reasons:

  • it's weak because it relies on the fact that anonymous types are reused. Make one little derivation, and I'm sure the compiler messages will be pretty misleading. Names have to be rewritten, and "=0" is quite a weak type declaration.
  • even worse, what if you want a non-trivial member type? e.g. new { Name="", Address = new Address() } even more objects would have to be instantiated, just to derive member types. what if you have no default ctors? what if ctors have side effects? this could get dirty.
  • you actually create instances you're never going to use, which will keep the GC quite busy if done too often. especially if you have to new up objects to specify member types... I tried to improve your sample by replacing the A in MakeFunction with a delegate: Func<A, R> MakeFunction<A, R, T> (Func<A, R> f, Func<T, A> a) { return f; } so that I could invoke it like that: var f = MakeFunction (c => c.Name, (Person p) => new {p.Name, p.Age, p.Address} ); (overloads with more than one "T" argument would be useful for join results) This would have two advantages:
  • by using a source object, we could receive member types and names from other (named) types, therefore reducing the probability of errors. changing members in the source type (Person in my example) would actually be done right using refactoring tools, or create useful error messages otherwise
  • no new objects have to be created. this prevents both problems (GC and non-default ctors) There's a disadvantage too: It does not work, because the complier (may CTP) cannot infer the types (ironically, it suggests I specify the type arguments for MakeFunction explicitly ;-)) However, I don't see a reason why it should not be able to infer this. Is this going to work in RTM?
  • Anonymous
    July 18, 2007
    A week or so back David Ing raised a concern that the new language features in C# 3.0 are bloating the

  • Anonymous
    October 07, 2007
    That seems conceptually much simpler than saying the expression HAS no type.

  • Anonymous
    June 12, 2008
    NOTE: If you haven't read the first post in this series, I would encourage you do to that first , or

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

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

  • Anonymous
    November 13, 2012
    I don't see why this shouldn't be allowed: String s = (a =>a == null ? "default" : a.ToString())(SomeObject); It should be clear from the type on the LHS that the return type is string and not a Func type.