/* CSC 123/252: Notes on Variant Types In Java and in C#, all reference types are subclasses of type object. So object x = "abc"; is perfectly valid, since strings are also objects. In general, if A extends/implements B, then B x = new A() is valid. But then how about the following: object[] A = new string[10]; is an array of strings also an array of objects? You might think yes, but then consider: A[0] = 3.14; Would this be accepted by the compiler? Well, if you believe that the above declaration for A type checks, so should this assignment. A double is also an object (at least in C# - in Java use new Double(3.14)). This code compiles! But clearly something is wrong. The code above will result in only a runtime error in Java and C#, not a compiler error. To be more type safe, that is, to be able to catch such errors at compile-time, it's recommended that you use the generic List class in C# and the generic ArrayList class in Java. using System.Collections.Generic; ... List A = new List(); This code will NOT compile. A List of strings is NOT considered a List of objects! This is because the type parameter to the List class is declaraed to be "invariant", whereas the type of arrays are "covariant". The designers of Java and C# decided to allow arrays to be covariant, as long as the type of the array is a reference type (an array of ints is not an array of objects, but an arrays of Integers - in java - is). This was probably done to give programmers some more flexibility, but those concerned with type safety should only use traditional arrays for small, localized vectors. List/ArrayList are generally more robust. ============= Covariance and Contravariance in C# ============= C# has a relatively advanced feature that currently Java does not have. Type parameters to a generic interface (or a generic delegate) can be declared to be covariant, contravariant, or invariant (default invariant). These terms come from a branch of mathematics called category theory, but I will try to explain these ideas using as few mathematical terms as possible. Consider the functions object f1(); string f2(); Clearly, wherever f1() is called, f2() can also be called without destroying type safety, because a string is an object. A function's return type (or OUTPUT parameter) can be "covariant". That is, since string can replace object, f2() can replace f1(). But now consider void g1(object x); void g2(string x); Now the "variance" relationship is reversed. Where g1(y) is called, g2(y) CANNOT be called, because g2 accepts a more specific type (y is any object, not necessarily a string). However, where g2(s) can be called, g1(s) can also be called. That is, if s is a string, and since strings are objects, g1(s) can replace g2(s) without breaking type safety. Thus we say that the INPUT types to a function can be "contravariant". In C# (but not Java), when defining a generic interface (or delegate) we can use the keywords "in" for contravariance and "out" for covariance in front of type parameters. No keyword means that the parameter is "invariant". For example, I can have: interface I { int g(A x); B f(C x); C h(int x); } That is, A can be declared contravariant because it only appears as an input type, B can be declared covariant because it only appears as an output (return) type. But since C is both an input and an output type, it must be declared invariant. Now suppose I have class c1 : I { public int g(object x) {...} public string f(string x) {...} public string h(int x) {...} .... } Then the following is valid: I x = new c1(); The type I is compatible ("is a") with I because the first type parameter is contravariant and the second one is covariant. But I = new c1(); // compiler error will not statically type check because the third parameter is invariant. The type of the List class must be invariant because we need to get elements from a list as well as insert new elements into a list; there is no way that the type of elements in a List can be restricted to just input or just output values of its methods. Before C# 2010 (.Net 4.0), all type parameters must be invariant. Clearly this new feature gives us greater flexibility, without sacraficing any type safety. */ using System; using System.Collections.Generic; // input arguments are contravariant. if A // "out" means T is covariant { T get(); // void set(T x); // error: T can only be a return (out) value. } public class cov1 : COV // don't use int or non-reference type { string x = "abc"; public string get() { return x; } public void set(string x) {this.x =x;} } ////////////// contravariant types can only be input parameters public interface CON // "in" declares T to be contravariant { void set(T x); } public class con1 : CON { object x = null; public object get() {return x;} public void set(object y) {x=y;} } public class variance { public static void Main() { object[] A; A = new string[4]; //for(int i=0;i<4;i++) A[i] = 2; // this compiles! runtime error //List L = new List(); //does not compile (invariant type) //L.Add(new mypi()); COV B = new cov1(); Console.WriteLine(B.get()); CON C = new con1(); C.set("def"); Console.WriteLine(((con1)C).get()); // differ some typing to runtime }//main }