首先,请记住.NET String是IConvertible和ICloneable.
现在,考虑以下非常简单的代码:
//contravariance "in"
interface ICanEat<in T> where T : class
{
void Eat(T food);
}
class HungryWolf : ICanEat<ICloneable>, ICanEat<IConvertible>
{
public void Eat(IConvertible convertibleFood)
{
Console.WriteLine("This wolf ate your CONVERTIBLE object!");
}
public void Eat(ICloneable cloneableFood)
{
Console.WriteLine("This wolf ate your CLONEABLE object!");
}
}
Run Code Online (Sandbox Code Playgroud)
然后尝试以下(在某些方法中):
ICanEat<string> wolf = new HungryWolf();
wolf.Eat("sheep");
Run Code Online (Sandbox Code Playgroud)
当编译它时,没有编译器错误或警告.运行它时,看起来调用的方法取决于我的class声明中的接口列表的顺序HungryWolf.(尝试在逗号(,)分隔列表中交换两个接口.)
问题很简单:这不应该给出编译时警告(或者在运行时抛出)吗?
我可能不是第一个提出像这样的代码的人.我使用了界面的逆变,但你可以用界面的covarainace做一个完全类似的例子.事实上,Lippert先生很久以前就做过这样的事情.在他博客的评论中,几乎每个人都认为这应该是一个错误.然而他们默默地允许这样做. …
有人能为我提供简单的C#例子,包括协方差,逆变,不变性和反不变性(如果存在这种情况).
到目前为止我见过的所有样品都只是投入一些物体System.Object.
我试图弄清楚这些词的确切含义,Covariance以及Contravariance在线的几篇文章和关于StackOverflow的问题,从我能理解的,它只是多态的另一个词.
我对上述陈述是否正确?或者我弄错了?
在C#中可以将方差注释添加到类型参数,约束为值类型:
interface IFoo<in T> where T : struct
{
void Boo(T x);
}
Run Code Online (Sandbox Code Playgroud)
如果在这种情况下方差注释完全没有意义,为什么编译器允许这样做?
首先,我对SO和有关协变和逆变和一个大感谢博客出去看了很多解释埃里克利珀用于生产在这样一个伟大的系列赛协变和逆变.
但是我有一个更具体的问题,我试图让我的头脑稍微偏执一点.
据我所知,根据埃里克的解释,协方差和反方差都是描述转换的形容词.协变变换是保留类型顺序的变换,逆变变换是逆转变换的变换.
我理解协方差,我认为大多数开发人员直观地理解.
//covariant operation
Animal someAnimal = new Giraffe();
//assume returns Mammal, also covariant operation
someAnimal = Mammal.GetSomeMammal();
Run Code Online (Sandbox Code Playgroud)
这里的返回操作是协变的,因为我们保留了动物仍然比哺乳动物或长颈鹿大的大小.在这方面,大多数返回操作都是协变的,逆变操作是没有意义的.
//if return operations were contravariant
//the following would be illegal
//as Mammal would need to be stored in something
//equal to or less derived than Mammal
//which would mean that Animal is now less than or equal than Mammal
//therefore reversing the relationship
Animal someAnimal = Mammal.GetSomeMammal();
Run Code Online (Sandbox Code Playgroud)
这段代码当然对大多数开发人员没有意义.
我的困惑在于Contravariant参数参数.如果你有一个方法,如
bool Compare(Mammal mammal1, Mammal …Run Code Online (Sandbox Code Playgroud) ReSharper建议我通过改变这个来改变类型参数T contravariant:
interface IBusinessValidator<T> where T: IEntity
{
void Validate(T entity);
}
Run Code Online (Sandbox Code Playgroud)
进入:
interface IBusinessValidator<in T> where T: IEntity
{
void Validate(T entity);
}
Run Code Online (Sandbox Code Playgroud)
那么<T>和之间有什么不同<in T>?这里逆变的目的是什么?
让说我有IEntity,Entity,User和Account实体.假设双方User和Account有Name需要验证属性.
如何在此示例中应用逆变的用法?
这有什么问题?
interface IRepository<out T> where T : IBusinessEntity
{
IQueryable<T> GetAll();
void Save(T t);
void Delete(T t);
}
Run Code Online (Sandbox Code Playgroud)
它说:
方差无效:类型参数"T"必须在"MyNamespace.IRepository.Delete(T)"上具有矛盾的有效性.'T'是协变的.
我试图在trait中使用covariant类型参数来构造一个case类,如下所示:
trait MyTrait[+T] {
private case class MyClass(c: T)
}
Run Code Online (Sandbox Code Playgroud)
编译说:
error: covariant type T occurs in contravariant position in type T of value c
Run Code Online (Sandbox Code Playgroud)
然后我尝试了以下但它也没有用:
trait MyTrait[+T] {
private case class MyClass[U <: T](c: U)
}
Run Code Online (Sandbox Code Playgroud)
这次的错误是:
error: covariant type T occurs in contravariant position in type >: Nothing <: T of type U
Run Code Online (Sandbox Code Playgroud)
有人可以解释为什么T在这里处于协变位置并建议解决这个问题吗?谢谢!
我相信人们可以将协方差(至少对象来说)定义为"使用较窄(子)类型的值代替某种较宽(超级)类型的值的能力",并且这种相反性恰好相反.这个.
显然,Scala函数是函数[-A1,...,+ B]的实例,用于逆变参数类型A1等,以及协变返回类型B.虽然这对于函数的子类型很方便,但上述定义不应该意味着我可以传递任何超类型作为参数?
请告诉我哪里弄错了.
我正在考虑以下示例来说明为什么逆变是有用的.
让我们考虑一个GUI框架Widgets,Events和Event Listeners.
abstract class Event;
class KeyEvent extends Event
class MouseEvent extends Event
trait EventListener[-E] { def listen(e:E) }Run Code Online (Sandbox Code Playgroud)
我们Widgets定义以下方法:
def addKeyEventListener(listener:EventListener[KeyEvent])
def addMouseEventListener(listener:EventListener[MouseEvent])
Run Code Online (Sandbox Code Playgroud)
这些方法只接受"特定"事件监听器,这很好.但是我想定义"kitchen-sink"监听器,它们监听所有事件,并将这些监听器传递给上面的"添加监听器"方法.
例如,我想定义LogEventListener记录所有传入的事件
class LogEventListener extends EventListener[Event] {
def listen(e:Event) { log(event) }
}Run Code Online (Sandbox Code Playgroud)
由于特征EventListener是逆变的,Event我们可以传递LogEventListener给所有那些"添加监听器"方法而不会失去其类型安全性.
是否有意义 ?
contravariance ×10
c# ×7
covariance ×7
.net ×3
scala ×3
generics ×2
ambiguity ×1
c#-4.0 ×1
case-class ×1
function ×1
invariants ×1
polymorphism ×1
resharper ×1
variance ×1