首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >CoVariance和ContraVariance中的类型安全

CoVariance和ContraVariance中的类型安全
EN

Stack Overflow用户
提问于 2015-05-19 03:02:20
回答 3查看 1.1K关注 0票数 9

我正在深入阅读乔恩·斯基特的C#。虽然我已经理解了CoVariance和ContraVariance的概念,但我无法理解这一行:

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

请有人举例说明,为什么两者都在一个方向而不是在另一个方向上是安全的?

最新问题:

从给出的答案来看,我还是不明白。我将尝试用“C# In Depth”一书中的同样例子来解释我的担忧。

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

协方差是:尝试从IEnumerable<Circle>转换到IEnumerable<IShape>,但是需要指出的是,只有当我们从某个方法返回时,这种转换才是类型安全的,而当我们将它作为IN参数传递时,类型安全就不是了。

代码语言:javascript
运行
复制
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
}

CONTRA方差是:尝试从IEnumerable<IShape>转换为IEnumerable<Circle>,在将其作为IN参数发送时,只有在执行时才提到类型安全。

代码语言:javascript
运行
复制
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
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-05-19 03:30:47

协方差

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

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

代码语言:javascript
运行
复制
public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

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

代码语言:javascript
运行
复制
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    T Current { get; }
}

如果您有一个名为Base的基类和一个名为Derived的派生类,那么您可以这样做:

代码语言:javascript
运行
复制
IEnumerable<Derived> derivedItems = Something();
IEnumerable<Base> baseItems = derivedItems;

这是因为derivedItems中的每一项也是Base的一个实例,所以以我们刚才的方式分配它是完全可以接受的。然而,我们不能以另一种方式:

代码语言:javascript
运行
复制
IEnumerable<Base> baseItems = Something();
IEnumerable<Derived> derivedItems = baseItems; // No good!

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

反方差

当SomeType只描述接受类型参数的操作时,反方差是安全的。

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

代码语言:javascript
运行
复制
public delegate void Action<in T>(T obj);

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

反方差允许您这样做:

代码语言:javascript
运行
复制
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是完全可以接受的。然而,它却不起作用:

代码语言:javascript
运行
复制
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的实例。

票数 10
EN

Stack Overflow用户

发布于 2015-05-19 03:18:51

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

代码语言:javascript
运行
复制
Interface ILogger<in T> where T: Request {
     void Log(T arg);
}

现在设想另一个接口IRequestProducer<out T>,它在某个队列中获取下一个请求。您的系统中有不同的请求源,当然,其中一些请求仍然可以有不同的子类。在这种情况下,我们不能依赖于将IRequestProducer<Request>转换为IRequestProducer<ExpeditedRequest>,因为它可能产生一个非加速请求。但反向转换是可行的。

代码语言:javascript
运行
复制
Interface IRequestProducer<T> where T: Request {
    T GetNextRequest();
}
票数 6
EN

Stack Overflow用户

发布于 2015-05-19 07:30:30

在阅读了MSDN之后,我理解了以下两页:

https://msdn.microsoft.com/en-us/library/dd469484.aspx

https://msdn.microsoft.com/en-us/library/dd469487.aspx

实际上,我认为从这本书中也可以清楚地看到书中的C# 4部分,它将解释inoutType Parameters of Generics的关键字。现在,我正在读书的Limitations of Generics in C#部分的C# 1部分。

我想要理解的是:

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

除了安全之外,也不可能以其他方向编写接口方法,因为编译器会抱怨,正如在msdn上的上述两页中所写的那样:

A type can be declared contravariant in a generic interface or delegate if it is used only as a type of method arguments and not used as a method return type.

In a generic interface, a type parameter can be declared covariant if it satisfies the following conditions: The type parameter is used only as a return type of interface methods and not used as a type of method arguments.

现在有两点使我对这一声明感到困惑:

首先,我误解了语句本身--我认为只有当Inteface<T>本身的实例从某个方法返回而不是作为输入传递到某个方法时,Covairance才是安全的。然而,它是关于类型参数T和接口方法。ContraVariance也一样。

第二,现在,当我理解这个语句的含义时--它是关于将泛型类型参数T传递/从接口方法返回的。我想知道为什么在协方差中从接口方法返回T时它是类型安全的,以及为什么它只在将T作为输入传递到ContraVariance中的接口方法时才是类型安全。

为什么只有在T中返回CoVariance时它才是安全类型:

代码语言:javascript
运行
复制
interface IBase<out T>
{
    T Return_T(); // Valid and Type Safe
    void Accept_T(T input) // Invalid and Type UnSafe
}
class Sample<T> : IBase<T> { }
class BaseClass {}
class DerivedClass : BaseClass
{}
IBase<BaseClass> ibase = new Sample<BaseClass>();
IBase<DerivedClass> iderived = new Sample<DerivedClass>();

ibase = iderived; // Can be assinged because `T` is Covariant

BaseClass b = new BaseClass();
DerivedClass d = new DerivedClass();

ibase.Return_T(); //  At runtime, this will return `DerivedClass` which can be assinged to variable of both base and derived class and is type safe
ibase.Accept_T(b); // The compiler will accept this statement, because at compile time, it accepts an instance of `BaseClass`, but at runtime, it actually needs an instance of `DerivedClass`. So, we are eventually assigning an instance of `BaseClass` to `DerivedClass` which is type unsafe.

为什么它只在将T作为ContraVariance中的输入参数传递时才是类型安全的:

代码语言:javascript
运行
复制
interface IBase<in T>
{
    T Return_T(); // Invalid and Type UnSafe
    void Accept_T(T input) // Valid and Type Safe
}
class Sample<T> : IBase<T> { }
class BaseClass {}
class DerivedClass : BaseClass
{}
IBase<BaseClass> ibase = new Sample<BaseClass>();
IBase<DerivedClass> iderived = new Sample<DerivedClass>();

iderived = ibase; // Can be assinged because `T` is Contravariant

BaseClass b = new BaseClass();
DerivedClass d = new DerivedClass();

iderived.Accept_T(d); // This is Type Safe, because both at compile time and runtime, either instance of `DerivedClass` can be assinged to `BaseClass` or instance of `BaseClass` can be assinged to `BaseClass`

DerivedClass d2 = iderived.Return_T(); // This is type unsafe, because this statement is valid at compile time, but at runtime, this will return an instance of `BaseClass` which is getting assinged to `DerivedClass`
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/30316008

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档