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()); } } //
Delegates (basics)
A delegate is like an enhanced classical pointer. It is type-safe and needs a specific method signature. Delegates can invoke methods, thus they can be used for events.
To declare a delegate simply add the word “delegate” in front of a method definition.
// methods private double Multiply(double a, double b) { return a * b; } private double Add(double a, double b) { return a + b; } // a delegate for any of the above methods private delegate double dCalc(double a, double b);
Assign a method to a delegate and then use the delegate to call the method.
void Delegates1() { dCalc calc = Add; // creates a new delegate Console.WriteLine("Sum " + calc(6.0, 3.0)); calc = Multiply; // creates a new delegate Console.WriteLine("Product " + calc(6.0, 3.0)); Console.ReadLine(); } //
example output:
Sum 9
Product 18
You can use operators to add or remove delegates. The next example shows that a delegate can point to multiple methods. This can be done, because delegates inherit from the System.MulticastDelegate class, which in turn inherits from System.Delegate.
private delegate void dPrint(); private void PrintA() { Console.WriteLine("Print A"); } private void PrintB() { Console.WriteLine("Print B"); } void Delegates2() { Console.WriteLine("--------------------------------"); dPrint print = PrintA; print += PrintB; Console.WriteLine("added A and B"); print(); Console.WriteLine("--------------------------------"); print -= PrintA; Console.WriteLine("removed A"); print(); Console.ReadLine(); } //
example output:
——————————–
added A and B
Print A
Print B
——————————–
removed A
Print B
Delegates do have iterators. You can loop through and/or count them. By now you should easily understand that delegates have nothing to do with pointers (as eg. used in C++). They do not just point somewhere. A delegate is a complex class. And we are dealing with multicast delegates on top of that.
private delegate void dPrint(); private void PrintA() { Console.WriteLine("Print A"); } private void PrintB() { Console.WriteLine("Print B"); } void Delegates3() { dPrint print = PrintA; print += PrintB; print += PrintB; print(); Console.WriteLine("number of invokes: " + print.GetInvocationList().GetLength(0)); foreach (Delegate d in print.GetInvocationList()) { Console.WriteLine(d.Method.Name + "()"); } Console.ReadLine(); } //
example output:
Print A
Print B
Print B
number of invokes: 3
PrintA()
PrintB()
PrintB()
There are some words to learn here:
Covariance describes a return value that is more derived than defined in the delegate.
Contravariance describes a method with parameters that are less derived than those defined in the delegate.
Invariant describes a generic type parameter that is neither marked covariant nor contravariant.
Variance Covariance and Contravariance are collectively called variance.
Covariance looks like standard polymorphism, whereas Contravariance seems counterintuitive. What is important to remember is that a covariant type parameter can be used as the return type of a delegate, and contravariant type parameters can be used as parameter types.
Contravariance was introduced in C# 4. Source code that did not compile in C# 3.5 is now compiling and running very well under C# 4. Contravariance is meaningful for write-only methods. So the input does not produce an output of the same hierarchy.
Covariance example:
class A { public int dummyA = 0; } class B : A { public double dummyB = 0.0; } delegate A dCovariance(); B Test() { return new B(); } void Delegates4() { dCovariance covariance = Test; // test returns class B, not class A as defined in the delegate foreach (Delegate d in covariance.GetInvocationList()) { Console.WriteLine(d.Method.Name + "()"); } Console.ReadLine(); } //
example output:
Test()
Contravariance example:
class A { public int dummyA = 0; } class B : A { public double dummyB = 0.0; } void Test2(A xParameter) { return; } delegate void dContravariance(B xClassB); void Delegates5() { // The parameter in Test2() is of type class "A", but the // delegate was defined with a class "B" parameter. dContravariance contravariance = Test2; foreach (Delegate d in contravariance.GetInvocationList()) { Console.WriteLine(d.Method.Name + "()"); } contravariance(new B()); // <= important !!!! Console.ReadLine(); } //
example output:
Test2()
When calling contravariance(…) we have to pass any class B instance as parameter. And the method Test2() has obviously no problems with this. Class B is more derived. It does make sense now that Test2() can be less derived, doesn’t it?
Contravariance will be discussed in more detail when looking at generic interfaces with the “in T” annotation. One commonly used contravariant interface is IComparer. Lean back for the moment and just have a quick look at a custom interface using the “in T” annotation. That should be enough for today.
interface IPerson<in T> { string Name { get; set; } int Age { get; set; } }