Bill Blogs in C#

Bill Wagner discusses C#, LINQ, and other items of interest

Another LINQ investigation item
Anonymous types and projections

The next set of LINQ samples takes us into the realm of Anonymous Types. It’s really not a hard concept. It’s just several small steps that add up to one huge leap in terms of programming language power.

Let’s look at the first anonymous type sample. Here’s the code:

public void Linq9() {
  string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };
  var upperLowerWords =
    from w in words
    select new {Upper = w.ToUpper(), Lower = w.ToLower()};
    
  foreach (var ul in upperLowerWords) {
    Console.WriteLine("Uppercase: {0}, Lowercase: {1}", ul.Upper, ul.Lower);
  }
}

The output is simple the upper and lower case versions of all the input strings:

Uppercase: APPLE, Lowercase: apple
Uppercase: BLUEBERRY, Lowercase: blueberry
Uppercase: CHERRY, Lowercase: cherry

Conceptually, this is a simple example: from every string, you want to produce a pair of strings. The pair contains the upper case and lower case versions of the input string.

Simple, right?

Of course it is. In C# today, you can create this class by hand in minutes: two private fields to contain the strings, and two public read / write properties to provide access to the private fields. It’s so simple that we could assign to our favorite intern, and expect a reasonably correct solution, including unit tests, by lunchtime.

But, hopefully, even our interns have something better to do, so the C# team is making the compiler to the grunt work of creating the new type for us. By writing:

  select new {Upper = w.ToUpper(), Lower = w.ToLower()};

We tell the compiler to define a new type, with two string properties “Upper” and “Lower”, and corresponding private fields. Furthermore, the compiler should return an instance of that new type for each result.

Great. I’m loving it. But, to make it work, the C# language needs some new features. How does the compiler determine the names for the public properties? You actually name them. Whatever label you place on the left hand side of the assignments inside the new expression determine the names of the public properties. The type of the right hand side of the assignments determine the type of the properties.

Now comes the part of the ocean with the dragons: What is the name of the new type? It really doesn’t have a name. At least, it doesn’t have a name you can use. Looking at the sample running in the debugger, my version gives the name “<Projection>f__28” to this particular type. You don’t want to type that in your code. In fact, you can’t because the compiler might change the name on the next compilation. So, you use var to declare variables of an anonymous type. In the example above, the type of ‘ul’ is <Projection>f__28. That’s because each member of the upperLowerWords collection is of the type “<Projection>f__28”. Furthermore, the type of upperLowerWords is “System.Query.Sequence.Select<string, <Projection>f__28>”.

If you use the debugger to look inside the anonymous type, you’ll find that <Projection>f__28 contains two private string fields: _Lower, and _Upper. It also contains two public read/write string properties: Lower and Upper.

Simple, right?

In a sense it is. The compiler did exactly what you would have done by hand, but did so automatically. The only hard part for you is to understand that ‘var’ is really a substitute for ‘whatever name the compiler gave that type I told it to produce’.

OK, I lied. ‘var’ is really a substitute for ‘the type on the right hand side of the initialization statement.’ But, ‘var’ exists, in part, because sometimes you don’t know the name of the type on the right hand side of the assignment. That makes it hard to declare a variable for it.

The next two samples show that this feature is not limited to strings. The next sample shows that you can create an anonymous type with a Boolean value:

public void Linq10() {
  int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
  string[] strings = { "zero", "one", "two", "three", 
    "four", "five", "six", "seven", "eight", "nine" };
  var digitOddEvens =
  from n in numbers
  select new {Digit = strings[n], Even = (n % 2 == 0)};

  foreach (var d in digitOddEvens) {
    Console.WriteLine("The digit {0} is {1}.", d.Digit, 
      d.Even ? "even" : "odd");
  }
}

The compiler generated type (this time called <Projection>f__32), contains a string, and a Boolean value.

The last sample shows one obvious use case for this feature. Here, you’re creating a new type that contains a subset of the source type.

public void Linq11() {
  List<Product> products = GetProductList();
  var productInfos =
    from p in products
    select new {p.ProductName, p.Category, Price = p.UnitPrice};

  Console.WriteLine("Product Info:");
  foreach (var productInfo in productInfos) {
    Console.WriteLine("{0} is in the category {1} and costs {2} per unit.", 
    productInfo.ProductName, productInfo.Category, productInfo.Price);
  }
}

The compiler generated type contains three properties: Category (a string), Price (a decimal), and ProductName (also a string).

Note that with two of the properties (Category and ProductName), the name of the property is projected from the source object. The Price property is explicitly named.

In this installment, I gave a brief overview of anonymous types, and the other C# syntax elements that are necessary to support them. There are two key points to remember on anonymous types. First, the concept really is not that hard. You define the contents of the anonymous type in your initialization statements. And, the compiler creates the simplest definition that matches your request. Second, don’t get too hung up on var. It’s simple a placeholder for the type on the right hand side of the initialization statement. Almost everyone misinterprets this for a loosely typed variable. It’s not. It’s just a short hand way to say ‘The type of this variable matches compile-time type of the right hand side of the assignment.’



Published Monday, February 27, 2006 6:09 AM by wwagner
Filed under: ,

Comments

No Comments