它具有以下interface
:
public interface ICovariant<out T>
{
public void InputFunc(Func<T> func);
}
我得到以下编译时错误:
无效方差:协变类型参数'T'用于逆变位置。参数必须输入安全
Func<T>
的位置是输入。Func<T>
是Func<out T>
,所以它是输出。
好吧,我错了。获取与位置相关的信息。
但是,当我添加以下方法时,会出现一个错误。
public interface ICovariant<out T>
{
public void InputFunc(Func<T> func);
public Action<T> ReturnAction();
}
无效方差:协变类型参数'T'用于逆变位置。方法返回类型必须是输出安全
Action<T>
的位置是输出。Action<T>
是Action<in T>
,所以它是输入。
什么?与以前不同的是,这次地点无关紧要。
为什么这两个例子对彼此的看法不同?
清扫者接受的答案是好的;我想提供一种稍微不同的方式来思考为什么你提出的计划是非法的。在此之前,请允许我再次为这里错误消息的低质量道歉。我想不出更好的办法了,显然在接下来的15年里,它们没有得到太大的改善。
public interface ICovariant<out T>
{
public Action<T> ReturnAction(); // Why is this illegal?
}
为什么这是违法的?试着去实现它,然后看看哪里出了问题!
class Animal {...}
class Mammal : Animal {...}
// ... and so on ...
class Danger: ICovariant<Mammal>
{
public Action<Mammal> ReturnAction()
{
return (Mammal m) => { m.ProduceMilk(); };
}
}
Danger
满足ICovariant<Mammal>
的要求——它返回一个动作,这个动作带走一只哺乳动物,它做一件事——让哺乳动物产奶:
ICovariant<Mammal> danger = new Danger();
Action<Mammal> action = danger.ReturnAction();
action(new Goat()); // Goat milk, mmmmm
但是ICovariant<Mammal>
可以转换为ICovariant<Animal>
,因为它是协变的,所以我们也可以写:
ICovariant<Animal> danger = new Danger();
// ICov<Animal> produces an Action<Animal>
Action<Animal> action = danger.ReturnAction();
action(new Chicken());
现在我们挤了一只鸡的奶。这种尝试不会有什么好处。
上面的线条完全合法;它们不可能是错误。为了防止这种情况发生,编译器必须使接口本身非法。
与以前不同,这次位置无关紧要。
位置确实很重要!这编译得很完美:
public interface ICovariant<out T>
{
public void AcceptAction(Action<T> action);
}
Action
中的逆变类型参数的作用是,它使其对应的类型参数具有与您放置它的位置相反的位置-如果它最初位于输入位置,则它现在位于输出位置,如果它最初处于输出位置,则现在处于输入位置。
"Contra-";是指";相反";,所以这是一个很容易记住的行为:D尽管这个特定的行为可能不是名字"的地方;反向方差";来自。Eric Lippert在这里的回答解释了为什么这么叫。
所以这也编译了(T
的位置被"翻转"了两次,所以它回到了输出位置(:
public Action<Action<T>> ReturnsNestedAction();
如果你只是想一想你实际上是如何调用ReturnAction
的,你应该能够直观地说服自己,事实上,ReturnAction
中的T
是在一个输入位置:
Action<T> action = someICovariant.ReturnAction();
action(someT); // I am *inputting* an instance of T here!
同样,您可以对AcceptAction
执行同样的操作,并看到T
确实处于输出位置:
someICovariant.AcceptAction(t => {
Console.WriteLine(t);
});
/*
Imagine the implementation of AcceptAction invoking its parameter at some point.
That is analogous to AcceptAction *returning* some instance of T and we print it out
The difference being that instead of "return someT;",
AcceptAction would do "action(someT);", but the fact it is producing an instance of T
doesn't change
*/
如果您对如何在技术术语中指定这一点感兴趣,可以阅读语言规范的这一部分。
还参见关于类似的";翻转位置";声明逆变类型参数时的行为。