C#逆变和协变的理解-----为什么逆变可以把Object类型的类型参数转换成String类型的类型参数
根据可变性的规则,只有接口和委托可以标记可变性。且只有类型参数为引用类型时才可以利用可变性。
不变性:泛型类型的参数不能改变,这表示一个创建一个MyInterface<String>类型的对象时,赋值给它的只能是MyInterface<String>类型
1 namespace ContravariantAndCovariant 2 { 3 public interface MyInterface<T>//没有指定任何可变性,说明这个接口的类型参数是不变的 4 { 5 void Show(T t); 6 } 7 8 public class ShowObject : MyInterface<Object> 9 { 10 public void Show(object t) 11 { 12 Console.WriteLine(t.ToString()); 13 } 14 } 15 16 public class ShowString : MyInterface<String> 17 { 18 public void Show(string t) 19 { 20 Console.WriteLine(t); 21 } 22 } 23 24 public class ShowInt : MyInterface<Int32> 25 { 26 public void Show(int t) 27 { 28 Console.WriteLine(t.ToString()); 29 } 30 } 31 32 class Program 33 { 34 static void Main(string[] args) 35 { 36 //正确 37 MyInterface<String> str_invariant = new ShowString();//只能赋值MyInterface<String>类型 38 //错误 39 MyInterface<String> str_contravariant = new ShowObject();//当赋值其它类型的时候,编译器会报错 40 //CS0266 无法将类型“ContravariantAndCovariant.ShowObject”隐式转换为“ContravariantAndCovariant.MyInterface<string>”。存在一个显式转换(是否缺少强制转换?) 41 42 } 43 } 44 45 }
逆变性:泛型的类型参数可以从一个类变成它的派生类,比如:可以把MyInterface<object>转换成MyInterface<string>。逆变用in表示,因为它出现在输入的位置(方法的参数),in 就是进去的意思。
1 namespace ContravariantAndCovariant 2 { 3 public interface MyInterface<in T>//用in关键字指定T的逆变性 4 { 5 void Show(T t);//T是参数类型 6 } 7 8 public class ShowObject : MyInterface<Object> 9 { 10 public void Show(object t) 11 { 12 Console.WriteLine(t.ToString()); 13 } 14 } 15 16 public class ShowString : MyInterface<String> 17 { 18 public void Show(string t) 19 { 20 Console.WriteLine(t); 21 } 22 } 23 24 public class ShowInt : MyInterface<Int32> 25 { 26 public void Show(int t) 27 { 28 Console.WriteLine(t.ToString()); 29 } 30 } 31 32 class Program 33 { 34 static void Main(string[] args) 35 { 36 MyInterface<object> obj = null; 37 MyInterface<string> str = obj;//可以把MyInterface<object>类型转换为MyInterface<string>类型 38 } 39 } 40 41 }
协变性:泛型的类型参数可以从一个类变成它的基类,比如:可以把MyInterface<string>转换成MyInterface<object>。逆变用out表示,因为它出现在输出的位置(方法返回值),out 就是出的意思。
1 namespace ContravariantAndCovariant 2 { 3 public interface MyInterface<out T>//用out关键字指定T的协变性 4 { 5 T Show(); //T是返回类型 6 } 7 8 public class ShowObject : MyInterface<Object> 9 { 10 public object Show() 11 { 12 return null; 13 } 14 } 15 16 public class ShowString : MyInterface<String> 17 { 18 public string Show() 19 { 20 return null; 21 } 22 } 23 24 class Program 25 { 26 static void Main(string[] args) 27 { 28 //错误 29 //MyInterface<object> obj = null; 30 //MyInterface<string> str = obj;//不能把MyInterface<object>类型转换为MyInterface<string>类型 31 32 //正确 33 MyInterface<string> str = null; 34 MyInterface<object> obj = str;//可以把MyInterface<string>类型转换成MyInterface<Object>类型 35 } 36 } 37 }
CLR是要保证类型的转换是安全的,协变性还好理解,子类是可以转换成父类的,因为子类对象中包括父类对象的所有成员。这个转换是安全的。
但是逆变的话,父类转换成子类是不安全的,当不能里氏转换的时候,父类对象中并没有子类的所有成员。
其实这么看的话是有一个误区,就是关于引用和对象。当逆变的时候,比如上面逆变的代码中:
MyInterface<object> obj = null;
MyInterface<string> str = obj;
str.Show("123")
str是MyInterface<string>类型的变量,但是它引用的对象是MyInterface<Object>类型的,所以当我们调用Show()方法的时候,其实是obj.Show();
obj的参数是object类型的,我们传入的是一个string类型,string---->object的转换是安全的。所以逆变是安全的!
同理的话,协变:
MyInterface<string> str = null;
Myinterface<object> obj = str;
object result = obj.show();
可以看到obj是MyInterface<object>类型的变量,但是它引用的对象是MyInterface<string>类型,所以当我们调用Show()方法的时候,其实是str.Show(),
它返回的是string类型,但是obj.Show()返回的是一个object类型,所以我们用一个object类型的变量result去接收返回值,最终变成 string ----->object类型的转换,所以协变是安全的。