我正在深入阅读乔恩·斯基特的C#。虽然我已经理解了CoVariance和ContraVariance的概念,但我无法理解这一行:
当SomeType只描述返回类型参数的操作时,协方差是安全的,而当SomeType只描述接受类型参数的操作时,反方差是安全的。
请有人举例说明,为什么两者都在一个方向而不是在另一个方向上是安全的?
最新问题:
从给出的答案来看,我还是不明白。我将尝试用“C# In Depth
”一书中的同样例子来解释我的担忧。
它使用以下类层次结构进行解释:
协方差是:尝试从IEnumerable<Circle>
转换到IEnumerable<IShape>
,但是需要指出的是,只有当我们从某个方法返回时,这种转换才是类型安全的,而当我们将它作为IN参数传递时,类型安全就不是了。
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参数发送时,只有在执行时才提到类型安全。
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
}
发布于 2015-05-19 03:30:47
协方差
当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
的实例。
发布于 2015-05-19 03:18:51
假设您创建了一个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>
,因为它可能产生一个非加速请求。但反向转换是可行的。
Interface IRequestProducer<T> where T: Request {
T GetNextRequest();
}
发布于 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
部分,它将解释in
和out
与Type 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时它才是安全类型:
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中的输入参数传递时才是类型安全的:
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`
https://stackoverflow.com/questions/30316008
复制相似问题