Skip to content

2.0

C# 2.0, released with .NET Framework 2.0 in November 2005, introduced several new features and enhancements over C# 1.0. These features aimed to improve the productivity of developers, provide more powerful programming constructs, and make the language more flexible and expressive. Here are some of the key features introduced in C# 2.0, along with examples and comparisons to C# 1.0:

1. Generics

Generics allow the definition of classes, interfaces, and methods with a placeholder for the type they operate on. This results in stronger type safety and performance improvements over using the System.Object type for collections.

C# 2.0

List<int> numbers = new List<int>();
numbers.Add(1);  // Strongly-typed, no boxing/unboxing

C# < 2.0 In C# 1.0, collections stored object types, requiring casting and boxing/unboxing for value types, which was less efficient and more error-prone.

ArrayList numbers = new ArrayList();
numbers.Add(1);  // Object type, requires boxing for value types
int firstNumber = (int)numbers[0];  // Requires explicit cast

2. Partial Classes

Partial classes allow the code for a single class to be split into multiple files. This is particularly useful for separating generated code from developer-written code.

C# 2.0

// File1.cs
public partial class MyClass {
    public void Method1() { }
}

// File2.cs
public partial class MyClass {
    public void Method2() { }
}

C# < 2.0 In C# 1.0, all code for a class had to be contained within a single file, which could become unwieldy for large classes or when part of the class code was auto-generated (e.g., by designer tools).

3. Anonymous Methods

Anonymous methods provide a way to define inline unnamed methods, useful for event handlers and delegate instantiation without the need to explicitly define a separate method.

C# 2.0

List<int> numbers = new List<int> { 1, 2, 3 };
numbers.ForEach(delegate(int number) {
    Console.WriteLine(number);
});

C# < 2.0 In C# 1.0, you had to explicitly define a method, even for simple delegate invocations, making the code more verbose.

public void PrintNumber(int number) {
    Console.WriteLine(number);
}
// Usage with a delegate
numbers.ForEach(new Action<int>(PrintNumber));

4. Iterators

Iterators simplify the implementation of IEnumerable and IEnumerator patterns using the yield return and yield break statements, making it easier to create custom collection classes.

C# 2.0

public IEnumerable<int> GetNumbers() {
    for (int i = 0; i < 10; i++) {
        yield return i;
    }
}

C# < 2.0 In C# 1.0, implementing custom iterators required manually managing the state machine, which was cumbersome and error-prone.

public class NumberEnumerable : IEnumerable {
    public IEnumerator GetEnumerator() {
        return new NumberEnumerator();
    }
}

public class NumberEnumerator : IEnumerator {
    private int count = -1;
    public bool MoveNext() {
        count++;
        return count < 10;
    }

    public object Current {
        get { return count; }
    }

    // Reset method implementation
}

5. Nullable Types

Nullable types allow value types to represent all the values of an underlying type plus an additional null value, which is particularly useful for database interactions and other scenarios where value types need to represent the absence of a value.

C# 2.0

int? nullableInt = null;
if (nullableInt.HasValue) {
    Console.WriteLine(nullableInt.Value);
} else {
    Console.WriteLine("null");
}

C# < 2.0 In C# 1.0, there was no built-in support for nullable value types. Developers had to use workarounds, such as special value assignments (e.g., using -1 or int.MaxValue to represent an undefined or null integer) or use reference types, which was less efficient.