Daily Archives: January 1, 2014

Generic types (part 1, basics, advanced)

Generic types use type parameters instead of hardcoded types. They accept value types/nullables and reference types. In general generics speed up code execution compared to applications prior to C# 2.0. Since then C# code requires much less cast operations. Type safety and code reusability also went up substantially.
When using generics, the code basically does not know the parameter type. Let’s start with a simple example:

public class GenericClass<T> {
    public void Add(T t) { 
        //AnyClass2 c = new AnyClass2();
        //if (t == c) return;  // compiler error
        //if (t != c) return;  // compiler error
        if (t == null) return;  // ok
} // class

private class AnyClass2 { }

static void Generics1() {
    GenericClass<int> a = new GenericClass<int>();
    GenericClass<string> b = new GenericClass<string>();
    GenericClass<AnyClass2> c = new GenericClass<AnyClass2>();
    GenericClass<GenericClass<AnyClass2>> d = new GenericClass<GenericClass<AnyClass2>>();
} //

Above code compiles and works fine. We used value types and reference types. Class GenericClass<T>{} is using an unbounded type parameter.
The parameter type is unclear. Hence the operators “!=” and “==” cannot be used. The support for these operators is unknown.
Nevertheless you can compare t with null, despite the fact that value types cannot be compared with null. In that case the code evaluates “t == null” as false. Value types can never be null. So there is some logic in the unlogic. We will need that behavior from time to time.
Generics can be constrained to particular data types. In my last post on nullable types (https://csharphardcoreprogramming.wordpress.com/2013/12/31/nullable-types-basics/) I quickly introduced “where T: struct”. Here is the complete list (http://msdn.microsoft.com/en-us/library/d5x73970.aspx):



where T: struct The type argument must be a value type. Any value type except nullable can be specified.
where T : class The type argument must be a reference type; this applies also to any class, interface, delegate, or array type.
where T : new() The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last.
where T : <base class name> The type argument must be or derive from the specified base class.
where T : <interface name> The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic.
where T : U The type argument supplied for T must be or derive from the argument supplied for U.

Constraints are important in case you want to use methods that are unknown to the compiler, because your definition is too unreserved. In the next code example the method Test(T t) knows it deals with IEnumerable<string>. Hence we can use “foreach (string s in t)” without any compiler error.

public class GenericClass2 where T : IEnumerable {
    public void Test(T t) {
        foreach (string s in t) Console.Write(s);
    } //
} // class

static void Generics2() {
    List lList = (new string[] { "hello", " ", "world" }).ToList();
    GenericClass2<List> lGenericClass = new GenericClass2<List>();
} //

We can go even further and add multiple constraints.

class Falcons<T> where T : Bird, IAnimal, IComparable<T>, new() {} 

class Cat { }  // base class
class Tiger<T, U> 
  where U : struct
  where T : Cat, new() { }

The “where T: U” is quite useless. It does not help the compiler to determine any method. The type is still unknown. Nevertheless you can forward that information if required.

class List<T> {
  void Add<U>(List<U> items) where U : T {}
} //

public class SampleClass<T, U, V> where T : V { }  //Type parameter V is used as a type constraint. 

The default keyword in connection with generics is returning the default value of a parameter. It can be null for objects or eg. 0 for value types.

public class GenericClass3<T> {
    public void Test() {
        T lDefaultValue = default(T);
        bool b = lDefaultValue == null;
        Console.WriteLine(typeof(T).ToString() + " default value is: " + lDefaultValue + ", is null: " + b);
    } //
} // class

static void Generics3() {
    GenericClass3<int> i = new GenericClass3<int>();
    GenericClass3<string> s = new GenericClass3<string>();
} //

example output:
System.Int32 default value is: 0, is null: False
System.String default value is: , is null: True