C# 协方差混淆
C# covariance confusion
以下是关于 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>
在包含 IExtract<string>
的继承层次结构中是 NOT
,除了 C# 使 IExtract<string>
可分配给 IExtract<object
>。但是 IExtract<string>
只是 NOT 有一个名为 Extract()
的方法,它继承自 IExtract<object>
,不像 normal[=39= 】 传承。目前对我来说似乎没有多大意义。
如果说 IExtract<string>
的 OWN 巧合(或设计)类似地命名为 Extract()
方法隐藏 IExtract<object
是否明智> 的 Extract()
方法?这是一种黑客攻击? (选词错误!)
谢谢
你肯定对协方差的工作原理有一些严重的误解,但我不是 100% 清楚它是什么。我先说什么是接口,然后我们逐行分析你的问题,指出所有的误解。
将接口视为 "slots" 的集合,其中每个插槽都有一个 合同 ,并且 包含 一个方法履行该合同。例如,如果我们有:
interface IFoo { Mammal X(Mammal y); }
然后 IFoo
有一个插槽,该插槽必须包含一个接受哺乳动物和 returns 哺乳动物的方法。
当我们将引用隐式或显式转换为接口类型时,我们不会以任何方式更改引用。相反,我们 验证 所引用的类型 已经 具有该接口的有效插槽 table。所以如果我们有:
class C : IFoo {
public Mammal X(Mammal y)
{
Console.WriteLine(y.HairColor);
return new Giraffe();
}
}
以后
C c = new C();
IFoo f = c;
认为 C 有一点 table 表示 "if a C is converted to IFoo, C.X goes in the IFoo.X slot."
当我们将c 转换为f 时,c 和f 具有完全相同的内容。它们是相同的引用。我们刚刚验证 c 的类型有一个与 IFoo 兼容的插槽 table。
现在让我们检查一下您的 post。
Invoking IExtract<object>.Extract()
invokes IExtract<string>.Extract()
, as evidenced by the output.
让我们把它弄清楚。
我们有 sampleClassOfString
实现了 IExtract<string>
。它的类型有一个 "slot table" 表示 "my Extract
goes in the slot for IExtract<string>.Extract
".
现在,当 sampleClassOfString
转换为 IExtract<object>
时,我们必须再次进行检查。 sampleClassOfString
的类型是否包含接口插槽 table,即 suitable for IExtract<object>
?是的,确实如此:我们可以将现有的 table 用于 IExtract<string>
用于此目的。
为什么我们可以使用它,即使它们是两种不同的类型?因为所有合同仍然得到满足。
IExtract<object>.Extract
有一个契约:它是一个不带任何东西的方法和returnsobject
。好吧,IExtract<string>.Extract
插槽中的方法符合该约定;它不需要任何东西,它 returns 一个字符串,它是一个对象。
既然满足了所有的契约,我们就可以使用我们已经得到的IExtract<string>
插槽table。赋值成功,所有调用都会通过IExtract<string>
槽table.
IExtract<object>
is NOT in the inheritance hierarchy containing IExtract<string>
正确。
except the fact that C# made IExtract<string>
assignable to IExtract<object>
.
不要混淆这两者;它们不一样。 继承是属性,即基类型的成员也是派生类型的成员。 赋值兼容性是属性一种类型的实例可以赋给另一种类型的变量。这些在逻辑上是非常不同的!
是的,有联系,因为派生意味着赋值兼容性和继承;如果 D 是基类型 B 的派生类型,则 D 的实例可分配给类型 B 的变量, 和 B 的所有 heritable 成员都是 D 的成员。
但是不要混淆这两者;仅仅因为它们相关并不意味着它们相同。实际上有些语言是不同的;也就是说,有些语言的继承与赋值兼容性是正交的。 C# 不是其中之一,您已经习惯了继承和赋值兼容性如此紧密相关的世界,您从未学会将它们视为独立的。开始将它们视为不同的事物,因为它们是。
协变是将赋值兼容性关系扩展到不在继承层次结构中的类型。这就是协变的意思;分配兼容性关系是 协变 如果 关系在到泛型 的映射中被保留。 "An apple may be used where a fruit is needed; therefore a sequence of apples may be used where a sequence of fruits is needed" 是 协方差 。分配兼容性关系 在到序列 .
的映射中保留
But IExtract<string>
simply does NOT have a method named Extract()
that it inherits from IExtract<object>
没错。在IExtract<string>
和IExtract<object>
之间没有任何继承。但是,它们之间存在兼容性关系,因为任何方法 Extract
满足IExtract<string>.Extract
的约定是也是一个符合IExtract<object>.Extract
约定的方法。因此,前者的插槽table可能会在需要后者的情况下使用。
Would it be sensible to say that IExtract<string>
's OWN coincidentally (or by design) similarly named Extract()
method hides IExtract<object>
's Extract() method?
绝对不是。没有任何隐藏。 "Hiding" 当派生类型具有与基类型的继承成员同名的成员时发生,并且新成员隐藏旧成员以便在编译时查找名称。隐藏只是一个编译时名称查找概念;它与接口在运行时的工作方式无关。
And that it is a kind of a hack?
绝对不会。
我试图不觉得这个建议令人反感,而且大部分都成功了。 :-)
此功能由专家精心设计;它是合理的(模扩展到 C# 中现有的不合理性,例如不安全的数组协变),并且它是在非常谨慎和审查的情况下实现的。 "hackish" 完全没有。
so exactly what happens when I invoke IExtract<object>.Extract()
?
逻辑上是这样的:
当您将 class 引用转换为 IExtract<object>
时,我们会验证引用中是否存在与 IExtract<object>
兼容的插槽 table。
当您调用 Extract
时,我们会在我们已确定为与 IExtract<object>
兼容的插槽 table 中查找 Extract
插槽的内容。由于那是 与对象已经具有的 IExtract<string>
相同的插槽 table,同样的事情发生了:class 的 Extract
方法在那个插槽中,因此它被调用。
实际上,情况比那复杂一点;调用逻辑中有一堆工具可以确保在常见情况下的良好性能。但从逻辑上讲,你应该把它看作是在table中找到一个方法,然后调用那个方法。
Delegates can also be marked as covariant and contravariant. How does that work?
从逻辑上讲,您可以将委托视为具有称为 "Invoke" 的单个方法的接口,它从那里开始。在实践中,由于委托组合等因素,机制当然会有所不同,但现在您也许可以了解它们的工作原理。
Where can I learn more?
这有点喷火:
https://whosebug.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" 作为逆变和协变的关键字之前编写的。
更多文章,按"newest first"时间顺序,可以在这里找到:
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# 协变的代码片段。我对如何应用协方差有一些了解,但有一些详细的技术资料我很难掌握。
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>
在包含 IExtract<string>
的继承层次结构中是 NOT
,除了 C# 使 IExtract<string>
可分配给 IExtract<object
>。但是 IExtract<string>
只是 NOT 有一个名为 Extract()
的方法,它继承自 IExtract<object>
,不像 normal[=39= 】 传承。目前对我来说似乎没有多大意义。
如果说 IExtract<string>
的 OWN 巧合(或设计)类似地命名为 Extract()
方法隐藏 IExtract<object
是否明智> 的 Extract()
方法?这是一种黑客攻击? (选词错误!)
谢谢
你肯定对协方差的工作原理有一些严重的误解,但我不是 100% 清楚它是什么。我先说什么是接口,然后我们逐行分析你的问题,指出所有的误解。
将接口视为 "slots" 的集合,其中每个插槽都有一个 合同 ,并且 包含 一个方法履行该合同。例如,如果我们有:
interface IFoo { Mammal X(Mammal y); }
然后 IFoo
有一个插槽,该插槽必须包含一个接受哺乳动物和 returns 哺乳动物的方法。
当我们将引用隐式或显式转换为接口类型时,我们不会以任何方式更改引用。相反,我们 验证 所引用的类型 已经 具有该接口的有效插槽 table。所以如果我们有:
class C : IFoo {
public Mammal X(Mammal y)
{
Console.WriteLine(y.HairColor);
return new Giraffe();
}
}
以后
C c = new C();
IFoo f = c;
认为 C 有一点 table 表示 "if a C is converted to IFoo, C.X goes in the IFoo.X slot."
当我们将c 转换为f 时,c 和f 具有完全相同的内容。它们是相同的引用。我们刚刚验证 c 的类型有一个与 IFoo 兼容的插槽 table。
现在让我们检查一下您的 post。
Invoking
IExtract<object>.Extract()
invokesIExtract<string>.Extract()
, as evidenced by the output.
让我们把它弄清楚。
我们有 sampleClassOfString
实现了 IExtract<string>
。它的类型有一个 "slot table" 表示 "my Extract
goes in the slot for IExtract<string>.Extract
".
现在,当 sampleClassOfString
转换为 IExtract<object>
时,我们必须再次进行检查。 sampleClassOfString
的类型是否包含接口插槽 table,即 suitable for IExtract<object>
?是的,确实如此:我们可以将现有的 table 用于 IExtract<string>
用于此目的。
为什么我们可以使用它,即使它们是两种不同的类型?因为所有合同仍然得到满足。
IExtract<object>.Extract
有一个契约:它是一个不带任何东西的方法和returnsobject
。好吧,IExtract<string>.Extract
插槽中的方法符合该约定;它不需要任何东西,它 returns 一个字符串,它是一个对象。
既然满足了所有的契约,我们就可以使用我们已经得到的IExtract<string>
插槽table。赋值成功,所有调用都会通过IExtract<string>
槽table.
IExtract<object>
is NOT in the inheritance hierarchy containingIExtract<string>
正确。
except the fact that C# made
IExtract<string>
assignable toIExtract<object>
.
不要混淆这两者;它们不一样。 继承是属性,即基类型的成员也是派生类型的成员。 赋值兼容性是属性一种类型的实例可以赋给另一种类型的变量。这些在逻辑上是非常不同的!
是的,有联系,因为派生意味着赋值兼容性和继承;如果 D 是基类型 B 的派生类型,则 D 的实例可分配给类型 B 的变量, 和 B 的所有 heritable 成员都是 D 的成员。
但是不要混淆这两者;仅仅因为它们相关并不意味着它们相同。实际上有些语言是不同的;也就是说,有些语言的继承与赋值兼容性是正交的。 C# 不是其中之一,您已经习惯了继承和赋值兼容性如此紧密相关的世界,您从未学会将它们视为独立的。开始将它们视为不同的事物,因为它们是。
协变是将赋值兼容性关系扩展到不在继承层次结构中的类型。这就是协变的意思;分配兼容性关系是 协变 如果 关系在到泛型 的映射中被保留。 "An apple may be used where a fruit is needed; therefore a sequence of apples may be used where a sequence of fruits is needed" 是 协方差 。分配兼容性关系 在到序列 .
的映射中保留But
IExtract<string>
simply does NOT have a method namedExtract()
that it inherits fromIExtract<object>
没错。在IExtract<string>
和IExtract<object>
之间没有任何继承。但是,它们之间存在兼容性关系,因为任何方法 Extract
满足IExtract<string>.Extract
的约定是也是一个符合IExtract<object>.Extract
约定的方法。因此,前者的插槽table可能会在需要后者的情况下使用。
Would it be sensible to say that
IExtract<string>
's OWN coincidentally (or by design) similarly namedExtract()
method hidesIExtract<object>
's Extract() method?
绝对不是。没有任何隐藏。 "Hiding" 当派生类型具有与基类型的继承成员同名的成员时发生,并且新成员隐藏旧成员以便在编译时查找名称。隐藏只是一个编译时名称查找概念;它与接口在运行时的工作方式无关。
And that it is a kind of a hack?
绝对不会。
我试图不觉得这个建议令人反感,而且大部分都成功了。 :-)
此功能由专家精心设计;它是合理的(模扩展到 C# 中现有的不合理性,例如不安全的数组协变),并且它是在非常谨慎和审查的情况下实现的。 "hackish" 完全没有。
so exactly what happens when I invoke
IExtract<object>.Extract()
?
逻辑上是这样的:
当您将 class 引用转换为 IExtract<object>
时,我们会验证引用中是否存在与 IExtract<object>
兼容的插槽 table。
当您调用 Extract
时,我们会在我们已确定为与 IExtract<object>
兼容的插槽 table 中查找 Extract
插槽的内容。由于那是 与对象已经具有的 IExtract<string>
相同的插槽 table,同样的事情发生了:class 的 Extract
方法在那个插槽中,因此它被调用。
实际上,情况比那复杂一点;调用逻辑中有一堆工具可以确保在常见情况下的良好性能。但从逻辑上讲,你应该把它看作是在table中找到一个方法,然后调用那个方法。
Delegates can also be marked as covariant and contravariant. How does that work?
从逻辑上讲,您可以将委托视为具有称为 "Invoke" 的单个方法的接口,它从那里开始。在实践中,由于委托组合等因素,机制当然会有所不同,但现在您也许可以了解它们的工作原理。
Where can I learn more?
这有点喷火:
https://whosebug.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" 作为逆变和协变的关键字之前编写的。
更多文章,按"newest first"时间顺序,可以在这里找到:
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());
?想一想,看看你能不能弄清楚会发生什么。