gpt4 book ai didi

c# - C#协方差混淆

转载 作者:行者123 更新时间:2023-12-04 16:57:34 26 4
gpt4 key购买 nike

以下是有关C#中协方差的代码段。我对如何应用协方差有一定的了解,但是我很难掌握一些详细的技术知识。

using System;
namespace CovarianceExample
{
interface IExtract<out T> { T Extract(); }
class SampleClass<T> : IExtract<T>
{
private T data;
public SampleClass(T data) {this.data = data;} //ctor
public T Extract() // Implementing interface
{
Console.WriteLine
("The type where the executing method is declared:\n{0}",
this.GetType() );
return this.data;
}
}
class CovarianceExampleProgram
{
static void Main(string[] args)
{
SampleClass<string> sampleClassOfString = new SampleClass<string>("This is a string");
IExtract<Object> iExtract = sampleClassOfString;

// IExtract<object>.Extract() mapes to IExtract<string>.Extract()?
object obj = iExtract.Extract();
Console.WriteLine(obj);
Console.ReadKey();
}
}
}

// Output:
// The type where the executing method is declared:
// CovarianceExample.SampleClass`1[System.String]
// This is a string


如输出所示,调用 IExtract<object>.Extract()会调用 IExtract<string>.Extract()。虽然我有点期待这种行为,但我无法告诉自己为什么它会如此行为。

在包含 IExtract<object>的继承层次结构中, NOTIExtract<string>,但C#使 IExtract<string>可分配给 >的事实除外。但是 IExtract<object根本没有从 IExtract<string>继承的名为 Extract()的方法,这与常规继承不同。目前,这对我来说似乎意义不大。

IExtract<object>的OWN巧合地(或通过设计)类似地命名为 IExtract<string>的方法隐藏 >的 Extract()方法是否明智?那是一种黑客吗? (错误的单词选择!)

谢谢

最佳答案

您肯定对协方差的工作原理有一些严重的误解,但我尚不清楚它是100%的。首先让我说一下什么是接口,然后我们可以逐行讨论您的问题并指出所有误解。

将接口视为“插槽”的集合,其中每个插槽都有一个合同,并包含一个实现该合同的方法。例如,如果我们有:

interface IFoo { Mammal X(Mammal y); }


那么 IFoo有一个槽位,并且该槽位必须包含将哺乳动物带回哺乳动物的方法。

当我们将引用隐式或显式转换为接口类型时,我们不会以任何方式更改引用。而是,我们验证引用类型已经具有该接口的有效插槽表。因此,如果我们有:

class C : IFoo { 
public Mammal X(Mammal y)
{
Console.WriteLine(y.HairColor);
return new Giraffe();
}
}


然后

C c = new C();
IFoo f = c;


可以将C看作一张小表,上面写着:“如果C转换为IFoo,则C.X进入IFoo.X插槽。”

当我们将c转换为f时,c和f具有完全相同的内容。它们是相同的参考。我们刚刚验证了c是具有与IFoo兼容的插槽表的类型。

现在,让我们浏览一下您的帖子。


如输出所示,调用 IExtract<object>.Extract()会调用 IExtract<string>.Extract()


让我们整理一下。

我们有实现 sampleClassOfStringIExtract<string>。它的类型有一个“插槽表”,上面写着“我的 Extract进入 IExtract<string>.Extract的插槽”。

现在,当 sampleClassOfString转换为 IExtract<object>时,我们必须进行检查。 sampleClassOfString的类型是否包含适合 IExtract<object>的接口插槽表?是的,它可以:为此,我们可以将现有表用于 IExtract<string>

即使它们是两种不同的类型,我们为什么也可以使用它呢?因为所有合同仍然得到履行。

IExtract<object>.Extract有合同:这是什么都不做并返回 object的方法。好吧, IExtract<string>.Extract插槽中的方法符合该合同;它不需要任何内容​​,并且返回一个字符串,它是一个对象。

由于已满足所有合同,因此我们可以使用已经获得的 IExtract<string>槽位表。分配成功,所有调用将通过 IExtract<string>插槽表。


IExtract<object>不在包含 IExtract<string>的继承层次结构中


正确。


除了C#使 IExtract<string>可分配给 IExtract<object>的事实。


不要混淆这两件事。她们不一样。继承是基本类型的成员也是派生类型的成员的属性。分配兼容性是可以将一种类型的实例分配给另一种类型的变量的属性。从逻辑上说,这些是完全不同的!

是的,存在某种联系,只要派生意味着赋值兼容性和继承。如果D是基本类型B的派生类型,则D的实例可分配给类型B的变量,并且B的所有可继承成员都是D的成员。

但是不要混淆这两件事。仅仅因为它们是相关的并不意味着它们是相同的。实际上,有些语言是不同的。也就是说,有些语言的继承与分配兼容性正交。 C#并不是其中之一,并且您已经习惯了这样一个世界,在这个世界中,继承和赋值兼容性是如此紧密地联系在一起,您从未学会将它们视为独立的。开始将它们视为不同的事物,因为它们是。

协方差是关于将分配兼容性关系扩展到不在继承层次结构中的类型。这就是协方差的含义;如果分配兼容性关系是在映射到泛型的映射中保留的,则该关系是协变的。协方差是“需要水果的地方可以使用苹果;因此需要水果的地方可以使用苹果序列”。分配兼容性关系在到序列的映射中保留。


但是 IExtract<string>根本没有从 Extract()继承的名为 IExtract<object>的方法


没错 IExtract<string>IExtract<object>之间没有任何继承。但是,它们之间存在兼容性关系,因为符合 Extract约定的任何方法 IExtract<string>.Extract也是符合 IExtract<object>.Extract约定的方法。因此,前者的插槽表可以在需要后者的情况下使用。


IExtract<string>的OWN巧合地(或通过设计)类似的 Extract()方法来掩盖 IExtract<object>的Extract()方法是否明智?


绝对不。没有任何隐藏。当派生类型具有与基本类型的继承成员同名的成员,并且新成员隐藏旧成员以在编译时查找名称时,就会发生“隐藏”。隐藏只是一个编译时名称查找概念;它与接口在运行时的工作方式无关。


那是一种黑客吗?


绝对不。

我试图不建议采取攻势,并且大多是成功的。 :-)

此功能是由专家精心设计的;它是健全的(以模数形式扩展到C#中现有的不完整性,例如不安全的数组协方差),并且在实施时要格外谨慎和认真。绝对没有任何“骇客”。


那么当我调用 IExtract<object>.Extract()时会发生什么呢?


从逻辑上讲,这是发生的情况:

将类引用转换为 IExtract<object>时,我们验证引用中是否存在与 IExtract<object>兼容的插槽表。

调用 Extract时,我们在已确定与 Extract兼容的插槽表中查找 IExtract<object>插槽的内容。由于该插槽与对象已为 IExtract<string>使用的插槽表是同一插槽表,因此发生了相同的事情:类的 Extract方法位于该插槽中,因此它被调用。

实际上,情况要复杂得多。在通常情况下,调用逻辑中有一堆齿轮可以确保良好的性能。但是从逻辑上讲,您应该将其视为在表中找到一个方法,然后调用该方法。


代表也可以标记为协变和反变。这是如何运作的?


从逻辑上讲,您可以将委托视为仅具有一个称为“ Invoke”的方法的接口,然后从那里开始。实际上,由于代理人组成之类的原因,机制当然有所不同,但也许现在您可以看到它们如何工作。


在哪里可以了解更多?


这有点费劲:

https://stackoverflow.com/search?q=user%3A88656+covariance

所以我将从顶部开始:

Difference between Covariance & Contra-variance

如果要使用C#4.0中的功能历史记录,请从此处开始:

https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/

请注意,这是在我们将“ in”和“ out”确定为逆变和协变关键字之前编写的。

在这里可以找到按“最新的第一”时间顺序排列的更多文章:

https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/

还有一些在这里:

https://ericlippert.com/category/covariance-and-contravariance/



练习:现在您大致了解了它在幕后的工作原理,您认为这是怎么做的?

interface IFrobber<out T> { T Frob(); }
class Animal { }
class Zebra: Animal { }
class Tiger: Animal { }
// Please never do this:
class Weird : IFrobber<Zebra>, IFrobber<Tiger>
{
Zebra IFrobber<Zebra>.Frob() => new Zebra();
Tiger IFrobber<Tiger>.Frob() => new Tiger();
}

IFrobber<Animal> weird = new Weird();
Console.WriteLine(weird.Frob());


?考虑一下,看看是否可以解决。

关于c# - C#协方差混淆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54257014/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com