Hejlsberg: First of all, I'm very pleased that we got generics into 2.0. Generics in many ways is the deep enabler of all the stuff that you're now seeing us do in C# 3.0. It is really profound how generics adds a new dimension of richness to the type system that opens up all sorts of possibilities, like language-integrated query, which we could've never done without generics. So in that sense, it's a deep enabler for interesting stuff. It also is a very pragmatic real-world problem solver.
Hey, more typing is good, because that means you find more errors sooner, and you can do better code generation because you have to have fewer dynamic checks at runtime to verify the solidity of the types in your program.
Now, with respect to Java and C#, syntactically, the two implementations of generics look very similar. They [both] look kind of like C++ templates; you can see the heritage there.
But once you scratch the surface, underneath they're actually very, very different. I think the biggest difference is that in .NET, generics are not just a language feature. They are deeply understood by the CLR [Ed.: Common language runtime] and by the type system of .NET itself. So [generics] have representation at runtime.
Java chose a different strategy for implementing generics, where in a nutshell, they're only a compile-time feature. And the [Java] compiler removes all of the genericity from the code, and just emits objects; it effectively substitutes
object for every type parameter. With Java, at runtime, there are no generics. This is interesting in the sense that it allows you to run on an unmodified VM [Ed.: Virtual machine], but it also brings about a host of very surprising limitations and rules that you have to abide by. And it does not give you some of the performance gains that we see from [our own implementation of] generics, because [with Java] at runtime, there are no generics and you have to still do all of the dynamic runtime checks and downcasts and whatever while you take things out of a
But I think the subtler point here is that because there is no runtime representation of generics [in Java], you lose some information going from your compiled code to the code that you run at runtime. So at compile time, you might be operating on a list of customers. If at runtime, someone hands you a list of customers typed as
object, they just give it to you as an
object, and [if ] you want to find out what this list is a list of, you can't, because reflection doesn't know about generics because that got erased.
And so you have these strange holes in the system, and in a world where we rely increasingly on dynamic code generation and inspection--of running code and dynamic behaviors and whatever--that is actually, to me, probably the biggest issue that I have with Java's implementation of generics; that is, this lack of true representation of the program being run.
Osborn: So you're saying that the [.NET implementation of] generics allows you to hang on to the--
Hejlsberg Oh, certainly. If I give you a
List<T> typed as
object, I can ask it, "What are you?" and it will say, "I am a list of customers." It'll say, "I'm a
Customer." Then I can say, "Well, why don't you give me just the
List<T>? And in fact, why don't you bind
Order?" And now I can make myself a
List<Order>, and then I can create instances on that. Anything I can do at compile time, I can do at runtime too, through reflection, and that's tremendously powerful.
Osborn: What about the addition of anonymous methods? I remember when that [feature] was announced, and I was talking to authors, saying you have to add anonymous methods to [your] text, but not really understanding why I was asking them to do that. I'm sure there are use cases for anonymous methods that [people reading this want will want to know about. But also, it's interesting to see how anonymous methods, like generics, are an enabler for new features in C# 3.0].
Hejlsberg: Oh, absolutely. And, you know, honestly, first of all, let's give credit where credit is due. I am not inventing anything completely new here. It's all based on this thing called lambda expressions or lambda calculus or whatever, which has existed in the functional programming space for decades. But somehow, that has never really seen the light of day in a mainstream programming language.
And C# is fortunate enough to be among the first to do that. We're very serious about evolving that, and that's what you're seeing in C# 3.0, where we evolved anonymous methods even further into these things we call lambda expressions now, where we have surrounded them with rich type inference, for instance, so you don't have to say a lot of the stuff that you would have to say manually before.
Hejlsberg: In terms of why they're important, let me illustrate it by an example. In C# 3.0 we're introducing this notion of language-integrated query [Ed.: LINQ]. And really, what we're doing is we're making it possible to build a query language as an API. You know, out of methods called
GroupBy and whatever. You can see that if a collection has a
Where and a
Select and an
OrderBy, and a
GroupBy method, then you can sort of string them together, and you can build a whole query language out of [them].
But if you were to do that in a language that doesn't support anonymous methods or lambda expressions, then if you think about how you would implement a
Where method, well, it wants to take a predicate as an argument, right? A
test to apply to each element, you know what I'm saying? So I want to say
list.Where(blah), and the
blah I want to pass in is a test.
But it's not like a parameter in the normal sense, because I'm not passing in just a
bool argument, because that obviously would [require that the test] get evaluated up front and then passed in. And I don't want to see
false, I want to be passed the test itself. You know what I'm saying?
Osborn: Yes, you want to pass a procedure to be executed?
Hejlsberg: Yes. And really, what I want is, I want a reference to some code that I can execute, right? I want a function reference or a method reference passed to the
Where operator, such that the
Where operator can run this code for each element that it's trying to test, and then, you know, return me all the elements for which this test is true. And if you look at how the
Select operator (i.e.,
projection) works, it's the same. It's like, give me an element, and give me a function that can make an element from one thing to another. That's a projection.
OrderBy, it's like, give me something that can compare two elements. Again, it's a piece of code. So really, the thing expressively that has been missing in programming languages is the ability to pass code as parameters.
Osborn: That's the significance--
Hejlsberg: And that, is in a nutshell, what lambda expressions and anonymous methods allow you to do. And by the way, lambda expressions and anonymous methods are really just two words for the same thing. The only thing that differs is, "What does the syntax look like?" And the lambda expressions are a further evolution of the syntax. But underneath, they do the same thing. They generate methods. You know, they're in-line methods.