I wrote about Generics at my previous blog post. It is all about type safety and performance. Generic is a powerful fetcher in C# programming language. Today I am taking about Variance in Generic Interface.
Generic Interface
As we all know about the function of interface in programming language, we can create Generic Interfaces also. The idea is the Interface having one or more type parameter(s) which needs to be replaced by the client code.
// ‘T’ is the type parameter. public interface IList<T> : // Implemented interfaces goes here. { // Members goes here. } // Client code replace type parameter with actual data type. IList<int> empAges = new List<int>(); IList<string> empNames = new List<string>();
Also we can create a custom Generic Interface.
// Generic interface. interface IGen<T> { void Set(T myParam); T Get(); } // Generic class. class Gen<T> : IGen<T> { private T _myData; public void Set(T myParam) { _myData = myParam; } public T Get() { return _myData; } } // Client code. IGen<string> myGen = new Gen<string>(); myGen.Set("my name"); Console.WriteLine(myGen.Get());
Variance
By default C# do not allow variance in Generic.
IGen<object> myGen = new Gen<string>(); // Compilation error.
We know that object is a base class for all the types in .NET Framework. But though we can not compile this line of code.
IGen<object> myGen = (IGen<object>)new Gen<string>(); // Runtime exception.
If we try this it compiles but fails at runtime. C# do this because of type safety. Imagine if C# allow this code then we can set a ‘Circle’ object inside ‘string’.
IGen<object> myGen = (IGen<object>)new Gen<string>(); myGen.Set(new Circle()); // Illogical.
This is unacceptable and illogical.
Covariant Interfaces
Now I am slightly changing my code.
interface IGenWrite<T> { void Set(T myParam); } interface IGenRead<T> { T Get(); } class Gen<T> : IGenWrite<T>, IGenRead<T> { private T _myData; public void Set(T myParam) { _myData = myParam; } public T Get() { return _myData; } }
Now the client code.
Gen<string> gen = new Gen<string>(); IGenWrite<string> write = gen; write.Set("my name"); IGenRead<string> read = gen; Console.WriteLine(read.Get());
We know this is OK. Now if we try the following code then what happened?
Gen<string> gen = new Gen<string>(); IGenRead<object> read = gen; // Compilation error. Console.WriteLine(read.Get());
As usual this is a compilation error. But now in this case if you think logically the previous logic is no longer valid in this case. Because Get() method is readonly. It is just returning the value. It is not doing any manipulation with the value. When this is our case then we can allow variance. Just think if Get() method can return ‘string’ then it should return an ‘object’ also. For that we have to tell our interface to allow variance. We have to give an ‘out’ keyword before the type parameter.
interface IGenRead<out T> { T Get(); }
Now the previous code can be successful.
Gen<string> gen = new Gen<string>(); IGenRead<object> read = gen; // Now OK. Console.WriteLine(read.Get());
This is called Covariance.
Contravariant Interfaces
This is just opposite of Covariance. Look at the following code.
public interface IComparer<in T> { int Compare(T x, T y); }
If we implement this interface in our class.
class ObjectComparer : IComparer<Object> { int IComparer<object>.Compare(Object x, Object y) { int xHash = x.GetHashCode(); int yHash = y.GetHashCode(); if (xHash == yHash) return 0; if (xHash < yHash) return -1; return 1; } }
This interface is for comparison ability in class. In our class when we implement this interface then our class objects can be compare. In the implementation we do not doing any object type specific work. We are using GetHashCode() method to get the hash code of the objects and doing our comparison. GetHashCode() method is in ‘object’ class. So all types can call this method. In this case we can allow variance. Think logically again, if we can compare ‘object’ then we should can compare ‘string’. Because ‘string’ is a special type of ‘object’. That is the basic thing of Inheritance. That is why the ‘IComparer’ interface having an ‘in’ keyword before the type parameter. So the following codes are valid.
// We know it is valid. Object x = ...; Object y = ...; ObjectComparer comparer = new ObjectComparer(); IComparer<Object> objectComparator = comparer; int result = objectComparator.Compare(x, y); // But it is also valid now. IComparer<String> stringComparator = comparer;
Summary
Covariance: If the methods in a generic interface can return strings, they can also
return objects. (All strings are objects.)
Contravariance: If the methods in a generic interface can take object parameters,
they can take string parameters. (If you can perform an operation by using an object,
you can perform the same operation by using a string because all strings are objects.)
Note: We can use only reference types in this procedure. Because value types are not in Inheritance that is why we can not use them in Variance. Also we can use ‘in’ and ‘out’ keywords with ‘Interfaces’ and ‘Delegates’ only.