New Language Features in CSharp
New Language Features in CSharp
0, Part 1
Four years ago, a new upstart language named C# surprised the development world with its
elegance and consistency. Now that Microsoft has released a technology preview version of
Visual Studio 2005 (formerly codenamed Whidbey), .NET's favorite language is back with
some new innovations. In this two-part series, you'll get a first look at three of the four major
language refinements in the latest version of C#.
Anonymous Methods
In .NET programming, every executable line of code takes place inside of a method, and
every method is explicitly declared at the class level -- until now. In C# 2.0, you can declare a
method inside of another piece of code, where a delegate is expected. This embedded method
is called an anonymous method. (Anonymous methods will look familiar to you if you've used
lambda functions in the Lisp programming language.)
Anonymous methods are primarily useful if you want to quickly hook up an event handler.
For example, here's the standard way to hook up an event handler that responds to the click of
a button (in any version of .NET):
In this example, there are two methods. Form1() initializes the window and connects the
event handlers. ButtonClick() receives the event when the user clicks the button.
Here's how you could rewrite this example using an anonymous method:
When you declare an anonymous method, the C# compiler has some liberty to decide how it
handles the situation. In this example, the compiler creates a new, uniquely named method in
the Form1 class. This anonymous method has access to all the public and private member
variables and properties of the Form1 class.
Even more interestingly, the anonymous method also has access to local variables defined in
the procedure where the anonymous method is declared. Here's an example that shows this
somewhat counterintuitive ability:
To make this work, the C# compiler handles this code a little differently. It actually creates a
new class that it nests inside of the Form1 class. It adds the anonymous method to the new
class, along with a private member variable to store the contents from tempVar.
You don't need to attach an anonymous method to an event. You can also declare an
anonymous method and assign it to a delegate variable. This technique is useful if you need to
pass a delegate as a callback, or if you want to attach the same anonymous method to two
event handlers, as shown here:
Iterators
Often, developers need to create classes that support enumeration. When a class supports
enumeration, you can use the convenient foreach syntax to step through a group of items.
For example, if you create an OrderCatalog class that contains a group of OrderItem
instances, you might want to enumerate over all of the items using this code:
This code gets translated by the C# compiler to use the GetEnumerator() method of the
class. Technically, it looks like this:
Enumerator e = catalog.GetEnumerator();
while (e.MoveNext())
{
OrderItem item = e.Current;
// (Process OrderItem here.)
}
As a result, this pattern only works with classes that implement the IEnumerable interface.
Creating a class that supports enumeration in C# 1.0 ranges from mildly inconvenient to
annoyingly awkward. In C# 2.0, a new language feature known as iterators makes it much
easer to create classes that support enumeration. Instead of building a state machine to keep
track of your position in a collection, you create one public method named GetEnumerator()
that returns all of the values directly using the new yield return keyword.
For example, here's a simple class that yields three pieces of information:
When you use foreach on a DecoratingColors instance, you'll wind up with three strings,
one after the other.
To see the real rewards of iterators, you need to consider a more realistic example. The
following code shows the OrderCatalog class, which contains a private collection of
OrderItem instances. In this example, the GetEnumerator() method traverses all the items in
the private collection in a loop, returning one item each time.
The beauty of iterators is that the enumerable class can concentrate on providing information,
while the consumer can concentrate on retrieving it. Neither part needs to worry about the
actual implementation details of how positioned is maintained.
Partial Classes
Partial classes give you the ability to split a single class into more than one source code (.cs)
file. Here's what a partial class looks like when it's split over two files:
When you build the application, Visual Studio .NET tracks down each piece of MyClass and
assembles it into a complete, compiled class with two methods, MethodA() and MethodB().
Partial classes don't offer much in the way of solving programming problems, but they can be
useful if you have extremely large, unwieldy classes. (Of course, this might be a sign that you
haven't properly factored your problem, in which case you should really break your class
down into separate classes.) The real purpose of partial classes in .NET is to hide
automatically generated designer code.
For example, if you build a .NET form in Visual Studio 2005, your event- handling code is
placed in the source code file for the form, but the designer code that creates and configures
each control and connects the event handlers is nowhere to be seen. In order to see this code,
you need to select Project-->Show All Files from the Visual Studio menu. When you do, a
new file appears with the other half of the class. For example, if you create a new form named
Form1, you'll actually wind up with a Form1.cs file for your code and a Form1.Designer.cs
file that contains the automatically generated part.
Summary
In the next article in this series, you'll learn about the sexiest new language feature in C# (and
no, it isn't multiple inheritance). In the meantime, for a more detailed look at the C# language
rules, surf to the C# language page at MSDN, at msdn.microsoft.com/vcsharp/team/language.
You'll find a link that allows you to download a detailed white paper on the new C# language
specifications, along with useful insight from the C# language designers in the "Ask a
Language Designer" section of the site.
Understanding Abstraction
In a strongly typed programming language like C#, you have two choices for manipulating
objects:
Use the most specific type possible. For example, if you want to manipulate customer
information, use references of type Customer.
Use a more generic type. This might be a base class or an interface that more than one
class shares. For example, if you need to manipulate LocalCustomer and
PremiumCustomer references, treat them both in the same way by using the ICustomer
interface.
Usually, the first approach gives you the most control because you can access every aspect of
the object. However, the second approach has a different advantage. It lets you write generic
logic that can be used with different types of objects, reducing the total lines of code you need
to write and the complexity of your solution. In programming speak, your code attains a
higher level of abstraction, which makes it easier to reuse and maintain.
The problem with programming generically is that sometimes there isn't a useful base class or
interface that's shared by all the types you want to support. Usually, the only solution is to
build a weakly typed class that treats everything as the base type Object.
C# 2.0 finally offers a solution to this problem with a new language feature called Generics.
Generics 101
In C# 2.0, you avoid these headaches by creating classes that are parameterized by type. In
other words, you create a class template that supports any type. When you instantiate that
class, you specify the type you want to use, and from that point on, your object is "locked in"
to the type you chose. (Behind the scenes, .NET actually creates a new strongly typed class
dynamically at runtime.)
To see generics in action consider the following example, which shows the template for a
typesafe ArrayList:
The ObjectList class wraps an ordinary ArrayList. However, it provides strongly typed Add()
and Remove() methods, along with a strongly typed indexes. These members use the type
ItemType, which is really just a placeholder for whatever type you choose when you create
the class. This placeholder is defined in angled brackets after the name of the class.
Here's an example of how you could use the ObjectList class to create an ArrayList collection
that only supports strings:
// The next statement will fail because it has the wrong type.
// In fact, this line won't ever run, because the compiler
// notices the problem and refuses to build the application.
list.Add(4);
Generics is a feature that's supported across all first-class .NET languages, including VB
.NET (unlike the other C# language features). Incidentally, the .NET Framework designers
are well aware of the usefulness of generic collections, and they've already created several
that you can use. You'll find them in the new Systems.Collections.Generic namespace.
Almost every type in this namespace duplicates a type from the System.Collections
namespace. The old collections remain for backward compatibility.
Advanced Generics
There's no limit to how many ways you parameterize a class. In the ObjectList example,
there's only one type parameter. However, you could easily create a class that works with two
or three types of objects, and allows you to make both of these types generic. To use this
approach, just separate each type with a comma (in between the angle brackets at the
beginning of a class).
For example, consider the following ObjectHashTable class. It allows you to define the type
you want to use for the items you are storing (ItemType) and the keys you are using to index
types (KeyType).
Another important feature in generics is the ability to use constraints, which can restrict what
types are allowed. For example, you might want to create a class that supports any type that
meets a certain interface. In this case, just use the new where keyword.
Here's an example that restricts the ObjectList class so that it can only use serializable items.
(One reason you might use this approach is if you want to add another method in ObjectList
that requires serialization, such as a method that writes all the items to a stream.)
You can define as many constraints as you want, as long as you separate them with commas.
Constraints are enforced by the compiler.
Incidentally, generics don't just work with classes. They can also be used in structures,
interfaces, and delegates. In fact, you can even use generics to parameterize a method by type,
as shown here:
The code that calls GenericFunction() defines the type, which is then used for a parameter
to the function and a method.
Summary
For a more detailed look at the C# language and the advanced wrinkles in generics, surf to the
C# Language Page on MSDN at http://msdn.microsoft.com/vcsharp/team/language. You'll
find a link that allows you to download a detailed white paper on the new C# language
specifications, along with useful insights from the C# language designers in the "Ask a
Language Designer" section of the site.