Fabulous Adventures In Coding

Eric Lippert's Blog

Delegates, Lambdas, Type Inference and Long Playing Records

Today is my 33 1/3rd birthday! I'm one third of the way through my first century. I feel like I should go buy some LPs to celebrate, not that I have anything that will play them.

In other news, someone sent me this code the other day. Suppose you've got a collection of mammals and you want to feed all the giraffes:

delegate void Action<X>(X x);
class MyCollection<T> : Collection<T> where T : class {
  public void Apply<S>(Action<S> action) where S : class, T {
    foreach ( T t in this ) {
      S s = t as S;
      if (s != null )
        action(s);
    }
  }
}
//...

MyCollection<Mammal> mammals = new MyCollection<Mammal>();
//...
mammals.Apply<Giraffe>(delegate(Giraffe g){Feed(g);});

This works just fine. The questioner was wondering why this didn't work:

mammals.Apply(delegate(Giraffe g){Feed(g);});

That is, why do we need to specify the type parameter to the generic method? Shouldn't type inference take care of it?

That would be nice, but unfortunately section 26.6.4 of the specification clearly states that nothing is inferred from any anonymous method, null, or method group argument.

In the next version of C# we will have a new, more powerful, more flexible kind of anonymous method called a lambda expression. A lambda expression is like an anonymous method with additional type inferencing features and a more natural syntax than the clunky delegate syntax.

Unfortunately for the questioner, the new lambda type inferencing rules actually will not help with this scenario. However, the new type inferencing rules will apply in any scenario where "classic" type inferencing can be used to determine the generic type of a lambda argument, and from that, determine the generic type of a lambda return, and from that, the generic return type of a delegate, and from that, a method type parameter.

That last sentence probably made no sense. Let's look at an example where the new type inferencing rule would apply.

Suppose you have a collection of Customers and you want to create a collection of their names:

delegate R Func<A, R> (A arg);
IEnumerable<S> Select<T, S>(IEnumerable<T> source, Func<T, S> selector) {...}
//...
names = Select(customers, c => c.Name);

With the old type inferencing rules we can infer that T is Customer, and we would infer nothing from the lambda. However, in C# 3.0 we will enter a second round of type inferencing, where we'd note that we hadn't inferred anything from the lambda yet, and that it is being converted to a partially inferred delegate type Func<Customer, R> requiring type inference on the return type.

We now can infer that the parameter to the lambda, c must be of type Customer, and therefore the return type of the lambda must be the type of Customer.Name, which is string. Therefore the delegate type is Func<Customer, string>, and hey,we've managed to infer the types of all the parameters to Select<T, S>, so we're done.

Cool eh? Now all we've got to do is actually implement it...

Published Friday, March 31, 2006 10:17 AM by Eric Lippert

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

IDisposable said:

Not that I don't get the concepts, but why did you gratuitously switch from the Giraffe example to another one when showing the the lambda way?  Why not code the same problem in lambdas to clearly show the delta?
March 31, 2006 3:50 PM
 

Eric Lippert said:

Hmm, I probably should have made this point more clear.  

The new lambda type inferencing feature actually would NOT help the initial example -- the initial example was simply what got me thinking about the problem space.  

The reason why it would not help is because the new type inferencing rules only allow for additional inferences to be made on delegate _return_ types, not on delegate _parameter_ types.  Once the delegate return type has been inferred, that fact may be used to infer the method parameter type.

However, Apply( (Giraffe g)=>Feed(g) ); would not infer that S is Giraffe.

Perhaps I ought to rewrite this article to use the select example throughout.

Thanks!
March 31, 2006 6:49 PM
 

sebmol said:

I have another question about C# generics that nobody seems to be able to answer. Personally, I'm very impressed with the new C# generic features, especially considering how long it took for most compilers to get C++ templates right (many of them still aren't quite there). Anyway, here's my question:

Is there any way in C# to generically call a constructor without resorting to reflection? What I mean is this: if I declare an interface or a base class, I can specify a specific function that will be available in all instances of that interface or base class. Then I can write something like:

public void foo(T x) where T: MyInterface {
 x.myFunction();
}

So how do I do that, if I want to call a constructor like that. Basically something like this (this doesn't compile in C#):

public interface IMyInterface {
 IMyInterface(string AValue);
}

public class MyClass : IMyInterface {
 public MyClass(string AValue) { Console.WriteLine("MyClass.Constructor with {0}", AValue); }

 public static void CreateMyInterface<T>(string AValue) where T: IMyInterface {
   return new T(AValue);
 }

 public static void Main() {
   IMyInterface myInterface = CreateMyInterface<MyClass>("42");
   // should call MyClass' constructor and return a new MyClass object
   // console output: "MyClass.Cconstructor with 42"
 }

}


Basically, it's a type of virtual constructor. Any ideas how to do that? My workaround has been to create static functions in each one of these classes and pass a delegate for this static function around but that's not really very convenient or safe. Reflection is another option but also fraught with significant performance problems. What's your take?

April 1, 2006 5:14 AM

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required
Submit

About Eric Lippert

Eric Lippert is a senior developer on the Microsoft C# compiler team. Before that he worked on the framework of Visual Studio Tools For Office. Before that, he worked on the compilers, runtimes and tools for VBScript, JScript, Windows Script Host and other Microsoft Scripting technologies. He lives in Seattle and spends his free time editing books about programming languages, playing the piano, and trying to keep his tiny sailboat upright in Puget Sound.

This Blog

Syndication


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker