- Shared .NET Language Additions
- VB Language Enhancements
- C# Language Enhancements
- .NET Framework 2.0 Enhancements
- Summary
C# Language Enhancements
The C# language takes another step forward in the 2005 release. We have already pointed out some of the common enhancements such as generics and nullable types. In addition, there are new IDE features for the C# developer. Some of these features include the following:
- Code snippets
- Refactoring
- IntelliSense updates
- Code wizards
- Project properties
We will cover those features throughout the book. However, here we intend to focus on C#-specific enhancements for 2005.
Anonymous Methods
The term anonymous method sounds a bit daunting when you first come across it. However, an anonymous method is simply an unnamed block of code (not a method) that is passed directly to a delegate. First, this feature is available only to C# programmers. Second, it is useful only when you do not need the full power of a delegate—that is, when you do not require multiple listeners nor the ability to control (add and remove) who's listening.
The fastest way to understand anonymous methods is to compare the established, standard way of implementing a delegate to using an anonymous method. In prior versions of C#, to use a delegate, you had to write a method that was called by the delegate. This required you to both write a new method and connect that method to the delegate.
For an example, let's look at the way to connect code to a button's (System.Windows.Forms.Button) Click event. First, the Click event is a System.EventHandler (or delegate). You want to make sure that code you write in a method is connected to that delegate. Suppose the code is in a method that looks as follows:
private void button1_Click(object sender, EventArgs e) { label1.Text = "textBox.Text"; }
You then connect the method to the delegate. Of course, Visual Studio does the work for you behind the scenes. But you can also write this code manually. In addition, Visual Studio only takes care of connecting UI control delegates (events) to the methods. You are responsible for wiring up other delegates—whether they are custom or part of the framework. The following shows how Visual Studio connects the button1_Click method to the Button class's Click event:
this.button1.Click += new System.EventHandler(this.button1_Click);
As you can see, you have to both write a method for the code and connect that method to the delegate. Now let's look at what is possible with anonymous methods. As we've stated, you can simply pass code directly to the delegate. Therefore, you could add the following line of code to the form's constructor (after the call to InitializeComponents):
this.button1.Click += delegate { label1.Text = "Goodbye"; };
As you can see in the example, using an anonymous method involves using the keyword delegate. Of course, delegates can take parameters, so there is an optional parameter list after delegate (not shown in the example). Finally, there is the statement list (or block of code). This is the code passed anonymously (without a method name) to the delegate. This code is set off by curly braces.
This section simply introduces anonymous methods. With them, you can write some reasonably sophisticated code (which can also be difficult to understand). As you might imagine, passing lines of code as parameters requires some careful thinking to stay out of trouble.
Static Classes
The new version of the .NET Framework provides language developers support for static classes. A static class is one whose every member is declared as a noninstance member (or static). That is, consumers of the class do not have to create an instance of the class to call its members. In fact, the Framework ensures that consumers cannot instantiate a static class. Static classes are common to the .NET Framework; however, the VB language does not currently allow for them. The C# language does. This is where we will focus our static class examples.
Defining a Static Class
You create a static class by applying the Static keyword to the class declaration. The following line of code provides an example:
static class ProjectProperties { ...
As indicated, declaring a class as static ensures that it cannot be instantiated. You still must explicitly declare all members of the class as static (they are not assumed as such). However, a static class will allow the compiler to verify that no instance members are added to the class by accident. You will receive a compiler error if you place a nonstatic member inside a static class; this includes both public and private members. Listing 3.1 provides a simple example of a static class and its members.
Listing 3.1. A Static Class
namespace StaticClasses { static class ProjectProperties { static string _projectName; static ProjectProperties() { _projectName = "SomeNewProject"; } public static string Name { get { return _projectName; } } public static DateTime GetDueDate() { //get the date the project is due DateTime dueDate = DateTime.Now.AddDays(10); return dueDate; } } }
Constructors and Static Classes
You cannot create a constructor for a static class. However, if you need similar features of a constructor (like setting initial values), you can create what is called a static constructor. Listing 3.1 shows an example of the static constructor named ProjectProperties. Notice that this constructor initializes the value of the static member _projectName.
The .NET CLR loads the static class automatically when the containing namespace is loaded. In addition, when a static member is called, the static constructor is automatically called by the CLR. No instance of the class is necessary (or even possible) to call this special type of constructor.
Reference Two Versions of the Same Assembly
As a developer, you sometimes get stuck between needing the features of an older version of a component and wanting to upgrade to the latest version of that component. Often, this is the result of a third-party component that has evolved without concern for backward compatibility. In these cases, your options are limited to either a complete upgrade to the new component or sticking with the older version. C# 2.0 now provides an additional option: working with both versions through an external assembly alias.
The principal issue with working with multiple versions of the same assembly is resolving conflicts between the names of members that share the same namespace. Suppose, for instance, that you are working with an assembly that generates charts for your application. Suppose that the namespace is Charting and there is a class called Chart. When a new version of the assembly is released, you want to be able to keep all of your existing code as is but reference the new assembly for your new code. To do so in C# 2.0, you must follow a couple of steps.
First, you must define an alias for the newly referenced assembly. You do this through the Properties window for the selected reference. Figure 3.1 provides an example of setting this alias. Note that we are setting an alias for version 2 of the assembly (ChartV2). This ensures that calls to Charting.Chart will still point to version 1 of the assembly (ChartV1).
Figure 3.1 Defining a reference alias.
Next, in the code file where you plan to use the additional version of the assembly, you must define the external alias. You do so at the top of the file (before the Using statements) with the keyword extern. The following line shows an example of what would be placed at the top of the file to reference the second version of the Charting component:
extern alias ChartV2;
Finally, to use the members of the new version, you use the :: operator (as you would for another, similar C# alias). The following line of code provides an example:
ChartV2::Charting.Chart.GenerateChart();
Friend Assemblies
C# 2.0 allows you to combine assemblies in terms of what constitutes internal access. That is, you can define internal members but have them be accessible by external assemblies. This capability is useful if you intend to split an assembly across physical files but still want those assemblies to be accessible to one another as if they were internal.
You use the new attribute class InternalsVisibleToAttribute to mark an assembly as exposing its internal members as friends to another assembly. This attribute is applied at the assembly level. You pass the name and the public key token of the external assembly to the attribute. The compiler will then link these two assemblies as friends. The assembly containing the InternalsVisibleToAttribute will expose its internals to the other assembly (and not vice versa). You can also accomplish the same thing by using the command-line compiler switches.
Friend assemblies, like most things, come at a cost. If you define an assembly as a friend of another assembly, the two assemblies become coupled and need to coexist to be useful. That is, they are no longer a single unit of functionality. This can cause confusion and increase management of your assemblies. It is often easier to stay away from this feature unless you have a very specific need.