为什么我可以将对象与这些对象的 IEnumerable 进行比较,而不是列表?

Why can I compare an object to an IEnumerable of those objects but not a List?

我刚刚意识到我编写了我认为无效的代码(关于语法),但编译器接受了。

为了简洁起见,我重新创建了一个示例。给定一个类型:

private class MyType
{
}

然后我可以将 MyType 的实例与 IEnumerable<MyType> 的实例进行比较:

var myCollection = new List<MyType> { };
var test = new MyType() == (IEnumerable<MyType>)myCollection;

我很惊讶我可以比较两个不同类型的对象。更有趣的是(至少对我来说很有趣),如果我将演员表删除为 IEnumerable 我无法比较它。为什么会这样? List 实现了 IEnumerable,所以假设我遗漏了某种相等比较器(可能是错误的),为什么 List 也不能使用它?

编译器允许您将对象与接口进行比较,但不允许您将它们与不相关的具体内容进行比较 类 因为它知道精确的类型 up-front 并且知道这两种类型不能比较的。允许与接口进行比较,因为将在 运行 时尝试从具体对象进行强制转换,这本身就是一个有效的操作。

您要用它做什么是另一回事,事实上,您的 none 比较在语义上是正确的。

无论如何,您的代码不会产生真正的比较。它会 return False 当 运行.

var test1 = new MyType() == (IEnumerable<MyType>)myCollection; // False
var test2 = new MyType() == new List<MyType>();                // Compile-time error
var test3 = new MyType() == (IComparable)5;                    // False

另一个问题是 == 的使用 - 这在另一个层面上是错误的。您也可以自由调用 Equals,并且似乎试图执行真正的语义比较。默认实现不会做任何有意义的事情,但再次 - 从编译器的角度来看,一切在语法上仍然是正确的。

这里引用规范中的相关内容(第 7.10.6 节引用类型相等运算符)。本节介绍在这种情况下使用的预定义对象相等运算符 (== (object x, object y)):

The predefined reference type equality operators require one of the following:

• Both operands are a value of a type known to be a reference-type or the literal null. Furthermore, an explicit reference conversion (§6.2.4) exists from the type of either operand to the type of the other operand.

• One operand is a value of type T where T is a type-parameter and the other operand is the literal null. Furthermore T does not have the value type constraint.

从6.2.4节我们可以发现:

The explicit reference conversions are:

• From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T.

正因为如此

var test = new MyType() == (IEnumerable<MyType>)myCollection;

有效,因为根据上述内容,存在从 MyTypeIEnumerable<MyType> 的显式引用转换。但是,如果您将 MyType 密封 - 那么将不会进行此类转换,也不会编译。如果您使用一些预定义的密封类型,例如 string(我的意思是 - 它不会编译),也会发生同样的情况。

non-sealed 类型允许显式引用转换为任何接口的原因是因为该变量的运行时类型可以是任何子 classes,并且那个子 class可能会实现该接口。 Sealed classes不能被继承,所以编译器可以确定不是这样。