有一个由Eric编写的博客帖子,他在这里描述了如何定义方差。在一般意义上,当赋值兼容性的方向保持不变时,就实现了协方差。当分配兼容性的方向被逆转时,就实现了反方差。
现在,让我们以一个C#通用接口为例。请考虑简单地说,C#中的以下代码:
IPoppable<Animal> animals = bears;
Animal a = animals.Pop();
public interface IPoppable<out T> { T Pop(); }
public interface IPushable<in T> { void Push (T obj); }
class Animal {}
class Bear : Animal {}
class Camel : Animal {}
public class Stack<T> : IPoppable<T>
{
int position;
T[] data = new T [100];
public void Push (T obj) => data [position++] = obj;
public T Pop() => data [--position];
}IPoppable接口使用<out T>表示只能在输出位置使用T。这在示例代码的上下文中是有意义的。由于Stack<T>类继承自IPoppable<out T>,因此Pop()只应从堆栈返回数据。它不应将数据作为输入添加。
因此,上面的代码说明了如何使用out参数实现与一般接口的协方差。out参数将Pop()方法限制为仅在输出位置使用T。
可以使用in参数实现反向操作,这将允许我们通过将T限制在泛型接口中的输入位置来实现逆方差。上面的IPushable接口显示了这一点。
考虑到所有这些,将类型限制在输出位置如何与Eric Lippert的定义有关,即在保持分配兼容性时定义协方差?类似地,将类型参数限制在输入位置与反方差的定义有什么关系?
发布于 2022-12-05 18:04:43
你已经有两个好答案了,让我提供一个更简洁的第三个答案。让我们从重新陈述这个问题开始:
将类型限制在输出(或输入)位置如何实现分配兼容性关系的保留(或逆转)?
关键的洞察力在于思考一个简单的函数。让我们假设每一个哺乳动物都有一个朋友,他也是哺乳动物。
static Dictionary<Mammal, Mammal> friends = whatever;
static Mammal F (Mammal input) => friends[input];问题是:我们如何在法律上改变(变化!)F的类型签名而不更改主体或friends的声明?(忽略F的任何现有调用者;我们只关心F的主体是否合法。)
static Mammal F (Animal input) => friends[input];
// WRONG, we could pass a Turtle
static Mammal F (Giraffe input) => friends[input];
// RIGHT, this is fine
static Animal F (Mammal input) => friends[input];
// RIGHT, this is fine
static Giraffe F (Mammal input) => friends[input];
// WRONG, it might return a Tiger结论是,像Func<Mammal, Mammal>这样的F总是可以在需要Func<Giraffe, Animal>的上下文中使用,但不能在需要Func<Animal, Giraffe>的上下文中使用。
这就是为什么协方差和输出以及反向方差和输入之间有联系的原因。它是从关于功能体如何工作的基本事实中得出的。使输出类型声明更大或输入类型声明更小,保持主体的正确性。
https://softwareengineering.stackexchange.com/questions/442469
复制相似问题