C#基础之泛型

2019-12-30 13:42:19王冬梅

c#,泛型

4.泛型中的out和in

  在VS查看IEnumerable<T>的定义时会看到在T前面有一个out,与其对应的还有一个in。这就是.NET中的协变与逆变,刚开始笔者对于这2个概念很晕,主要以下4个疑惑,我想如果你解决了的话应该也会有更进一步的认识。

1.为什么需要协变和逆变,协变与逆变有什么效果?

2.为什么有了协变与逆变就可以类型安全的进行转换,不加out和in就不可以转换?

3.使用协变和逆变需要注意什么?

4.协变与逆变为什么只能用于接口和委托?

下面第一段代码解决了第一个问题。对于第二个问题请看第二段代码,里面对无out、in的泛型为什么不安全讲得很清楚,从中我们要注意到如果要当进行协变时Function2是完全ok的,当进行逆变时Function1又是完全ok的。所以加out只是让开发者在代码里无法使用in的功能,加in则是让开发者无法使用out的功能。读者可以自己动手试试,在out T的情况下作为输入参数将会报错,同样将in T作为返回参数也会报错,且VS报错时会直接告诉你这样只能在协变或逆变情况下使用。也就是说加了out后,只有Function2能够编译通过,这样o=str将不会受Function1的影响而不安全;加了in后,只有Function1能够编译通过,这样str=o将不会受Function2的影响而不安全。使用out和in要注意它们只能用于接口和委托,且不能作用于值类型。out用于属性时只能用于只读属性,in则是只写属性,进行协变和逆变时这2个类型参数必须要有继承关系,现在为什么不能用于值类型你应该懂了吧。对于第四个疑惑我没有找到一个完全正确的答案,只是发现了我认同的想法。接口和委托,有什么共同点?显然就是方法,在接口或委托中声明的T都将用于方法且只能用于方法,由上面的讨论可知协变和逆变这种情况正是适用于方法这样的成员。对于在抽象类中不可以使用的原因,或许微软是觉得在抽象类中再搞一个仅限于方法的限制太麻烦了吧。


public interface INone<T> { }
  public interface IOut<out T> { }
  public interface IIn<in T> { }
  public class MyClass<T> : INone<T>, IOut<T>, IIn<T> { }
  void hh()
  {
   INone<object> oBase1 = new MyClass<object>();
   INone<string> o1 = new MyClass<string>();
   //下面两句都无法编译通过
   //o1 = oBase1;
   //oBase1 = o1;
   //为了能够进行转换,于是出现了协变与逆变
   IOut<object> oBase2 = new MyClass<object>();
   IOut<string> o2 = new MyClass<string>();
   //o2 = oBase2; 编译不通过
   //有了out关键字的话,就可以实现从IOut<string>到IOut<object>的转换-往父类转换
   oBase2 = o2;
   IIn<object> oBase3 = new MyClass<object>();
   IIn<string> o3 = new MyClass<string>();
   //oBase3 = o3; 编译不通过
   //有了in关键字的话,就可以实现从IIn<object>到IOut<string>的转换-往子类转换
   o3 = oBase3;
  }