C# 协变、逆变、不变

类型参数和转换

C#的转化操作符支持下列转换:

  1. 数值转换
  2. 引用转换
  3. 装箱拆箱转换
  4. 自定义转换

决定采用的是哪种转换,发生在编译时,根据已知类型的操作数来决定。
泛型在编译的时候,还不知道具体类型是什么,运行时才知道

类型参数和转换

StringBUilder Foo<T> (T arg)
{
	if(arg is StringBuilder)
	{
		return (StringBuilder)arg;//无法编译,原因是泛型T编译的时候还不知道类型
	}
	...
}

解决方案 1:
StringBuilder Foo<T>(T arg)
{
	StringBuilder sb=arg as StringBuilder;
	if(sb!=null)  return sb;
	...
}

解决方案 2:
return (StringBuilder)(Object)arg;

协变,逆变,不变

协变:当值作为返回值 out输出
逆变:当值作为输入input
不变:当值既输入又输出

public interface IEnumerable<out T>  //协变,作为返回值输出
public delegate void Action<in T>   //逆变,作为输入
public interface IList<T>

variance

协变、逆变、不变都叫做variance,variance 只能出现在接口和委托里

variance转换

1 variance转换是 引用转换 的一个例子。引用转换是指,无法改变底层的值,只能改变编译时的类型。
2 identity conversion 对于CLR而言从一个类型转化到相同的类型

合理的转换

如果从A到B的转换是本体转换或者隐式引用转换,
那么从IEnumerable<A> 到IEnumerable<B>的转换就是合理的,如:
IEnumerable<string> to IEnumerable<object>
IEnumerable<string> to IEnumerable<IConvertible>
IEnumerable<IDisposable> to IEnumerable<object>

不合理的转换

IEnumerable<object> to IEnumerable<string>
IEnumerable<string> to IEnumerable<Stream>
IEnumerable<int> to IEnumerable<Iconvertible>
IEnumerable<int> to IEnumerable<long>

案例

  //引例,转换异常
  IList<string> strings1 = new List<string> { "a", "b", "c" };
  IList<object> objects1 = strings1;//这里会报错,这种转换不安全
  //如果不报错的话,object1可以添加了一个object,
  //那么strings1就可以访问到这个新添加的元素,但是新添加的元素并不是string,是一个object
  objects1.Add(new object());
  var newValue = strings1[3];

//协变(小变大)
//IEnumerable out关键字修饰,只能作为输出 
//协变:IEnumerable<string> 可以转化为 IEnumerable<object>,子类的T可以转化为父类的
IEnumerable<string> strings = new List<string> { "a", "b", "c" };
IEnumerable<object> objects = strings;//合法的

 //逆变(大变小,本质上还是小变大)
 //逆变 Action<object>可以转化为Action<string> 即大变小
 //本质上还是子类转化为父类,调用时传递参数将子类参数传递给父类参数
 Action<object> objectAction =obj => Console.WriteLine(obj);
 Action<string> stringAction = objectAction;//方法是object类型,传进去string是可以的
 stringAction("print me");

//案例
IEnumerable<string> strings2 = new[] { "av", "b", "csadf", "d" };
//不合法  将IEnumerable<string>转化成 List<string>是不合法的,原因是List<T>是不变的
List<object> list = strings2
                   .Where(x => x.Length > 1)
                   .ToList();
//合法
List<object> list1 = strings2.Where(x => x.Length > 1)
                    .Cast<object>()
                    .ToList();
//合法
List<object> list2 = strings2.Where(x => x.Length > 1)
                    .ToList<object>();//发生了协变 从IEnurable<string>转化成了IEnurable<object>


猜你喜欢

转载自blog.csdn.net/weixin_40719943/article/details/106846974