仍然对协变和逆变以及输入/输出感到困惑

2023-12-29

好的,我在 stackoverflow 上读了一些关于这个主题的内容,观看了this http://msdn.microsoft.com/en-us/vcsharp/ee672319.aspx & this http://channel9.msdn.com/posts/bruceky/Whirlwind-13-Whats-new-in-C-4-Covariance--Contravariance/,但对协方差/反方差仍然有点困惑。

from here https://stackoverflow.com/questions/1078423/c-is-variance-covariance-contravariance-another-word-for-polymorphism/1078469#1078469

协方差允许“更大”(更少 特定)类型被替换为 仅原始类型的 API 用于“输出”位置(例如 返回值)。逆变允许 “更小”(更具体)的类型 替换为 API,其中 原始类型仅用于 “输入”位置。

我知道这与类型安全有关。

有关in/out事物。我可以说我用吗in当我需要写信给它时,以及out当其只读时。和in表示逆变,out协方差。但从上面的解释来看...

and here https://stackoverflow.com/questions/1078423/c-is-variance-covariance-contravariance-another-word-for-polymorphism

例如,一个List<Banana>不可能 被视为List<Fruit>因为list.Add(new Apple())有效期为 列出但不用于List<Banana>.

所以不应该是这样,如果我要使用in/ 我要写入对象,它必须更大更通用。

我知道这个问题已经被问过,但仍然很困惑。


我不得不认真思考如何很好地解释这一点。解释似乎和理解它一样困难。

假设您有一个基类 Fruit。你有两个子类 Apple 和 Banana。

     Fruit
      / \
Banana   Apple

您创建两个对象:

Apple a = new Apple();
Banana b = new Banana();

对于这两个对象,您可以将它们类型转换为 Fruit 对象。

Fruit f = (Fruit)a;
Fruit g = (Fruit)b;

您可以将派生类视为它们的基类。

但是,您不能将基类视为派生类

a = (Apple)f; //This is incorrect

让我们将其应用到列表示例中。

假设您创建了两个列表:

List<Fruit> fruitList = new List<Fruit>();
List<Banana> bananaList = new List<Banana>();

你可以做这样的事情......

fruitList.Add(new Apple());

and

fruitList.Add(new Banana());

因为当您将它们添加到列表中时,它本质上是对它们进行类型转换。你可以这样想...

fruitList.Add((Fruit)new Apple());
fruitList.Add((Fruit)new Banana());

然而,将相同的逻辑应用于相反的情况会引发一些危险信号。

bananaList.Add(new Fruit());

是相同的

bannanaList.Add((Banana)new Fruit());

因为不能像派生类一样对待基类,所以会产生错误。

以防万一您的问题是为什么这会导致错误,我也会对此进行解释。

这是水果类

public class Fruit
{
    public Fruit()
    {
        a = 0;
    }
    public int A { get { return a; } set { a = value } }
    private int a;
}

这是香蕉类

public class Banana: Fruit
{
   public Banana(): Fruit() // This calls the Fruit constructor
   {
       // By calling ^^^ Fruit() the inherited variable a is also = 0; 
       b = 0;
   }
   public int B { get { return b; } set { b = value; } }
   private int b;
}

想象一下你再次创建了两个对象

Fruit f = new Fruit();
Banana ba = new Banana();

请记住,香蕉有两个变量“a”和“b”,而水果只有一个“a”。 所以当你这样做时...

f = (Fruit)b;
f.A = 5;

您创建了一个完整的 Fruit 对象。 但如果你要这样做...

ba = (Banana)f;
ba.A = 5;
ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist?

问题是您没有创建完整的 Banana 类。并非所有数据成员都已声明/初始化。

现在我洗完澡回来,给自己买了点零食,事情变得有点复杂。

事后看来,当我讨论复杂的事情时,我应该放弃这个比喻

让我们创建两个新类:

public class Base
public class Derived : Base

他们可以做任何你喜欢做的事

现在让我们定义两个函数

public Base DoSomething(int variable)
{
    return (Base)DoSomethingElse(variable);
}  
public Derived DoSomethingElse(int variable)
{
    // Do stuff 
}

这有点像“out”的工作原理,您应该始终能够像使用基类一样使用派生类,让我们将其应用于接口

interface MyInterface<T>
{
    T MyFunction(int variable);
}

out/in 之间的主要区别在于泛型用作返回类型或方法参数时,这是前一种情况。

让我们定义一个实现该接口的类:

public class Thing<T>: MyInterface<T> { }

然后我们创建两个对象:

MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;

如果你这样做:

base = derived;

您会收到类似“无法隐式转换...”的错误

您有两种选择,1)显式转换它们,或者 2)告诉编译器隐式转换它们。

base = (MyInterface<Base>)derived; // #1

or

interface MyInterface<out T>  // #2
{
    T MyFunction(int variable);
}

如果您的界面如下所示,则第二种情况会出现:

interface MyInterface<T>
{
    int MyFunction(T variable); // T is now a parameter
}

再次将其与两个函数联系起来

public int DoSomething(Base variable)
{
    // Do stuff
}  
public int DoSomethingElse(Derived variable)
{
    return DoSomething((Base)variable);
}

希望您看到情况如何逆转,但本质上是相同类型的转换。

再次使用相同的类

public class Base
public class Derived : Base
public class Thing<T>: MyInterface<T> { }

和相同的物体

MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;

如果你尝试将它们设置为相等

base = derived;

你的编译器会再次对你大喊大叫,你有和以前一样的选择

base = (MyInterface<Base>)derived;

or

interface MyInterface<in T> //changed
{
    int MyFunction(T variable); // T is still a parameter
}

基本上,当泛型仅用作接口方法的返回类型时,就可以使用了。当它将被用作方法参数时使用。使用委托时也适用相同的规则。

有一些奇怪的例外,但我不会在这里担心它们。

提前对任何粗心错误表示歉意=)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

仍然对协变和逆变以及输入/输出感到困惑 的相关文章

  • 如何在 Caliburn.Micro 中使用 Conductor 的依赖注入

    我有时用Caliburn Micro http caliburnmicro com创建应用程序 使用最简单的 BootStrapper 我可以像这样使用 IoC 容器 SimpleContainer private SimpleContai
  • 线程独占数据:如何存储和访问?

    NET 中是否有可能将对象实例绑定到线程的当前执行上下文 这样在代码的任何部分我都可以做类似的事情CurrentThread MyObjectData DoOperation 并确保我访问特定于线程的数据 谢谢 你可以看一下线程静态属性 h
  • 为什么使用数组索引循环数组比指针访问慢?

    我正在读Kochan的书 Programming in C 在第 14 页的 指针和数组 部分中 264 他说 一般来说 索引数组的过程比执行索引过程花费更多的时间 访问指针内容的过程 其实这也是主要原因之一 为什么使用指针来访问数组的元素
  • C++:字符串流有什么好处?

    谁能告诉我一些在 C 中使用字符串流的实际例子 即使用流插入和流提取运算符输入和输出到字符串流 您可以使用字符串流来转换任何实现operator lt lt 到一个字符串 include
  • 提取单花括号内的值

    我想要一个收藏 value 一个字符串使用正则表达式 例如 lorem ipsum field1 lorem ipsum field2 lorem ipsum field1 lorem ipsum field2 field3 我会得到 fi
  • 带有嵌入 Flash 视频的 PDF 示例?

    有谁知道我在哪里可以查看嵌入 Flash 视频的 PDF 示例 我知道问这个问题很愚蠢 因为你会认为任何面向技术的用户都应该能够使用谷歌找到一个 但我真的找不到 我的另一个问题是 使用 C 中的 API 将 Flash 视频嵌入 PDF 文
  • UI 线程正在阻塞调用 COM 对象的后台线程

    我正在开发一个通过第三方 COM 库与外部设备通信的应用程序 我试图让与设备的所有通信都通过后台线程 以防止通信问题搞砸我的应用程序 并消除在 UI 线程中进行通信所引入的一些其他复杂性 问题是 每当发生导致主 UI 线程阻塞的情况 即调用
  • 如何在Unity Inspector中创建多维数组?

    如何在 Unity Inspector 中创建枚举多维数组并使其可序列化 以便我可以从不同的脚本调用它 public enum colors red blue green yellow cyan white purple public in
  • 我如何知道向量的实际最大大小? (不使用 std::vector::max_size)

    在在线课程中 我正在学习向量 在其中一个例子中 他们解释说 std vector max size 应该给我向量可以达到的最大大小 我决定测试一下 include
  • _MM_TRANSPOSE4_PS 在 GCC 中导致编译器错误?

    我第一次在 GCC 而不是 MSVC 中编译我的数学库 并经历了所有的小错误 我遇到了一个根本没有意义的错误 Line 284 error lvalue required as left operand of assignment 284号
  • Cookie 在 ASP.net 中失去价值

    我有以下设置 cookie 的代码 string locale DropDownList this LoginUser FindControl locale SelectedValue HttpCookie cookie new HttpC
  • 使用左连接获得不适当的输出

    我正在尝试获取变体列表 并且对于每个变体都获取所有subvariants list无论子变体属于何处 特别的Test say 100 这是示例数据 Id TestId SourceSubVariantId TargetSubVariantI
  • 如何将输出重定向到 boost 日志?

    我有一个使用boost log的C 程序 我加载了用户提供的动态链接库 我想将 stderr 重定向到 boost 日志 以便用户的库随时执行以下操作 std cerr lt lt Some stuff 它产生相同的结果 BOOST LOG
  • Rx 在不同的线程上生产和消费

    我试图通过此处的示例代码来简化我的问题 我有一个生产者线程不断地输入数据 并且我尝试在批次之间添加时间延迟来对其进行批处理 以便 UI 有时间渲染它 但结果并不如预期 生产者和消费者似乎在同一个线程上 我不希望批处理缓冲区在正在生成的线程上
  • 如何解决 boost::multi precision::cpp_dec_float 除法错误

    除以boost multiprecision cpp dec float有某种舍入误差 如下 include
  • C中使用JNI从对象获取对象

    public class Student private People people private Result result private int amount 这是 Java 中类的示例 在C中 我试图获取 学生 中的 人 但失败了
  • 如何在realm-dotnet中存储System.Collections.Generic.Dictionary

    我正在尝试将 Realm NET 集成到我的 uwp 项目中 我想知道是否有任何方法可以在 Realm dotnet 库中存储 System Collections Generic Dictionary 我试过这个 public class
  • 使用 DataGridViewCheckboxCell 真正禁用 DataGridView 中的复选框

    有谁知道如何使用 DataGridViewCheckboxCell 禁用 DataGridView 中的复选框 我可以将其设置为只读 并设置背景颜色 但我无法让复选框本身显示为禁用状态 有什么想法吗 Guess 你必须自己画 http so
  • 如何从 C# 中的 Web Api 方法正确获取字节数组?

    我有以下控制器方法 HttpPost Route SomeRoute public byte MyMethod FromBody string ID byte mybytearray db getmybytearray ID working
  • Selenium - 模式对话框存在 - 如何接受信息?

    我有以下问题 在页面上提交一些日期后 我有一个如图所示的模式对话框 我想单击 ENTER 来浏览该模式 但它不起作用 我有以下代码 driver FindElement By CssSelector input submit Click A

随机推荐