New features in C# 2.0

There are dozens (hundreds, probably) of pages listing the new features of C# 2.0. However, I never know where to find a good one quickly, and they don't always tell me what I need to know at the time. I figured if I added my own set of pages, I could update them whenever I wanted to, and point other people at them when answering questions. Without further ado then, here are the new features of C# 2.0:

Partial types

Code generators have existed for a long time. In the past, they usually (depending on the language) either "owned" a whole type/module (creating a whole file which shouldn't or couldn't be edited) or reserved sections of files which shouldn't be edited manually. In some cases, where code was generated by a separate tool from something like a database schema, it could be very hard to make changes to the schema and regenerate the code without losing any additions made by hand.

C# 2.0 introduces the concept of a partial type declaration. This is quite simply a single type which spans multiple files, where each file declares the same type using the partial modifier. The files may refer to members declared within one another without problem (just as forward references within C# is already not a problem). Here's an example (which in itself is a complete program). This allows all the auto-generated code which either mustn't be touched on pain of brokenness or shouldn't be touched because you'll lose all your changes anyway to live in a completely separate file to the code you wish to add. It doesn't help much if you want to tweak the generated code, of course, but that's a less common issue.

Test1.cs:

partial class Test
{
                  string name;
    
                  static void Main()
    {
        Test t = new Test("C# 2.0");
        t.SayHello();
    }
}

Test2.cs:

using System;

partial class Test
{
    Test(string name)
    {
                  this.name = name;
    }
    
                  void SayHello()
    {
        Console.WriteLine ("Hi there. My name is {0}.", name);
    }
}

Compile with:

csc Test1.cs Test2.cs

Results:

Hi there. My name is C# 2.0.

A few little things to be aware of:

Aliases

In previous versions of C#, it was impossible to use two different types which had the same name (including namespace) within the same assembly. (The types themselves would have to be defined in different assemblies anyway, of course, but you might want to use them both from the same assembly.)

C# 2.0 introduces the concept of an "alias". This allows you to effectively name an assembly reference when you compile the code, and use that name to disambiguate between names. As well as disambiguating between identical namespace-qualified names, aliases allow you to disambiguate between names which have been declared within an already used namespace and names which belong to the "root" namespace. This is achieved with the predefined alias of global. Here's an example of aliases at work:

Lib.cs

using System;

namespace Foo.Bar
{
                  public class Baz
    {
                  public static void SayHiLib()
        {
            Console.WriteLine ("Hello Lib");
        }
    }
}

Baz.cs:

using System;

namespace Foo.Bar
{
                  public class Baz
    {
                  public static void SayHiNested()
        {
            Console.WriteLine ("Hello Nested");
        }
    }
}

class Baz
{
                  public static void SayHiBaz()
    {
        Console.WriteLine ("Hello Baz");
    }
}

Test.cs:

extern alias X;

namespace Foo.Bar
{
    class Test
    {
        static void Main()
        {
            // Default to using the definition within the same assembly
            // and namespace
            Baz.SayHiNested();
            
            // Disambiguate to use the definition in the root namespace
            global::Baz.SayHiBaz();
            
            // Disambiguate to use the definition in the aliased assembly
            X::Foo.Bar.Baz.SayHiLib();
        }
    }
}    

Compile:

csc /target:library Lib.cs
csc /r:X=lib.dll Baz.cs Test.cs

(The results are exactly what you'd expect.)

There are, I suspect, various subtleties to do with aliases. However, I don't know them and I don't want to know them at the moment - because I think aliases should be avoided wherever possible. In a very few cases they'll be absolutely invaluable, but you should really try to avoid the situation where they're needed from cropping up in the first place.

Static classes

Prior to version 2.0, it was impossible to create a class with no instance constructors in C#. If you didn't declare one, the compiler provided a default constructor for you (a public parameterless constructor which called the parameterless constructor of the base type). For classes which were never meant to be instantiated (usually utility classes such as System.Math), this meant you needed to include a private constructor which you didn't call yourself in order to prevent instantiation.

In C# 2.0, there are static classes. These are simply declared using the static modifier. They cannot be derived from or instantiated, and they have no constructors (it is a compile-time error to provide any yourself, and the compiler won't add one for you). Their members must all be static. Here's an example:

using System;

public static class UtilityClass
{
                  public static void Foo()
    {
        Console.WriteLine ("Hello");
    }
    
                  // Uncommenting this creates a compile-time error,
                  // as static classes can't have instance members
                  // int x;
}

// Uncommenting this creates a compile-time error,
// as static classes can't be derived from
// public class DerivationAttempt : UtilityClass{}

class Test
{
                  static void Main()
    {
                  // Uncommenting this creates a compile-time error,
                  // as static classes cannot be instantiated.
                  // new UtilityClass();
        
                  // You can use static classes in the normal
                  // way in terms of static members though:
        UtilityClass.Foo();
    }
}

Property access modifiers

This is a feature which is long, long overdue. Prior to 2.0, it was impossible to declare a property with one access level for the "getter" and a different access level for the "setter". This has meant that people have written separate SetXXX methods if they wanted a public getter but a more limited setter. Fortunately, this glaring omission has been fixed in 2.0. It's very straightforward - you just add the access modifier to the get or set as desired:

public class Test
{
                  string someProperty;
    
                  public string SomeProperty
    {
        get
        {
                  return someProperty;
        }
        
                  private set
        {
            someProperty = value;
        }
    }
}

The basic rules are that you can't specify an access modifier for both the getter and the setter, and you have to use the "extra" modifier to make access more restrictive than the rest of the property.


Next page: Nullable types and the null coalescing operator

Back to the main C# page.