Blog Archives

Generic types (part 2, basics, advanced)

We have looked into generic classes in part 1 https://csharphardcoreprogramming.wordpress.com/2014/01/01/generic-types-part-1-basics-advanced/.
On the following day I used a generic method when explaining reflection https://csharphardcoreprogramming.wordpress.com/2014/01/02/reflection-basics-advanced/.

Let’s look at generic delegates more closely. They look neat and make code, as the name says, more generic.

delegate T Calc<T>(T a, T b);
private static int Add(int a, int b) { return a + b; }
private static double Multiply(double a, double b) { return a * b; }

public static void Generics8() {
    Calc<double> lProduct = Multiply;
    Calc<int> lSum = Add;

    Console.WriteLine("Product:" + lProduct(5.0, 2.0));
    Console.WriteLine("Sum:" + lSum(5, 2));
    Console.ReadKey();
} //

We can also use constraints on delegates:

delegate T Calc<T>(T a, T b) where T : struct;

Generally we know the pattern now. And without much explanation a quick summary should cover all generic types:

// generic delegate
delegate T myDelegate<T>(T t) where T : struct;

// generic method
void myMethod<T, U>(T a, T b, U c) {}

// generic class
class myClass<T> where T : System.IComparable<T>, IEnumerable<T> {}

// generic interface
interface IMyInterFace<out TResult, in TInput> {
    TResult DoSometing(TInput Args);
}

Interfaces can make use of the in and out keywords.
The out keyword declares a generic type parameter covariant. The in keyword makes it contravariant.
Covariance and contravariance were introduced when I described delegates https://csharphardcoreprogramming.wordpress.com/2013/12/16/delegates-basics/ .
The out keyword can only be used as an input parameter if it is a contravariant generic delegate.

interface ICovariant<out T> {
    void DoSomething(Action<T> xCallback);
}

The contravariant type (in) can only be used in method arguments and not as return types, it can also be used for generic constraints.

interface IContravariant<in T> {
    void DoSomething<U>() where U : T;
}

There are certain rules about inheritance.
A covariant class cannot inherit from a contravariant class.
A contravariant class cannot inherit from a covariant class.

interface ICovariant<out T> { }
interface IContravariant<in T> { }

// all ok
interface IInvariant1<T> : ICovariant<T> { }
interface IInvariant2<T> : IContravariant<T> { }
interface IInvariant3<T> : ICovariant<T>, IContravariant<T> { }
interface ICovariant1<out T> : ICovariant<T> { }
interface IContravariant1<in T> : IContravariant<T> { }

// compiler error. 
//interface ICovariant2<out T> : IContravariant<T> { }
//interface ICovariant3<out T> : ICovariant<T>, IContravariant<T> { }
//interface IContravariant2<in T> : ICovariant<T> { }
//interface IContravariant3<in T> : ICovariant<T>, IContravariant<T> { }

There is no generic type for Arrays. Backward compatibility did not allow an evolution here. Arrays are very basic, they can be used for high-performing applications. If Microsoft would make them more complex, we would somehow lose that benefit. My advice is to use generic collections/Lists instead. You can use T[], but this is rather a strongly typed array than a generic array.

public static void Generics9<T>() {
    T[] lArray = new T[10];
    foreach (T lElement in lArray) {
        Console.WriteLine(lElement.ToString());
    }
} //
Advertisements

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
        return;
    }
} // 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):

Constraint

Description

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>();
    lGenericClass.Test(lList);
} //

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>();
    i.Test();
    s.Test();            
} //

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

Nullable types (basics)

You have most likely come across such code:

int? i = 5;

By using “int?”, the integer which usually is a value type, becomes a nullable type. More specifically variable “i” is an instance of System.Nullable<T>. It can hold all values of an integer plus the null value. Nevertheless it behaves more or less like an integer. A nullable type can hold the value null, but not be itself null. Therefore you can still use the variable “i” even though you assign null to it.
Nullable types are very useful when using databases. Database booleans may not be initialized. The System.Nullable<bool> can replicate that properly by allowing 3 values: true, false and null.

static void Main(string[] args) {
    int? i = 5;

    Console.WriteLine(i.ToString());
    i = null;  // important: we assign a null value, we do not assign null to the variable
    Console.WriteLine("null value ToString(): " + i.ToString());  // the variable itself is not null, no exception
    Console.WriteLine("no exception thrown!");

    //string s = null;
    //Console.WriteLine(s.ToString());  // throws a NullReferenceException as expected

    Console.WriteLine(i.HasValue ? "variable i has a value" : "variable i has no value");
    i = 7;
    Console.WriteLine(i.HasValue ? "value of variable i is " + i : "variable i has no value");
    i = null;
    Console.WriteLine("default value is: " + i.GetValueOrDefault()); // i.GetValueOrDefault() returns a value type (=integer)
    //Console.WriteLine("the integer value is: " + i.Value); // throws an exception, null cannot be unboxed

    i = 1;
    int b = 2;
    Console.WriteLine(b + i + 3);  // no problem using operators
    Console.WriteLine(i.Value);

    Console.ReadLine();
} //

example output:
5
null value ToString():
no exception thrown!
variable i has no value
value of variable i is 7
default value is: 0
6

A nullable type can only be created for value types. Reference types can hold null already, there is no need for a nullable reference type. The syntax T? is shorthand for Nullable<T>, where T is a value type. The two variations are interchangeable. You can also use the operators == and != eg. if (i == null) i = 1;

In fact the nullable class is not a class. It is a struct. And this explains a lot. A struct is a value type. If supported you do not need the new keyword to create an instance (custom structs do need the new keyword). And when you assign the nullable (=struct) to another variable then you create a copy of that instance. You do not simply copy a reference, which would be much faster. Parameters and locals are created on the stack, members on the heap. As we are talking about value types, we now understand why variables themselves cannot be null. They can hold the value null though (see the following example code). Classes are more complex, you intent to modify them during their use. In contrast structs are best suited for small data which is not intended to be modified after the struct is created.

The nullable struct could look like this:

struct Nullable<T> where T : struct {
    private readonly bool _HasValue;
    private readonly T _Value;

    public Nullable(T xValue) {
        _HasValue = true;
        _Value = xValue;
    } // constructor

    public bool HasValue {
        get { return _HasValue; }
    } //

    public T Value {
        get {
            if (_HasValue) return _Value;
            throw new ArgumentException();
        }
    } //

    public T GetValueOrDefault() {
        return _Value;
    } //

} // struct

The struct “struct Nullable<T> where T : struct {}” is defined with “where T : struct”. This means the argument T has to be a value type (except a nullable, you cannot create nullables of nullables).
We will take a closer look at the “where T:” in the next post.