因此,您可能知道,C# 中的数组实现IList<T>
了其他接口。但不知何故,他们这样做并没有公开实现 ! 的 Count 属性IList<T>
。数组只有一个 Length 属性。
这是 C#/.NET 打破其自己的接口实现规则的明目张胆的例子,还是我遗漏了什么?
如您所知,C# 中的数组实现
IList<T>
了其他接口
嗯,是的,呃不,不是真的。这是 .NET 4 框架中 Array 类的声明:
[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable,
IStructuralComparable, IStructuralEquatable
{
// etc..
}
它实现 System.Collections.IList,而不是System.Collections.Generic.IList<>。它不能, Array 不是通用的。通用 IEnumerable<> 和 ICollection<> 接口也是如此。
但是 CLR 动态创建具体的数组类型,因此它可以在技术上创建一个实现这些接口的类型。然而事实并非如此。试试这个代码,例如:
using System;
using System.Collections.Generic;
class Program {
static void Main(string[] args) {
var goodmap = typeof(Derived).GetInterfaceMap(typeof(IEnumerable<int>));
var badmap = typeof(int[]).GetInterfaceMap(typeof(IEnumerable<int>)); // Kaboom
}
}
abstract class Base { }
class Derived : Base, IEnumerable<int> {
public IEnumerator<int> GetEnumerator() { return null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
对于具有“未找到接口”的具体数组类型,GetInterfaceMap() 调用失败。然而,对 IEnumerable<> 的强制转换没有问题。
这就像鸭子一样的嘎嘎打字。正是这种类型的类型产生了一种错觉,即每个值类型都派生自从 Object 派生的 ValueType。编译器和 CLR 都对数组类型有特殊的了解,就像它们对值类型一样。编译器看到您尝试转换为 IList<> 并说“好的,我知道该怎么做!”。并发出 castclass IL 指令。CLR 对此没有任何问题,它知道如何提供适用于底层数组对象的 IList<> 的实现。它具有其他隐藏的 System.SZArrayHelper 类的内置知识,该类是实际实现这些接口的包装器。
它不像每个人都声称的那样明确地做,你问的 Count 属性看起来像这样:
internal int get_Count<T>() {
//! Warning: "this" is an array, not an SZArrayHelper. See comments above
//! or you may introduce a security hole!
T[] _this = JitHelpers.UnsafeCast<T[]>(this);
return _this.Length;
}
是的,您当然可以称该评论为“违反规则”:) 否则它非常方便。而且隐藏得非常好,您可以在 SSCLI20(CLR 的共享源代码分发版)中查看这一点。搜索“IList”以查看类型替换发生的位置。看到它的最佳位置是 clr/src/vm/array.cpp,GetActualImplementationForArrayGenericIListMethod() 方法。
与 CLR 中允许为 WinRT(又名 Metro)编写托管代码的语言投影中发生的情况相比,CLR 中的这种替换相当温和。几乎所有核心 .NET 类型都可以在那里被替换。IList<> 映射到 IVector<> 例如,一个完全非托管的类型。COM 本身是一个替代品,不支持泛型类型。
好吧,那是看看幕后发生的事情。生活在地图尽头的龙可能是非常不舒服、陌生和不熟悉的海洋。使地球变平并为托管代码中实际情况的不同图像建模可能非常有用。将它映射到每个人最喜欢的答案是很舒服的。这对于值类型来说效果不佳(不要改变结构!)但是这个隐藏得很好。GetInterfaceMap() 方法失败是我能想到的抽象中唯一的泄漏。
根据汉斯的答案的新答案
感谢 Hans 给出的答案,我们可以看到实现比我们想象的要复杂一些。编译器和 CLR 都非常努力地给人一种数组类型实现的印象IList<T>
——但是数组变化使这变得更加棘手。与 Hans 的回答相反,数组类型(一维,无论如何都是从零开始的)确实直接实现了泛型集合,因为任何特定数组的类型都不是 System.Array
——这只是数组的基本类型。如果你问一个数组类型它支持什么接口,它包括泛型类型:
foreach (var type in typeof(int[]).GetInterfaces())
{
Console.WriteLine(type);
}
输出:
System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.IStructuralComparable
System.Collections.IStructuralEquatable
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.Generic.IEnumerable`1[System.Int32]
对于从零开始的一维数组,就语言而言,数组确实也实现IList<T>
了。C# 规范的第 12.1.2 节是这样说的。所以无论底层实现做什么,语言都必须表现得好像T[]
实现的类型IList<T>
与任何其他接口一样。从这个角度来看,接口是通过一些显式实现的成员来实现的(例如Count
)。这是在语言层面上对正在发生的事情的最佳解释。
请注意,这仅适用于一维数组(和从零开始的数组,而不是 C# 作为一种语言对非从零开始的数组有任何说明)。T[,]
没有实现IList<T>
。
从 CLR 的角度来看,正在发生一些更有趣的事情。您无法获取通用接口类型的接口映射。例如:
typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))
给出以下例外:
Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.
那么为什么会出现奇怪的现象呢?好吧,我相信这真的是由于数组协方差,这是类型系统中的一个缺陷,IMO。即使IList<T>
不是协变的(并且不能安全),数组协变也允许它工作:
string[] strings = { "a", "b", "c" };
IList<object> objects = strings;
...这使它看起来像typeof(string[])
implements IList<object>
,但实际上并非如此。
CLI 规范 (ECMA-335) 分区 1,第 8.7.1 节有:
签名类型 T 与签名类型 U 兼容当且仅当以下至少一项成立
...
T 是一个从零开始的 rank-1 数组
V[]
,并且U
是IList<W>
,并且 V 是与 W 兼容的数组元素。
(它实际上并没有提到ICollection<W>
或IEnumerable<W>
我认为这是规范中的一个错误。)
对于非变异性,CLI 规范直接与语言规范一起使用。从分区 1 的第 8.9.1 节:
此外,创建的元素类型为 T 的向量实现了接口
System.Collections.Generic.IList<U>
,其中 U := T。(第 8.7 节)
(向量是一个基数为零的一维数组。)
现在就实现细节而言,显然 CLR 正在做一些时髦的映射以保持此处的赋值兼容性:当string[]
要求 a 实现 时ICollection<object>.Count
,它无法以非常正常的方式处理它。这算作显式接口实现吗?我认为以这种方式对待它是合理的,因为除非您直接要求接口映射,否则从语言的角度来看它总是以这种方式运行。
怎么样ICollection.Count
?
到目前为止,我已经讨论了泛型接口,但还有非泛型ICollection
及其Count
属性。这次我们可以得到接口映射,实际上接口是直接实现的System.Array
。ICollection.Count
属性实现的文档Array
说明它是通过显式接口实现来实现的。
如果有人能想到这种显式接口实现与“普通”显式接口实现不同的方式,我很乐意进一步研究。
关于显式接口实现的旧答案
尽管上述情况由于数组知识而变得更加复杂,但您仍然可以通过显式接口实现来做一些具有相同可见效果的事情。
这是一个简单的独立示例:
public interface IFoo
{
void M1();
void M2();
}
public class Foo : IFoo
{
// Explicit interface implementation
void IFoo.M1() {}
// Implicit interface implementation
public void M2() {}
}
class Test
{
static void Main()
{
Foo foo = new Foo();
foo.M1(); // Compile-time failure
foo.M2(); // Fine
IFoo ifoo = foo;
ifoo.M1(); // Fine
ifoo.M2(); // Fine
}
}
IList<T>.Count
明确实施:
int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);
这样做是为了当你有一个简单的数组变量时,你没有两个Count
和Length
直接可用的。
通常,当您希望确保可以以特定方式使用类型时使用显式接口实现,而不强制该类型的所有使用者以这种方式考虑它。
编辑:哎呀,糟糕的回忆。ICollection.Count
是明确实现的。泛型IList<T>
被处理为下面的Hans描述。
显式接口实现。简而言之,您将其声明为void IControl.Paint() { }
or int IList<T>.Count { get { return 0; } }
。
它与 IList 的显式接口实现没有什么不同。仅仅因为你实现了接口并不意味着它的成员需要作为类成员出现。它确实实现了 Count 属性,只是没有在 X[] 上公开它。
//----------------------------------------------------------------------------------------
// ! READ THIS BEFORE YOU WORK ON THIS CLASS.
//
// The methods on this class must be written VERY carefully to avoid introducing security holes.
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]".
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>,
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is
// made:
//
// ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates
// it for type <T> and executes it.
//
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.)
//----------------------------------------------------------------------------------------
sealed class SZArrayHelper {
// It is never legal to instantiate this class.
private SZArrayHelper() {
Contract.Assert(false, "Hey! How'd I get here?");
}
/* ... snip ... */
}
具体这部分:
接口存根分派器将此视为一种特殊情况,加载 SZArrayHelper,找到相应的泛型方法(仅 通过方法名称匹配),将其实例化为类型并执行它。
(强调我的)
来源(向上滚动)。