Asynchronous I/O has been present in the .Net Framework from version 1.0, through the Begin/End methods, but it’s only in the recent years when it became a popular topic. There were probably two main reasons for that. One of them was the raise in popularity of node.js which has continuously advertised the event-driven, non-blocking IO … Continue reading
New features in C# 2.0

In this post I will try to present the new things that were introduced in C# 2.0, by creating a short overview which should be able to bring anybody up to speed on them. Readers should be familiar with C# 1.0.
Generics
This topic is too big to be covered in this document. Check the MSDN for details on it. You can also check the following links for more details:
- http://msdn.microsoft.com/en-us/library/512aeb7t.aspx – MSDN topics on generics
- http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx – introduction to generics (MSDN)
- http://blogs.msdn.com/kcwalina/archive/2004/03/15/89860.aspx – best practices when using generics (Krzysztof Cwalina, MSDN blog)
Also note, that with time, content related to generics might be added to this section, however without canceling the above notes.
Variance, covariance on generics
C# does not support variance for generic types, that is you cannot cast from type MyGenType<Type1> to MyGenType<Type0>, even if Type1 inherits from Type0.
Example:
List<int> ints = new List<int>(); ints.Add(1); ints.Add(10); ints.Add(42); List<object> objects = new List<object>(); // doesn't compile ints is not a IEnumerable<object> //objects.AddRange(ints);
Note
I use the term variance because this is term used in MSDN when talking about generics. However covariance is used more often in the technical literature.
Searching on the internet I couldn’t find a definition for covariance that would suit very well the C# language but, after reading from different sources, I could come out with the following one: in oop we say we have covariance when different constructs that depend on certain types reflect the inheritance direction of those types.
For example C#, starting from version 1.0, supports arrays covariance, which means that an array of strings is an array of objects, as a string is an object:
string[] arrStr = new string[] {"one", "two", "three"}; object[] arrObj = arrStr; arrObj[1] = "Hello";
However, array covariance applies only to reference types. Arrays to value types are invariant. The following example will fail to compile:
int[] arrInt = new int[] {1, 2, 3}; object[] arrObj = arrInt;
Stating from C# 2.0, covariance is available for delegates. This might be a hint that it might also be available in the future for generics.
Nullable types
Provide a mechanism to assign the null value to value types. This is especially useful when working with databases. The main ideas behind nullable types are:
- Nullable types are expressed using the
Nullable<T>
generic structure. Example:Nullable<int> ni1 = null; Nullable<int> ni2 = 10;
- There is shorthand notation for nullable types, consisting in adding a ‘?’ sign after the value type. Example: Nullable<int> is equivalent to int?.
- Nullable types work by holding two values: the value of the underlying type and a bool value indicating if the instance is null. This means that when you use a nullable type you use more memory than when you use the underlying type directly. Also, using a nullable type might be slower because of the conversions made in the different operations.
- Only value types have a nullable representation. Reference types accept null as a value by their own nature. For example the statement
string? nstr;
will generate a compiler error. - There is implicit conversion from a value type to its nullable representation. For example, the following statements are valid:
int i = 12; int? ni = i;
int? ni1 = null; int? ni2 = 2; int? ni3 = 3;
Adding two instances of int? will have as result another int? instance.
ni1 + ni2 // equals null. ni2 + ni3 will return an int? // instance with the value of 5. // ---------------------------------------------------- Color? nc1 = Color.Red; Color? nc2 = Color.Blue; Color? nc3 = nc1 + nc2 // will raise a compiler error because the Color structure doesn't implement the addition operator.
int? ni = 5; object obj = ni; Console.WriteLine(ni.GetType() == typeof(int)); // will output true
?? operator (coalesce operator)
It has the following syntax: result = left_val ?? right_val;
and it means: assign left_val to result when left_val is not null or assign right_val to result when left_val is null.
The ?? operator is especially useful when left_val is a nullable type and result is of its underlying type. In this case, if left val is not null, it is casted to its underlying type and assigned to result.
Example:
int? ni = 3; int i = ni ?? -1; // assigns 3 to i. If ni would have been null, -1 would have been assigned to i
Static classes
Are declared using the static keyword and denote classes that cannot be instantiated and can only have static fields and methods. Example:
private static class Program() { private static void Main() { Console.WriteLine("Hello world!"); } }
Properties get/set access modifiers
Starting from C# 2.0, setting different access modifiers for the get/set part of a property is possible. Example:
public string Message { get { return _message; } protected set { _message = value; } }
Partial classes
Are used to declare the same class in multiple source files. This is useful when using code generators. Partial classes follow the following rules:
- A partial class is declared using the partial keyword, which has to be placed immediately before the class, struct or interface keywords.
- All partial-type definitions meant to be parts of the same type must use the partial keyword in their definition.
- All parts from a partial class must be defined in the same assembly.
- Access modifiers, generic constraints, base class and interface implementation declarations, the abstract, sealed, static and new keywords can be used on any partial definition as long they do not conflict with each other. For example:
public partial class MyClass : MyBase, Interface1 { } abstract partial class MyClass : Interface2 { }
is equivalent with:
public abstract class MyClass : MyBase, Interface1, Interface2 { }
- Inner classes can be partial.
Delegate covariance
For more details on covariance check the note, from variance section in Generics. By using covariance, delegates take into account the inheritance properties of the types they contain. The following example reflects that:
private delegate object GetObjectDelegate(); // returns object private static string GetString() // returns string which inherits object { return "Hello world!"; } private static void Main() { // allowed by delegate covariance GetObjectDelegate objectGetter = new GetObjectDelegate(GetString); Console.WriteLine(objectGetter()); }
Anonymous methods
Are used to directly attach blocks of code to delegates and represent another step forward towards functional programming in C#.
Here is an example of how to declare an anonymous method:
MethodInvoker anonymousMethod = delegate { Console.WriteLine("Hello from anonymous method"); }; anonymousMethod();
Here are some rules for anonymous methods:
- You declare an anonymous method by using the delegate keyword, followed by the block of code representing the method’s body, folowed by a colon.
- An anonymous method must be assigned to a delegate. The method can only be invoked by invoking the delegate.
- An anonymous method can use input parameters that will have to be declared immediately after the delegate keyword. Example:
EventHandler anonymousMethod = delegate(object sender, EventArgs e) { Console.WriteLine(sender.GetType().ToString()); }
- An anonymous method is strong typed since it gets its type from the delegate it is being assigned to. However, if the method doesn’t use any parameters in its body, it can omit enumeration them after the delegate keyword. For example, the following statement is valid:
// The EventHandler handler delegate requires two parameters, but since // they are not used in the body of the method, they have been omitted // from the declaration of the anonymous method. EventHandler anonymousMethod = delegate { Console.WriteLine("Hello world"); }
- There is no way you can specify the return type in the declaration of an anonymous method. It will be retrieved from the delegate the method is assigned to. This is the reason why in C# 3.0 you cannot assign an anonymous method to var.
- While you can declare anonymous methods, you cannot declare anonymous delegates, that is declare a delegate by its return type and parameters without naming it. This means you will always need to have a delegate type defined for every type of anonymous method you need to use. To avoid declaring delegates that are going to be used in only one place, the framework provides a series of generic delegates, that can be found under the Func and Action names. Func generic delegates are for methods that return a value, while Action delegates are for methods that return void. Here is the complete list of Action and Func delegates:
public delegate void Action(); public delegate void Action<T1>(T1 arg1); public delegate void Action<T1, T2>(T1 arg1, T2 arg2); public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3); public delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3,T4 arg4); public delegate TResult Func<TResult>(); public delegate TResult Func<T1, TResult>(T1 arg1); public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2); public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3); public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
- Another delegate with a specific meaning defined in the framework is the Predicate delegate. The Predicate delegate is intended to be used for checking when an object corresponds to a criteria defined in the method assigned to the delegate.
public delegate bool Predicate<T>( T obj);
- Anonymous methods allow the use of closures in C#. Example:
private void Test2() { MethodInvoker counter = this.GetCounter(); for (int i = 0; i < 10; i++) counter(); } private MethodInvoker GetCounter() { int val = 0; MethodInvoker counter = delegate { Console.WriteLine("Value = {0}", val++); }; return counter; }
Method Group Conversions
It is more a shorthand than a new feature consisting in the fact that you can assign the name of a method directly to a delegate or event without explicitly initializing the delegate with the new keyword. Example:
private void Test3() { // no need for 'new Action(PrintMessage)' Action action = PrintMessage; action(); } private void PrintMessage() { Console.WriteLine("Hello world!"); }
yield keyword (iterator methods)
In C# 1.0, a class must implement the IEnumerable interface in order to offer foreach functionality. Starting with C# 2.0, iterator methods provide an easier way to implement foreach functionality.
Here are some of the properties of iterator methods:
- Iterator methods use the yield keyword, which can be found in two flavors:
yield return <expression>
, andyield break
. Yield return is called for every element of the iteration, in the order of the iteration. Yield break is called to signal that the iteration is finished. Example:
public class MyClass { private string[] _messages = new string[] {"one", "two", "three"}; public IEnumerator GetEnumerator() { foreach (string str in _messages) yield return str; } } class Program { private static void Main() { MyClass myClass = new MyClass(); foreach (string str in myClass) Console.WriteLine(str); } }
which will output:
one two three
public IEnumerator GetEnumerator() { yield return "one"; yield return "two"; yield return "three"; }
private static void Main() { MyClass myClass = new MyClass(); foreach (string str in myClass.GetHelloMessages()) Console.WriteLine(str); } public class MyClass { private string[] _messages = new string[] { "one", "two", "three" }; public IEnumerable GetHelloMessages() { yield return "Hello world!"; yield return "Bonjour!"; yield return "Salut!"; } }