C#协方差混淆 - c#

以下是有关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());

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

无法从ArrayList <String>转换为List <Comparable> - java

当我写下面的代码时,编译器说 无法从ArrayList<String>转换为List<Comparable>private List<Comparable> get(){ return new ArrayList<String>(); } 但是当我用通配符编写返回类型时,代码会编译。private List&l…

为什么要使用Func <string>而不是string? - c#

为什么要使用Func<string>而不是string?我的问题特别是关于this回购。有问题的行是22: private static Func<string> getToken = () => Environment.GetEnvironmentVariable("GitHubToken", Enviro…

将对象转换为List <object> - c#

我看过类似的问题,但没有什么合适的。我有一个碰巧包含列表的对象。我想把它变成我可以列举的东西。例如:object listObject; // contains a List<Something> List<object> list; list = listObject as List<object>; // list c…

将谓词<T>转换为Func <T,bool> - c#

我有一个包含成员Predicate的类,希望在Linq表达式中使用该类:using System.Linq; class MyClass { public bool DoAllHaveSomeProperty() { return m_instrumentList.All(m_filterExpression); } private IEnumerable&…

合并List <T>和List <Optional <T >> - java

鉴于: List<Integer> integers = new ArrayList<>(Arrays.asList( 10, 12 )); List<Optional<Integer>> optionalIntegers = Arrays.asList( Optional.of(5), Optional.em…