CSharp - C#中CoVariance和ContraVariance 类型安全

  显示原文与译文双语对照的内容

我正在从 Jon Skeet深入阅读 C# 。 虽然我已经理解了协变和逆变的概念,但我无法理解这一行:

当SomeType只描述返回类型parameter—and逆变是安全的。当SomeType只描述接受类型参数的操作时,协方差是安全的。

有人可以用一个例子来解释,为什么两个都是安全的,而不是另一个方向?

更新的问题:

我仍然不理解给出的答案。 我将使用本书中的相同示例解释我的关注点- C# In Depth

它解释了使用以下类层次结构:

Covariance and ContraVariance

协方差是:试图从 IEnumerable<Circle> 转换为 IEnumerable<IShape>,但是提到这个转换只是当我们从某个方法返回它的时候,这个转换是安全的,而不是当我们将它作为参数传递时安全。


IEnumerable<IShape> GetShapes()
{
 IEnumerable<Circle> circles = GetEnumerableOfCircles();
 return circles;//Conversion from IEnumerable<Circle> to IEnumerable<IShape> - COVARIANCE
}

void SomeMethod()
{
 IEnumerable<Circle> circles = GetEnumerableOfCircles();
 DoSomethingWithShapes(circles);//Conversion from IEnumerable<Circle> to IEnumerable<IShape> - COVARIANCE
}

void DoSomethingWithShapes(IEnumerable<IShape> shapes)//Why this COVARIANCE is type unsafe??
{
//do something with Shapes
}

相反的差异是:试图从 IEnumerable<IShape> 转换为 IEnumerable<Circle>,这被认为是类型安全的,只在将它作为一个参数发送时进行。


IEnumerable<Circle> GetShapes()
{
 IEnumerable<IShape> shapes = GetEnumerableOfIShapes();
 return shapes;//Conversion from IEnumerable<IShape> to IEnumerable<Circle> - Contra-Variance
//Why this Contra-Variance is type unsafe??
}

void SomeMethod()
{
 IEnumerable<IShape> shapes = GetEnumerableOfIShapes();
 DoSomethingWithCircles(shapes);//Conversion from IEnumerable<IShape> to IEnumerable<Circle> - Contra-Variance
}

void DoSomethingWithCircles(IEnumerable<Circle> circles) 
{
//do something with Circles
}

时间:

假设你创建了一个 ILogger<in T> 接口,它知道如何记录 T的细节。 假设你有一个 Request 类和一个 ExpeditedRequest 子类。 ILogger<Request> 应该可以转换为 ILogger<ExpeditedRequest> 。 毕竟,它可以记录任何请求。


Interface ILogger<in T> where T: Request {
 void Log(T arg);
}

现在想象另一个接口 IRequestProducer<out T>,它在某些队列中获取下一个请求。 你的系统中有不同的请求来源,当然,其中一些仍然有不同的子类。 在这种情况下,我们不能依靠将 IRequestProducer<Request> 转换成 IRequestProducer<ExpeditedRequest> 因为它可以产生一个non-expedited请求。 但是反向转换会工作。


Interface IRequestProducer<T> where T: Request {
 T GetNextRequest();
}

协方差

当SomeType只描述返回类型参数的操作时,协方差是安全

IEnumerable<out T> 接口可能是最常见的协方差示例。 之所以安全是因为它只返回类型 T ( 特别是一个 IEnumerator<out T>,但不接受任何 T 对象作为参数) 。


public interface IEnumerable<out T> : IEnumerable
{
 IEnumerator<T> GetEnumerator();
}

这是因为 IEnumerator<T> 也是协变的并且只返回 T:


public interface IEnumerator<out T> : IDisposable, IEnumerator
{
 T Current { get; }
}

如果你有一个基类派生类称为 Base 和称为 Derived,那么你可以使用象这样的东西:


IEnumerable<Derived> derivedItems = Something();
IEnumerable<Base> baseItems = derivedItems;

也可以这样做是因为每个 derivedItems 上商品 Base,因此它是完全可以接受的一个实例以将它的分配给我们刚才做的方式。 但是,我们不能指定另一种方式:


IEnumerable<Base> baseItems = Something();
IEnumerable<Derived> derivedItems = baseItems;//No good!

因为不能保证 Base的每个实例也是 Derived的实例,所以这是不安全的。

逆变

当SomeType只描述接受类型参数的操作时,逆变是安全

Action<in T> 委托是一个很好的逆变例子。


public delegate void Action<in T>(T obj);

因为它只接受 T 作为参数,但不返回 T,所以它是安全的。

逆变使你可以执行如下操作:


Action<Base> baseAction = b => b.DoSomething()
Action<Derived> derivedAction = baseAction;

Derived d = new Derived();
//These 2 lines do the same thing:
baseAction(d);
derivedAction(d);

因为它完全可以接受,所以将 Derived的一个实例传递给 baseAction 。 但是,它不能在另一种情况下工作:


Action<Derived> derivedAction = d => d.DoSomething()
Action<Base> baseAction = derivedAction;//No good!

Base b = new Base();
baseAction(b);//This is OK.
derivedAction(b);//This does not work because b may not be an instance of Derived!

因为不保证 Base的实例也是 Derived的实例,所以这不安全。

...