C# 3.0 Language Enhancements
>> Aug 19, 2009
The C# language is not static. Anders Hejlsberg (the inventor of C#) and others at Microsoft continue to update and refine the language. At the time of this writing, the most recent changes are part of version 3.0 of the C# language, which is released as part of the Visual Studio 2008 product line. At this point in the book you may be wondering what else could be needed; indeed, previous versions of C# lack little in terms of functionality. However, this doesn ’ t mean that it isn ’ t possible to make some aspects of C# programming easier, or that the relationships between C# and other technologies can ’ t be streamlined.
Perhaps the best way to understand this is to consider an addition that was made between versions 1.0 and 2.0 of the language — generics. You could argue that while generics are extremely useful, they don ’ t actually provide any functionality that you couldn ’ t achieve before. True, they simplify things a great deal, and you would have to write a lot more code without them. None of us would want to go back to the days before generic collection classes. Nonetheless, generics aren ’ t an essential part of C#. They are, though, a definite improvement to the language.
The C# 3.0 language enhancements are much the same. They provide new ways of achieving things that would have been difficult to accomplish before without lengthy and/or advanced programming techniques.
You ’ ve already seen two of the new features of C# 3.0: automatic properties and partial methods (Chapter 10 ). In both cases you may have noticed that the changes affect the compilation of C# code, rather than do something completely new. That ’ s a common aspect of C# language improvements, reinforcing the point that these improvements do not reflect enormous changes.
In this chapter you look at the following:
. Initializers
. Type inference
. Anonymous types
. Extension methods
. Lambda expressions
Probably the biggest change to the C# language is the Language Integrated Query (LINQ) technology, which is covered later in Chapters 26 – 29 . LINQ is a new way to manipulate collections of objects that can come from a variety of sources — most importantly, database or XML data. Many of the enhancements covered in this chapter are primarily used in association with LINQ, although they are also enhancements in their own rights.
Initializers
In earlier chapters you learned to instantiate and initialize objects in various ways. Invariably, that has required you either to add additional code to class definitions to enable initialization or to instantiate and initialize objects with separate statements. You have also learned how to create collection classes of various types, including generic collection classes. Again, you may have noticed that there was no easy way to combine the creation of a collection with adding items to the collection.
Object initializers provide a way to simplify your code by enabling you to combine instantiation and initialization of objects. Collection initializers give you a simple, elegant syntax to create and populate collections in a single step. This section explains how to use both of these new features.
Object Initializers
Consider the following simple class definition:
public class Curry
{
public string MainIngredient {get; set;}
public string Style {get; set;}
public int Spiciness {get; set;}
}
This class has three properties that are defined using the automatic property syntax shown in Chapter 10 . If you want to instantiate and initialize an object instance of this class, you must execute several statements:
Curry tastyCurry = new Curry();
tastyCurry.MainIngredient = “panir tikka”;
tastyCurry.Style = “jalfrezi”;
tastyCurry.Spiciness = 8;
This code uses the default, parameterless constructor that is supplied by the C# compiler if you don ’ t include a constructor in your class definition. To simplify this initialization, you can supply an appropriate nondefault constructor:
public class Curry
{
public Curry(string mainIngredient, string style, int spiciness)
{
MainIngredient = mainIngredient;
Style = style;
Spiciness = spiciness;
}
...
}
That enables you to write code combining instantiation with initialization:
Curry tastyCurry = new Curry(“panir tikka”, “jalfrezi”, 8);
This works fine, although it forces code that uses this class to use this constructor, which would prevent the previous code, which used a parameterless constructor, from working. Often, particularly where classes must be serializable, it is necessary to provide a parameterless constructor:
public class Curry
{
public Curry()
{
}
...
}
Now you have a situation where you can instantiate and initialize the Curryclass any way you like, although you have added several lines of code to the initial class definition, that doesn ’ t do anything much other than provide the basic plumbing required for this to work.
Enter object initializers, which are a way to instantiate and initialize objects without having to add additional code (such as the constructors detailed here) to a class. When you instantiate an object, you supply values for publicly accessible properties or fields using a name - value pair for each property you want to initialize. The syntax for this is as follows:
className variableName = new className
{
propertyOrField1 = value1,
propertyOrField2 = value2,
...
propertyOrField N = valueN
};
For example, you could rewrite the code shown earlier, which instantiates and initializes an object of type Curry, as follows:
Curry tastyCurry = new Curry
{
MainIngredient = “panir tikka”,
Style = “jalfrezi”,
Spiciness = 8
};
Often you can put code like that on a single line without seriously degrading readability.
When you use an object initializer, you cannot explicitly call a constructor of the class (no parentheses are permitted in the object initializer syntax). Instead, the default parameterless constructor is called automatically. This happens before any parameter values are set by the initializer, which enables you to provide default values for parameters in the default constructor if desired. If you do not provide a constructor for a class, then the compiler- provided public default constructor will work just fine.
Because of this automatic constructor call, you must have access to the default constructor of the class in order for object initializers to work. Therefore, if you have added a nondefault constructor with parameters, you also require a default constructor — just as in the preceding case that used constructors to initialize properties.
If one of the properties you want to initialize with an object initializer is more complex than the simple types used in this example, then you may find yourself using a nested object initializer. That simply means using the exact same syntax you ’ ve already seen:
Curry tastyCurry = new Curry
{
MainIngredient = “panir tikka”,
Style = “jalfrezi”,
Spiciness = 8,
Origin = new Restaurant {Name = “King’s Balti”, Location = “York Road”, Rating = 5}
};
Here, a property called Originof type Restaurant (not shown here) is initialized. The code initializes three properties of the Origin property, Name, Location, and Rating, with values of type string, string, and int, respectively. This initialization uses a nested object initializer.
Note that object initializers are not a replacement for nondefault constructors. The fact that you can use object initializers to set property and field values when you instantiate an object does not mean that you will always know what state needs initializing. With constructors you can specify exactly what values are required for an object to function, and then execute code in response to those values immediately.
Collection Initializers
Chapter 5 described how arrays can be initialized with values using the following syntax:
int[] myIntArray = new int[5] {5, 9, 10, 2, 99};
This is a quick and easy way to combine the instantiation and initialization of an array. Collection initializers simply extend this syntax to collections:
List < int > myIntCollection = new List < int > {5, 9, 10, 2, 99};
By combining object and collection initializers, it is possible to configure collections with simple and elegant code. Rather than code like this:
List < Curry > curries = new List < Curry > ();
curries.Add(new Curry(“Chicken”, “Pathia”, 6));
curries.Add(new Curry(“Vegetable”, “Korma”, 3));
curries.Add(new Curry(“Prawn”, “Vindaloo”, 9));
You can use the following:
List < Curry > moreCurries = new List < Curry >
{
new Curry {MainIngredient = “Chicken”, Style = “Pathia”, Spiciness = 6},
new Curry {MainIngredient = “Vegetable”, Style = “Korma”, Spiciness = 3},
new Curry {MainIngredient = “Prawn”, Style = “Vindaloo”, Spiciness = 9}
};
This works very well for types that are primarily used for data representation, and as such, collection initializers are a great accompaniment for the LINQ technology described later in the book.
Try It Out - Initializers
1. Create a new console application called Ch14Ex01 and save it in the directory C:\BegVCSharp\Chapter14.
2. Right - click on the project name in the Solution Explorer window, and select the Add Existing Item option.
3. Select the Animal.cs, Cow.cs, Chicken.cs, SuperCow.cs, and Farm.cs files from the C:\BegVCSharp\Chapter12\Ch12Ex04\Ch12Ex04 directory, and click Add.
4. Modify the namespace declaration in the file you have added as follows:
namespace Ch14Ex01
5. Add a default constructor to the Cow, Chicken, and SuperCowclasses. For example, for Cow add the following code:
namespace Ch14Ex01
{
public class Cow : Animal
{
public Cow()
{
}
...
6. Modify the code in Program.csas follows:
static void Main(string[] args)
{
Farm < Animal > farm = new Farm < Animal >
{
new Cow { Name=”Norris” },
new Chicken { Name=”Rita” },
new Chicken(),
new SuperCow { Name=”Chesney” }
};
farm.MakeNoises();
Console.ReadKey();
}
7. Build the application. You should receive the build errors shown in Figure 14 - 1 .
Figure 14-1
8. Add the following code to Farm.cs:
namespace Ch14Ex01
{
public class Farm < T > : IEnumerable < T >
where T : Animal
{
public void Add(T animal)
{
animals.Add(animal);
}
...
9. Run the application. The result is shown in Figure 14 - 2.
Figure 14-2
How It Works
This example combined object and collection initializers to create and populate a collection of objects in a single step. It used the farmyard collection of objects that you have seen in previous chapters, although two modifications are necessary for initializers to be used with these classes.
First, you add default constructors to the classes derived from the base Animal class. That ’ s necessary because, as shown earlier in this chapter, default constructors are called when object initializers are used. When using these default constructors, the Name property is initialized according to the default constructor in the base class, which has code as follows:
public Animal()
{
name = “The animal with no name”;
}
However, when an object initializer is used with a class that derives from Animal, recall that any properties set by the initializer are set after the object is instantiated, and therefore after this base class constructor is executed. If a value for the Name property is supplied as part of an object initializer, it will override this default value. In the example code, the Name property is set for all but one of the items added to the collection.
Second, you add an Add()method to the Farm class. This is in response to a series of compiler errors of the following form:
Ch14Ex01.Farm < Ch14Ex01.Animal > ’ does not contain a definition for ‘Add’
This error exposes part of the underlying functionality of collection initializers. Behind the scenes, the compiler calls the Add()method of a collection for each item that you supply in a collection initializer. The Farmclass exposes a collection of Animal objects through a property called Animals . The compiler cannot guess that this is the property you want to populate (through Animals.Add() ), so the code fails. To correct this problem, you add an Add()method to the class, which is initialized through the object initializer.
Alternatively, you could modify the code in the example to provide a nested initializer for the Animals property as follows:
static void Main(string[] args)
{
Farm < Animal > farm = new Farm < Animal >
{
Animals =
{
new Cow { Name=”Norris” },
new Chicken { Name=”Rita” },
new Chicken(),
new SuperCow { Name=”Chesney” }
}
};
farm.MakeNoises();
Console.ReadKey();
}
With this code there is no need to provide an Add()method for the Farmclass. This alternative technique is appropriate when you have a class that contains multiple collections. In this case, there is no obvious candidate for a collection to add to with an Add()method of the containing class.
Type Inference
Earlier in this book you saw how C# is a strongly typed language, meaning that every variable has a fixed type and can only be used in code that takes that type into account. In every code example you ’ ve seen so far you ’ ve declared variables with code of the following form:
type varName;
or
type varName = value;
The following code shows at a glance what type of variable varNameis:
int myInt = 5;
Console.WriteLine(myInt);
You can also see that the IDE is aware of the variable type simply by hovering the mouse pointer over the variable identifier, as shown in Figure 14 - 3 .
Figure 14-3
C# 3.0 introduces the new keyword var, which you can use as an alternative for typein the preceding code:
var varName = value;
In this code, the variable varNameis implicitly typed to the type of value. Note that there is no type called var. In the code
var myVar = 5;
myVaris a variable of type int, not of type var. Again, as shown in Figure 14 - 4 , the IDE is aware of this.
Figure 14-4
This is an extremely important point. When you use var you are not declaring a variable with no type, or even a type that can change. If that were the case, C# would no longer be a strongly typed language. All you are doing is relying on the compiler to determine the type of the variable.
If the compiler is unable to determine the type of variable declared using var, then your code will not compile. Therefore, you can ’ t declare a variable using varwithout initializing the variable at the same time, because if you did there would be no value that the compiler could use to determine the type of the variable. The following code, therefore, will not compile:
var myVar;
The var keyword can also be used to infer the type of an array through the array initializer:
var myArray = new[] {4, 5, 2};
In this code, the type of myArrayis implicitly int[]. When you implicitly type an array in this way, the array elements used in the initializer must be one of the following:
. All the same type.
. All the same reference type or null .
. All elements that can be implicitly converted to a single type.
If the last of these rules is applied, the type that elements can be converted to is referred to as the best type for the array elements. If there is any ambiguity as to what this best type might be — that is, if there are two or more types that all the elements can be implicitly converted to — your code will not compile. Instead, you receive the error indicating that no best type is available:
var myArray = new[] {4, “not an int”, 2};
Note also that numeric values are never interpreted as nullable types, so the following code will not compile:
var myArray = new[] {4, null, 2};
You can, however, use a standard array initializer to make this work:
var myArray = new int?[] {4, null, 2};
A final point: The identifier varis not a forbidden identifier to use for a class name. This means, for example, that if your code has a class called var in scope (in the same namespace or in a referenced namespace), then you cannot use implicit typing with the varkeyword.
In itself, type inference is not particularly useful because in the code you ’ ve seen in this section it only serves to complicate things. Using var makes it more difficult to see at a glance what type a given variable is. However, as you will see later in this chapter, the concept of inferred types is important because it underlies other techniques. The next subject, anonymous types, is one for which inferred types are essential.
Anonymous Types
After programming for a while you may find, especially in database applications, that you spend a lot of time creating simple, dull classes for data representation. It is not unusual to have families of classes that do absolutely nothing other than expose properties. The Curryclass shown earlier in this chapter is a perfect example:
public class Curry
{
public string MainIngredient {get; set;}
public string Style {get; set;}
public int Spiciness {get; set;}
}
This class doesn ’ t actually do anything — it merely stores structured data. In database or spreadsheet terms, you could think of this class as representing a row in a table. A collection class that was capable of holding instances of this class would be a representation of multiple rows in a table or spreadsheet.
This is a perfectly acceptable use of classes, but writing the code for these classes can become monotonous, and any modifications to the underlying data schema requires you to add, remove, or modify the code that defines the classes.
Anonymous types are a way to simplify this programming model. The idea behind anonymous types is that rather than define these simple data storage types, you can instead use the C# compiler to automatically create types based on the data that you want to store in them.
The preceding Currytype can be instantiated as follows:
Curry curry = new Curry { MainIngredient = “Lamb”, Style = “Dhansak”, Spiciness = 5 };
Alternatively, you could use an anonymous type, as in the following code:
var curry = new { MainIngredient = “Lamb”, Style = “Dhansak”, Spiciness = 5 };
There are two differences here. First, the var keyword is used. That ’ s because anonymous types do not have an identifier that you can use. Internally they do have an identifier, as you will see in a moment, but it is not available to you in your code. Second, no type name is specified after the new keyword. That is how the compiler knows that you want to use an anonymous type.
The IDE detects the anonymous type definition and updates IntelliSense accordingly. With the preceding declaration, you can see the anonymous type as shown in Figure 14 - 5.
Figure 14-5
Here, internally, the type of the variable curryis ‘ a. Obviously, you can ’ t use this type in your code — it ’ s not even a legal identifier name. The ‘is simply the symbol used to denote an anonymous type in IntelliSense. IntelliSense also enables you to inspect the members of the anonymous type, as shown in Figure 14 - 6.
Figure 14-6
Note that the properties shown here are defined as read - only properties. This means that if you want to be able to change the values of properties in your data storage objects, you cannot use anonymous types.
The other members of anonymous types are implemented, as you will see in the following Try It Out.
Try It Out - Anonymous Types
1. Create a new console application called Ch14Ex02 and save it in the directory C:\BegVCSharp\Chapter14.
2. Modify the code in Program.csas follows:
static void Main(string[] args)
{
var curries = new[]
{
new { MainIngredient = “Lamb”, Style = “Dhansak”, Spiciness = 5 },
new { MainIngredient = “Lamb”, Style = “Dhansak”, Spiciness = 5 },
new { MainIngredient = “Chicken”, Style = “Dhansak”, Spiciness = 5 }
};
Console.WriteLine(curries[0].ToString());
Console.WriteLine(curries[0].GetHashCode());
Console.WriteLine(curries[1].GetHashCode());
Console.WriteLine(curries[2].GetHashCode());
Console.WriteLine(curries[0].Equals(curries[1]));
Console.WriteLine(curries[0].Equals(curries[2]));
Console.WriteLine(curries[0] == curries[1]);
Console.WriteLine(curries[0] == curries[2]);
Console.ReadKey();
}
3. Run the application. The result is shown in Figure 14 - 7 .
Figure 14-7
How It Works
In this example you create an array of anonymous type objects that you then proceed to use to perform tests of the members supplied by anonymous types. The code to create the array of anonymously typed objects is as follows:
var curries = new[]
{
new { MainIngredient = “Lamb”, Style = “Dhansak”, Spiciness = 5 },
new { MainIngredient = “Lamb”, Style = “Dhansak”, Spiciness = 5 },
new { MainIngredient = “Chicken”, Style = “Dhansak”, Spiciness = 5 }
};
This uses an array that is implicitly typed to an anonymous type, using a combination of syntax from this section and the “ Type Inference ” section earlier in this chapter. The result is that the curries variable contains three instances of an anonymous type.
The first thing the code does after creating this array is to output the result of calling ToString()on the anonymous type:
Console.WriteLine(curries[0].ToString());
This results in the following output:
{ MainIngredient = Lamb, Style = Dhansak, Spiciness = 5 }
The implementation of ToString()in an anonymous type is to output the values of each property defined for the type.
The code next calls GetHashCode()on each of the array ’ s three objects:
Console.WriteLine(curries[0].GetHashCode());
Console.WriteLine(curries[1].GetHashCode());
Console.WriteLine(curries[2].GetHashCode());
When implemented, GetHashCode()should return a unique integer for an object based on the state of the object. The first two objects in the array have the same property values, and therefore the same state. The result of these calls is the same integer for each of these objects, but a different integer for the third object. The output is as follows:
294897435
294897435
621671265
Next, the Equals()method is called to compare the first object with the second object, and then to compare the first object with the third object:
Console.WriteLine(curries[0].Equals(curries[1]));
Console.WriteLine(curries[0].Equals(curries[2]));
The result is as follows:
True
False
The implementation of Equals()in anonymous types compares the state of objects. The result is true where every property of one object contains the same value as the comparable property on another object.
That is not what happens when you use the == operator, however. The == operator, as shown in previous chapters, compares object references. The last section of code performs the same comparisons as the previous section of code, but uses ==instead of Equals():
Console.WriteLine(curries[0] == curries[1]);
Console.WriteLine(curries[0] == curries[2]);
Each entry in the curries array refers to a different instance of the anonymous type, so the result is falsein both cases. The output is as expected:
False
False
Interestingly, when you created instances of the anonymous types, the compiler noticed that the parameters are the same and created three instances of the same anonymous type — not three separate anonymous types. However, this doesn ’ t mean that when you instantiate an object from an anonymous type the compiler looks for a type to match it with. Even if you have defined a class elsewhere that has matching properties, if you use anonymous type syntax, an anonymous type will be created (or reused as in this example).
Extension Methods
Extension methods are a way to extend the functionality of types without modifying the types themselves. You can even use extension methods to extend types that you cannot modify — including types defined in the .NET Framework. Using an extension method, for example, you could even add functionality to something as fundamental as the System.Stringtype.
In this context, to extend the functionality of a type means to provide a method that can be called through an instance of that type. The method you create to do this, known as the extension method, can take any number of parameters and return any return type (including void). To create and use an extension method, you must do the following:
1. Create a non - generic static class.
2. Add the extension method to the class you have created as a static method, using extension method syntax (described shortly).
3. Ensure that the code where you want to use the extension method imports the namespace containing the extension method class with a usingstatement.
4. Call the extension method through an instance of the extended type as if you were calling any other method of the extended type.
The C# compiler works its magic between step 3 and step 4. The IDE is instantly aware that you have created an extension method, and even displays it in IntelliSense, as shown in Figure 14 - 8 .
Figure 14-8
In Figure 14 - 8, an extension method called MyMarvelousExtensionMethod()is available through a string object (here just a literal string). This method, which is denoted with a slightly different method icon that includes a blue, downward - pointing arrow, takes no additional parameters and returns a string.
To define an extension method you define a method in the same way as any other method, but that method must meet the requirements of extension method syntax. These requirements are as follows:
. The method must be static.
. The method must include a parameter to represent the instance of the type that the extension method will be called on. (This parameter will be referred to here as the instance parameter.)
. The instance parameter must be the first parameter defined for the method.
. The instance parameter must have no other modifier other than the thiskeyword.
The syntax for an extension method is as follows:
public static class ExtensionClass
{
public static ReturnType ExtensionMethodName(this TypeToExtend instance)
{
...
}
}
Once you have imported the namespace containing the static class that includes this method (which is known as making the extension method available), you can write code as follows:
TypeToExtend myVar;
// myVar is initialized by code not shown here.
myVar.ExtensionMethodName();
You can also include any additional parameters you want in the extension method, and make use of its return type.
Effectively, this call is identical to the following, but with simpler syntax:
TypeToExtend myVar;
// myVar is initialized by code not shown here.
ExtensionClass.ExtensionMethodName(myVar);
The other advantage is that, once imported, you can find the functionality you need much more easily by looking at extension methods through IntelliSense. Extension methods may be spread across multiple extension classes, or even libraries, but they will all show up in the member list of the extended type.
When you define an extension method that can be used with a particular type, you can use it with any types that derive from this type. Referring back to an example used earlier in this chapter, if you defined an extension method for the Animalclass, you could call it on, for example, a Cowobject.
Extension methods provide a fantastic way to provide libraries of utility code that you can reuse across your applications. They are also used extensively in LINQ, which you will learn about later in this book. To better understand them, work through a full Try It Out example.
Try It Out : Extension Methods
1. Create a new console application called Ch14Ex03 and save it in the directory C:\BegVCSharp\Chapter14.
2. Add a new class library project to the solution called ExtensionLib.
3. Remove the existing Class1.cs class file from ExtensionLiband add a new class called ExtensionLibExtensions.
4. Modify the code in ExtensionLibExtensions.csas follows:
public static class ExtensionLibExtensions
{
public static string ToTitleCase(this string inputString, bool forceLower)
{
inputString = inputString.Trim();
if (inputString == “”)
{
return “”;
}
if (forceLower)
{
inputString = inputString.ToLower();
}
string[] inputStringAsArray = inputString.Split(‘ ‘);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < inputStringAsArray.Length; i++)
{
if (inputStringAsArray[i].Length > 0)
{
sb.AppendFormat(“{0}{1} “,
inputStringAsArray[i].Substring(0, 1).ToUpper(),
inputStringAsArray[i].Substring(1));
}
}
return sb.ToString(0, sb.Length - 1);
}
public static string ReverseString(this string inputString)
{
return new string(inputString.ToCharArray().Reverse().ToArray());
}
public static string ToStringReversed(this object inputObject)
{
return inputObject.ToString().ReverseString();
}
}
5. Add a project reference to the ExtensionLib project to the Ch14Ex03 project.
6. Modify the code in Program.csas follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExtensionLib;
namespace Ch14Ex03
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“Enter a string to convert:”);
string sourceString = Console.ReadLine();
Console.WriteLine(“String with title casing: {0}”,
sourceString.ToTitleCase(true));
Console.WriteLine(“String backwards: {0}”, sourceString.ReverseString());
Console.WriteLine(“String length backwards: {0}”,
sourceString.Length.ToStringReversed());
Console.ReadKey();
}
}
}
7. Run the application. When prompted, type in a string (at least 10 characters long and more than one word for the best effect). An example result is shown in Figure 14 - 9.
Figure 14-9
How It Works
This example created a class library containing utility extension methods, which you used on a simple client application. The class library includes a static class called ExtensionLibExtensionsthat contains the extension methods, and you imported the ExtensionLibnamespace that contains this class into the client application, thus making the extension methods available.
You created the three extension methods shown in the following table:
The client code used each of these methods in turn, using the string you input as a starting point.
The first of these extension methods, ToTitleCase(), contains the most code but is the simplest method to understand. It is defined by using the extension method syntax shown earlier. You can see that this extension method will apply to stringobjects because the instance parameter is a string. There is also a second parameter defined, forceLower. When you call this extension method, though, only one parameter is used because the instance parameter comes from the object from which the extension method is called.
The code for this method starts by preparing the input string by trimming whitespace and (optionally) converting it to lowercase:
public static string ToTitleCase(this string inputString, bool forceLower)
{
inputString = inputString.Trim();
if (inputString == “”)
{
return “”;
}
if (forceLower)
{
inputString = inputString.ToLower();
}
Next, the string is split into an array of single words, and the array is parsed in a forloop that assembles the result string in a StringBuilderobject:
string[] inputStringAsArray = inputString.Split(‘ ‘);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < inputStringAsArray.Length; i++)
{
if (inputStringAsArray[i].Length > 0)
{
sb.AppendFormat(“{0}{1} “,
inputStringAsArray[i].Substring(0, 1).ToUpper(),
inputStringAsArray[i].Substring(1));
}
}
Finally, the result string is returned. The last character of the string is a space because of the processing in the for loop, so that character is trimmed when the string is returned:
return sb.ToString(0, sb.Length - 1);
}
The next extension method, ReverseString(), looks simple, but looks can be deceptive. The sequence of operations carried out by this method is straightforward: A string is compared to a char array, the elements in this array are reversed, and the reversed array is converted back into a string, which is then returned. However, the method that reverses the elements in the chararray is itself an extension method. This method is Reverse(), and is defined in the System.Linqnamespace as follows:
public static IEnumerable < TSource > Reverse < TSource > (this IEnumerable < TSource > source);
This looks more complicated than it is, as it uses generics. Extension methods can use generic type parameters much like any other method. The Reverse()method uses a single generic type parameter, TSource, where the instance parameter used by the generic method must support the IEnumerable < TSource > interface. In the ReverseString()method, the Reverse()method is called on an object of type char[]. An array of type TSourcesupports the IEnumerable < TSource > interface by definition, so an array of type charsupports the IEnumerable < char > interface. The version of Reverse()that is called is therefore as follows:
public static IEnumerable < char > Reverse < char > (this IEnumerable < char > source);
In ReverseString()the resultant IEnumerable < char > interface is converted back into a char[] object by calling its ToArray()method, and this char[]object is used in the constructor of a new stringobject. This string object, finally, is returned:
public static string ReverseString(this string inputString)
{
return new string(inputString.ToCharArray().Reverse().ToArray());
}
The System.Linq namespace contains a large array of extension methods, most of which are designed for use with LINQ, as you will see later in the book. Many, however, can be useful in other situations, as shown here.
You can see these extension methods through IntelliSense if you have the System.Linqnamespace imported — which it is by default. However, don ’t concern yourself with them at this point because many of these extension methods use lambda expressions, which are covered in the next section.
The final extension method in the example, ToStringReversed(), is an example of a more general extension method. Rather than require a string type instance parameter, this method instead has an instance parameter of type object. This means that this extension method can be called on any object and will show up in IntelliSense on every object you use. There isn ’ t a lot you can do in this extension method, as you cannot assume very much about the object that might be used. You could use the is operator or try conversion to find out what the instance parameter type is and act accordingly, or you could do what is done in this example and use basic functionality that is supported by all objects — the ToString()method:
public static string ToStringReversed(this object inputObject)
{
return inputObject.ToString().ReverseString();
}
This method simply calls the ToString()method on its instance parameter and reverses it using the ReverseString()method described earlier. In the example client application, the ToStringReversed()method is called on an int variable, which results in a string representation of the integer with its digits reversed.
Extension methods that can be used with multiple types can be very useful. Remember as well that you can define generic extension methods, which can have constraints applied to the types that can be used, as shown in Chapter 12 .
Lambda Expressions
Lambda expressions are a new construct in C# 3.0 that you can use to simplify certain aspects of C# programming, in particular when combined with LINQ. They can be difficult to grasp at first, mainly because they are so flexible in their usage. Lambda expressions are extremely useful when combined with other C# language features, such as anonymous methods. Without looking at LINQ, a subject left until later in the book, anonymous methods are the best entry point for examining this subject. Start with a quick refresher.
Anonymous Methods Recap
In Chapter 13 you learned about anonymous methods, methods that you supply inline, where a delegate type variable would otherwise be required. When you add an event handler to an event, the sequence of events is as follows:
1. Define an event handler method whose return type and parameters match those of the delegate required for the event you want to subscribe to.
2. Declare a variable of the delegate type used for the event.
3. Initialize the delegate variable to an instance of the delegate type that refers to the event handler method.
4. Add the delegate variable to the list of subscribers for the event.
In practice, things are a bit simpler than this because you typically won ’ t bother with a variable to store the delegate — you will just use an instance of the delegate when you subscribe to the event.
This was the case when you used the following code in Chapter 13 :
Timer myTimer = new Timer(100);
myTimer.Elapsed += new ElapsedEventHandler(WriteChar);
This code subscribes to the Elapsedevent of a Timerobject. This event uses the ElapsedEventHandler delegate type, which is instantiated using a method identifier, WriteChar. The result here is that when the Timerraises the Elapsedevent, the WriteChar()method is called. The parameters passed to WriteChar()depend on the parameter types defined by the ElapsedEventHandlerdelegate and the
values passed by the code in Timerthat raises the event.
In fact, the C# compiler can achieve the same result with even less code:
myTimer.Elapsed += WriteChar;
The C# compiler knows the delegate type required by the Elapsedevent, so it can fill in the blanks. However, this isn ’ t advisable in most circumstances because it makes it harder to read your code and know exactly what is happening. When you use an anonymous method, the sequence of events shown earlier is reduced to a single step:
1. Use an inline, anonymous method that matches the return type and the parameters of the delegate required by an event to subscribe to that event.
The inline, anonymous method is defined by using the delegatekeyword:
myTimer.Elapsed += delegate(object source, ElapsedEventArgs e)
{
Console.WriteLine(“Event handler called after {0} milliseconds.”,
(source as Timer).Interval);
};
This code works just as well as using the event handler separately. The main difference is that the anonymous method used here is effectively hidden from the rest of your code. You cannot, for example, reuse this event handler elsewhere in your application. In addition, the syntax used here is, for want of a better description, a little clunky. The delegate keyword is instantly confusing because it is effectively being overloaded — you use it both for anonymous methods and for defining delegate types.
Lambda Expressions for Anonymous Methods
This brings us to lambda expressions. Lambda expressions are a way to simplify the syntax of anonymous methods. In fact, they are more than that, but let ’ s keep things simple for now. Using a lambda expression, you can rewrite the code at the end of the previous section as follows:
myTimer.Elapsed += (source, e) = > Console.WriteLine(
“Event handler called after {0} milliseconds.”, (source as Timer).Interval);
At first glance this looks . . . well, a little baffling (unless you are familiar with so - called functional programming languages such as Lisp or Haskell, that is). However, if you look closer you can see, or at least infer, how this works and how it relates to the anonymous method that it replaces. The lambda expression is made up of three parts:
. A list of (untyped) parameters in parentheses
. The = > operator
. A C# statement
The types of the parameters are inferred from the context, using the same logic shown in the section “ Anonymous Types ” earlier in this chapter. The = > operator simply separates the parameter list from the expression body. The expression body is executed when the lambda expression is called.
The compiler takes this lambda expression and creates an anonymous method that works exactly the same way as the anonymous method in the previous section. In fact, it will be compiled into the same or similar Microsoft Intermediate Language (MSIL) code.
To clarify what occurs in lambda expressions, here ’ s another Try It Out.
Try It Out: Simple Lambda Expressions
1. Create a new console application called Ch14Ex04 and save it in the directory C:\BegVCSharp\Chapter14.
2. Modify the code in Program.csas follows:
namespace Ch14Ex04
{
delegate int TwoIntegerOperationDelegate(int paramA, int paramB);
class Program
{
static void PerformOperations(TwoIntegerOperationDelegate del)
{
for (int paramAVal = 1; paramAVal < = 5; paramAVal++)
{
for (int paramBVal = 1; paramBVal < = 5; paramBVal++)
{
int delegateCallResult = del(paramAVal, paramBVal);
Console.Write(“f({0},{1})={2}”,
paramAVal, paramBVal, delegateCallResult);
if (paramBVal != 5)
{
Console.Write(“, “);
}
}
Console.WriteLine();
}
}
static void Main(string[] args)
{
Console.WriteLine(“f(a, b) = a + b:”);
PerformOperations((paramA, paramB) = > paramA + paramB);
Console.WriteLine();
Console.WriteLine(“f(a, b) = a * b:”);
PerformOperations((paramA, paramB) = > paramA * paramB);
Console.WriteLine();
Console.WriteLine(“f(a, b) = (a - b) % b:”);
PerformOperations((paramA, paramB) = > (paramA - paramB) % paramB);
Console.ReadKey();
}
}
}
3. Run the application. The result is shown in Figure 14 - 10 .
Figure 14-10
How It Works
This example uses lambda expressions to generate functions that can be used to return the result of performing specific processing on two input parameters. Those functions then operate on 25 pairs
of values and output the results to the console.
You start by defining a delegate type called TwoIntegerOperationDelegate to represent a method that takes two int parameters and returns an intresult:
delegate int TwoIntegerOperationDelegate(int paramA, int paramB);
This delegate type is used later when you define your lambda expressions. These lambda expressions compile into methods whose return type and parameter types match this delegate type, as you will see shortly.
Next, you add a method called PerformOperations(), which takes a single parameter of type TwoIntegerOperationDelegate:
static void PerformOperations(TwoIntegerOperationDelegate del)
{
The idea behind this method is that you can pass it a delegate instance (or an anonymous method or lambda expression because these constructs compile to delegate instances) and the method will call the method represented by the delegate instance with an assortment of values:
for (int paramAVal = 1; paramAVal < = 5; paramAVal++)
{
for (int paramBVal = 1; paramBVal < = 5; paramBVal++)
{
int delegateCallResult = del(paramAVal, paramBVal);
The parameters and results are then output to the console:
Console.Write(“f({0},{1})={2}”,
paramAVal, paramBVal, delegateCallResult);
if (paramBVal != 5)
{
Console.Write(“, “);
}
}
Console.WriteLine();
}
}
In the Main()method you create three lambda expressions and use them to call PerformOperations() in turn. The first of these calls is as follows:
Console.WriteLine(“f(a, b) = a + b:”);
PerformOperations((paramA, paramB) = > paramA + paramB);
The lambda expression used here is as follows:
(paramA, paramB) = > paramA + paramB
Again, this breaks down into three parts:
1. A parameter definition section. Here there are two parameters, paramAand paramB . These parameters are untyped, meaning the compiler can infer the types of these parameters according to the context. In this case the compiler can determine that the PerformOperations() method call requires a delegate of type TwoIntegerOperationDelegate. This delegate type has two int parameters, so by inference both paramAand paramB are typed as intvariables.
2. The = > operator. This separates the lambda expression parameters from the lambda expression body.
3. The expression body. This specifies a simple operation, which is the summation of paramA and paramB. Notice that there is no need to specify that this is a return value. The compiler knows that to create a method that can be used with TwoIntegerOperationDelegate , the method must have a return type of int. Because the operation specified, paramA+ paramB, evaluates to an int, and there is no additional information supplied, the compiler infers that the result of this expression should be the return type of the method.
In longhand then, you can expand the code that uses this lambda expression to the following code that uses an anonymous method:
Console.WriteLine(“f(a, b) = a + b:”);
PerformOperations(delegate(int paramA, int paramB)
{
return paramA + paramB;
});
The remaining code performs operations using two different lambda expressions in the same way:
Console.WriteLine();
Console.WriteLine(“f(a, b) = a * b:”);
PerformOperations((paramA, paramB) = > paramA * paramB);
Console.WriteLine();
Console.WriteLine(“f(a, b) = (a - b) % b:”);
PerformOperations((paramA, paramB) = > (paramA - paramB) % paramB);
Console.ReadKey();
The last lambda expression involves more calculations but is no more complicated than the others. The syntax for lambda expressions enables you to perform far more complicated operations, as you will see shortly.
Lambda Expression Parameters
In the code you have seen so far, the lambda expressions have used type inference to determine the types of the parameters passed. In fact, this is not mandatory; you can define types if you wish. For example, you could use the following lambda expression:
(int paramA, int paramB) = > paramA + paramB
This has the advantage of making your code more readable, although you lose out in both brevity and flexibility. You could use the implicitly typed lambda expressions from the previous Try It Out for delegate types that used other numeric types, such as longvariables.
Note that you cannot use implicit and explicit parameter types in the same lambda expression. The following lambda expressions will not compile because paramAis explicitly typed and paramB is implicitly typed:
(int paramA, paramB) = > paramA + paramB
Parameter lists in lambda expressions always consist of a comma - separated list of either all implicitly typed or all explicitly typed parameters. If you have only one parameter, then you can omit the parentheses; otherwise, they are required as part of the parameter list, as shown earlier. For example, you could have the following as a single - parameter, implicitly typed lambda expression:
param1 = > param1 * param1
You can also define lambda expressions that have no parameters. This is denoted by using empty parentheses, ():
() = > Math.PI
This could be used where a delegate requiring no parameters but returning a double value is required.
Lambda Expression Statement Bodies
In all the code that you have seen so far, a single expression has been used in the statement body of lambda expressions. You have also seen how this single expression has been interpreted as the return value of the lambda expression, which is, for example, how you can use the expression paramA+ paramB as the statement body for a lambda expression for a delegate with a return type of int(assuming both paramAand paramB are implicitly or explicitly typed to intvalues, as they were in the example code).
An earlier example showed how a delegate with a void return type was less fussy about the code used in the statement body:
myTimer.Elapsed += (source, e) = > Console.WriteLine(
“Event handler called after {0} milliseconds.”, (source as Timer).Interval);
Here, the statement doesn ’ t evaluate to anything, so it is simply executed without any return value being used anywhere.
Given that lambda expressions can be visualized as an extension of the anonymous method syntax, you may not be surprised to learn that you can also include multiple statements as a lambda expression statement body. To do so, you simply provide a block of code enclosed in curly braces, much like any other situation in C# where you must supply multiple lines of code:
(param1, param2) = >
{
// Multiple statements ahoy!
}
If you use a lambda expression in combination with a delegate type that has a non - void return type, then you must return a value with the return keyword, just like any other method:
(param1, param2) = >
{
// Multiple statements ahoy!
return returnValue;
}
For example, earlier you saw how you could rewrite the following code from the Try It Out:
PerformOperations((paramA, paramB) = > paramA + paramB);
as
PerformOperations(delegate(int paramA, int paramB)
{
return paramA + paramB;
});
Alternatively, you could rewrite the code as follows:
PerformOperations((paramA, paramB) = >
{
return paramA + paramB;
});
This is more in keeping with the original code because it maintains implicit typing of the paramA and paramB parameters.
For the most part, lambda expressions are at their most useful — and certainly their most elegant — when used with single expressions. To be honest, if you require multiple statements, your code may read much better if you define a separate, non - anonymous method to use instead of a lambda expression; that also makes your code more reusable.
Lambda Expressions as Delegates and Expression Trees
You have already seen some of the differences between lambda expressions and anonymous methods where lambda methods have more flexibility — for example, implicitly typed parameters. At this point it is worth noting another key difference, although the implications of this will not become apparent until later in the book when you learn about LINQ.
You can interpret a lambda expression in two ways. The first way, which you have seen throughout this chapter, is as a delegate. That is, you can assign a lambda expression to a delegate type variable, as you did in the previous Try It Out.
In general terms, you can represent a lambda expression with up to four parameters as one of the following generic types, all defined in the Systemnamespace:
. Action < > for lambda expressions with no parameters and a return type of void.
. Action < > for lambda expressions with up to four parameters and a return type of void.
. Func < > for lambda expressions with up to four parameters and a return type that is not void.
Action < > has up to four generic type parameters, one for each parameter, and Func < > has up to five generic type parameters, used for up to four parameters and the return type. In Func < > , the return type is always the last in the list.
For example, the following lambda expression, which you saw earlier:
(int paramA, int paramB) = > paramA + paramB
can be represented as a delegate of type Func < int, int, int > because it has two parameters and a return type all of type int.
The second way is to interpret the lambda expression as what is known as an expression tree . An expression tree is an abstract representation of a lambda expression, and as such cannot be executed directly. Instead, you can use the expression tree to analyze the lambda expression programmatically and perform actions in response to the lambda expression.
This is, obviously, a complicated subject. However, expression trees are critical to the LINQ functionality you will learn about later in this book. To give a more concrete example, the LINQ framework includes a generic class called Expression < > , which you can use to encapsulate a lambda expression. One of the ways in which this class is used is to take a lambda expression that you have written in C# and convert it into an equivalent SQL script representation for executing directly in a database.
You don ’ t need to know any more about that at this point. When you encounter this functionality later in the book, you will be better equipped to understand what is going on, as you now have a thorough grounding in the key concepts that C# 3.0 provides.
Lambda Expressions and Collections
Now that you have learned about the Func < > generic delegate, you can understand more of the list of extension methods that the System.Linq namespace provides for array types. For example, there is an extension method called Aggregate(), which is defined with three overloads as follows:
public static TSource Aggregate < TSource > (
this IEnumerable < TSource > source,
Func < TSource, TSource, TSource > func);
public static TAccumulate Aggregate < TSource, TAccumulate > (
this IEnumerable < TSource > source,
TAccumulate seed,
Func < TAccumulate, TSource, TAccumulate > func);
public static TResult Aggregate < TSource, TAccumulate, TResult > (
this IEnumerable < TSource > source,
TAccumulate seed,
Func < TAccumulate, TSource, TAccumulate > func,
Func < TAccumulate, TResult > resultSelector);
As with the extension method shown earlier, this looks at first glance to be impenetrable, but if you break it down you can work it out easily enough. The IntelliSense for this function tells you that it does the following:
Applies an accumulator function over a sequence.
This means an accumulator function (which you can supply in the form of a lambda expression) will be applied for each pair of elements in a collection from beginning to end, with the output of each evaluation becoming one of the inputs of the next.
In the simplest of the three overloads there is only one generic type specification, which can be inferred from the type of the instance parameter. For example, in the following code the generic type specification will be int(the accumulator function is left blank for now):
int[] myIntArray = {2, 6, 3};
int result = myIntArray.Aggregate(...);
This is equivalent to the following:
int[] myIntArray = {2, 6, 3};
int result = myIntArray.Aggregate < int > (...);
The lambda expression that is required here can be deduced from the extension method specification. Because in this code the type TSourceis int, you must supply a lambda expression for the delegate Func < int, int, int > . For example, you could use one you ’ ve seen before:
int[] myIntArray = {2, 6, 3};
int result = myIntArray.Aggregate((paramA, paramB) = > paramA + paramB);
This call results in the lambda expression being called twice, first with paramA= 2 and paramB= 6, and once with paramA = 8 (the result of the first calculation) and paramB = 3. The final result assigned to the variable resultwill be the int value 11 — the summation of all the elements in the array.
The other two overloads of the Aggregate()extension method are similar, but enable you to perform slightly more complicated processing. This is illustrated in the following short Try It Out.
Try It Out : Lambda Expressions and Collections
1. Create a new console application called Ch14Ex05 and save it in the directory C:\BegVCSharp\Chapter14.
2. Modify the code in Program.csas follows:
static void Main(string[] args)
{
string[] curries = { “pathia”, “jalfreze”, “korma” };
Console.WriteLine(curries.Aggregate(
(a, b) = > a + “ “ + b));
Console.WriteLine(curries.Aggregate < string, int > (
0,
(a, b) = > a + b.Length));
Console.WriteLine(curries.Aggregate < string, string, string > (
“Some curries:”,
(a, b) = > a + “ “ + b,
a = > a));
Console.WriteLine(curries.Aggregate < string, string, int > (
“Some curries:”,
(a, b) = > a + “ “ + b,
a = > a.Length));
Console.ReadKey();
}
3. Run the application. The result is shown in Figure 14 - 11 .
Figure 14-11
How It Works
In this example you experiment with each of the overloads of the Aggregate() extension method, using a string array with three elements as source data.
First, a simple concatenation is performed:
Console.WriteLine(curries.Aggregate(
(a, b) = > a + “ “ + b));
The first pair of elements is concatenated into a string using simple syntax. This is far from the best way to concatenate strings — ideally you would use string.Concat()or string.Format()to optimize performance — but here it provides a very simple way to see what is going on. After this first concatenation, the result is passed back into the lambda expression along with the third element in the array, in much the same way as you saw int values being summed earlier. The result is a concatenation of the entire array, with spaces separating entries.
Next, the second overload of the Aggregate()function, which has the two generic type parameters TSourceand TAccumulate, is used. In this case the lambda expression must be of the form Func < TAccumulate, TSource, TAccumulate > . In addition, a seed value of type TAccumulate must be specified. This seed value is used in the first call to the lambda expression, along with the first array element. Subsequent calls take the accumulator result of previous calls to the expression. The code used is as follows:
Console.WriteLine(curries.Aggregate < string, int > (
0,
(a, b) = > a + b.Length));
The accumulator (and, by implication, the return value) is of type int. The accumulator value is initially set to the seed value of 0, and with each call to the lambda expression is summed with the length of an element in the array. The final result is the sum of the lengths of each of the elements in the array.
Next you come to the last overload of Aggregate(). This takes three generic type parameters and differs from the previous version only in that the return value can be a different type from both the type of the elements in the array and the accumulator value. First, this overload is used to concatenate the string elements with a seed string:
Console.WriteLine(curries.Aggregate < string, string, string > (
“Some curries:”,
(a, b) = > a + “ “ + b,
a = > a));
The final parameter of this method, resultSelector, must be specified even if (as in this example) the accumulator value is simply copied to the result. This parameter is a lambda expression of type Func < TAccumulate, TResult > .
In the final section of code, the same version of Aggregate()is used again, but this time with an int return value. Here, resultSelector is supplied with a lambda expression that returns the length of the accumulator string:
Console.WriteLine(curries.Aggregate < string, string, int > (
“Some curries:”,
(a, b) = > a + “ “ + b,
a = > a.Length));
This example hasn ’ t done anything spectacular, but it demonstrates how you can use more complicated extension methods that involve generic type parameters, collections, and seemingly complex syntax. You ’ ll see more of this later in the book.
Summary
This chapter examined the new features that are included in version 3.0 of the C# language, which is the version that you use in Visual Studio 2008 and Visual C# Express Edition 2008. You learned how these features simplify some of the coding required to achieve certain commonly used and/or advanced functionality.
Highlights of this chapter included the following:
. How to use object and collection initializers to instantiate and initialize objects and collections in one step.
. How the IDE and C# compiler are capable of inferring types from context, and how to use the var keyword to permit type inference to be used with any variable type .
. How to create and use anonymous types, which combine the initializer and type inference topics already covered .
. How to create extension methods that can be called on instances of other types without adding code to the definition of these types, and how this technique can be used to supply libraries of utility methods.
. How to use lambda methods to provide anonymous methods to delegate instances, and how the extended syntax of lambda methods makes additional functionality possible .
Most of the C# features that you learned about in this chapter have been added specifically to cater to the new LINQ capabilities of the .NET Framework. Much of the elegance and many of the subtleties of the code that you have seen will only become apparent later. Having said that, you have learned some extremely powerful techniques that you can put to work straight away to improve your C# programming skills.
You have now covered the entire C# language as it stands with version 3.0. However, this is not the same thing as knowing everything about programming with the .NET Framework. The C# language gives you all the tools you need to write .NET applications, but it is the classes available to you in the .NET Framework that give you the raw materials to build with. From this point on in the book you will become increasingly immersed in these classes, and you will learn how to perform a multitude of tasks with them. The next chapter moves away from the console applications you have thus far spent most of your time working with, and starts to use the rich functionality offered by Windows Forms to create graphical user interfaces. As you do this, remember that the underlying principles are the same regardless of the type of application you create. The skills you learned in the first part of this book will serve you well as you progress through the following chapters.
Exercises
1. Why can ’ t you use an object initializer with the following class? After modifying this class to enable the use of an object initializer, give an example of the code you would use to instantiate and initialize this class in one step:
public class Giraffe
{
public Giraffe(double neckLength, string name)
{
NeckLength = neckLength;
Name = name;
}
public double NeckLength {get; set;}
public string Name {get; set;}
}
2. True or false: If you declare a variable of type varyou will then be able to use it to hold any object type.
3. When you use anonymous types, how can you compare two instances to determine whether they contain the same data?
4. Try to correct the following code for an extension method, which contains an error:
public string ToAcronym(this string inputString)
{
inputString = inputString.Trim();
if (inputString == “”)
{
return “”;
}
string[] inputStringAsArray = inputString.Split(‘ ‘);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < inputStringAsArray.Length; i++)
{
if (inputStringAsArray[i].Length > 0)
{
sb.AppendFormat(“{0}”, inputStringAsArray[i].Substring(0, 1).ToUpper());
}
}
return sb.ToString();
}
5. How would you ensure that the extension method in question 4 was available to your client code?
6. Rewrite the ToAcronym method shown here as a single line of code. The code should ensure that strings including multiple spaces between words do not cause errors. Hint: You will require the ?: tertiary operator, the string.Aggregate < string, string > ()extension function, and a lambda expression to achieve this.