你可能知道,C#中的数组实现了 IList<T>
等其他接口。但是,他们没有公开实现 IList<T>
的Count属性,而只有 Length 属性。
这是C#/.NET违反其自己的接口实现规则的一个明显例子吗?还是我漏掉了什么?
正如您所知,C#中的数组实现了
IList<T>
等接口。
好吧,是的,嗯不是真的。这是.NET 4框架中Array类的声明:
[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable,
IStructuralComparable, IStructuralEquatable
{
// etc..
}
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(); }
}
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;
}
Array
类的声明,它不是数组的类型,而是数组的基本类型。在C#中,单维数组确实实现了IList<T>
。非泛型类型当然也可以实现泛型接口...这是因为有许多不同的类型——typeof(int[])
!= typeof(string[])
,所以typeof(int[])
实现了IList<int>
,而typeof(string[])
实现了IList<string>
。 - Jon SkeetArray
的推理(正如你展示的那样,它是一个抽象类,所以不可能是数组对象的实际类型)和结论(它不实现IList<T>
)在我看来是不正确的。它实现IList<T>
的方式是不寻常和有趣的,我同意这一点 - 但这纯粹是一个实现细节。声称T[]
不实现IList<T>
是误导性的,在我看来。这违反了规范和所有观察到的行为。 - Jon SkeetIList<T>
是因为Array
没有实现,这个逻辑是我不同意的主要部分。除此之外,我认为我们需要就一个类型实现一个接口的定义达成共识:在我的理解中,数组类型展示了除了GetInterfaceMapping
以外的实现了IList<T>
的所有可观测特征。再次强调,对我来说,如何实现这一点并不那么重要,就像我说System.String
是不可变的,尽管具体的实现细节是不同的。 - Jon SkeetIList<T>
才能正常工作。 - Tobias Knauss根据Hans的回答更新
感谢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[]
类型,即使其实际上是显式实现某些成员(如 Count
),也要表现出实现了该接口。从这个角度来看,该接口已被实现。这是解释发生的最好方式。T[,]
不实现 IList<T>
。typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))
给出一个异常:
Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.
string[] strings = { "a", "b", "c" };
IList<object> objects = strings;
...这使得看起来像typeof(string[])
实现了IList<object>
,但实际上并不是这样。
CLI规范(ECMA-335)第1部分第8.7.1节中有这样一段:
如果以下至少一个条件成立,则签名类型T与签名类型U兼容:
...
T是一个从零开始的一维数组V [],U是IList ,V与W的数组元素兼容。 (实际上它没有提到ICollection 或IEnumerable ,我认为这是规范中的错误。)对于非变性,CLI规范直接遵循语言规范。来自第1部分8.9.1节:此外,使用元素类型T创建的向量实现接口System.Collections.Generic.IList ,其中U:= T。(§8.7)(向量是具有零基础的单维数组。)string[]
作为ICollection<object>.Count
的实现时,它无法以完全正常的方式处理。这算不算显式接口实现?我认为将其视为这种方式是合理的,因为从语言角度来看,除非直接要求接口映射,否则它总是以这种方式表现。ICollection.Count
呢?Count
属性的非通用ICollection
。这次我们可以获得接口映射,事实上,System.Array
直接实现了该接口。在Array
中,ICollection.Count
属性实现的文档说明它是通过显式接口实现来实现的。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
}
}
SiameseCat()
将实现IList<Animal>
,即使后者类型既不在支持的接口列表中,也不被它们所隐含(尽管IList<SiameseCat>
隐含了IEnumerable<Animal>
,但IList<SiameseCat>
并不隐含IList<Animal>
)。 - supercatIList
和ICollection
。这不仅涉及到OP提到的Count
属性,还包括IList
接口中存在的其他协定,例如Add
,Remove
等。在代码中,我总是可以像这样做 - int[] myArr = new int[20];((IList)myArr).Add(4); var myCount = ((ICollection)myArr).Count;
- RBTCount
是可以的,但使用Add
总是会抛出异常,因为数组的大小是固定的。 - Jon SkeetIList<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所述。
ICollection
声明了 Count
,如果一个带有“collection”一词的类型不使用 Count
,那么这将会更加令人困惑 :). 在做出这些决策时总是存在权衡。 - dlevIList<T>
接口。我敢说,在底层实现接口的方式可能很复杂,但在许多情况下都是如此。如果有人说System.String
是不可变的,只是因为内部工作方式是可变的,你是否也会对其进行反对票?对于所有实际目的来说 - 而且肯定就C#语言而言 - 它是显式实现。 - Jon Skeet显式接口实现。简而言之,您可以像这样声明它:void IControl.Paint() { }
或 int IList<T>.Count { get { return 0; } }
。
//----------------------------------------------------------------------------------------
// ! 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,找到相应的通用方法(仅通过方法名称匹配),为类型实例化它并执行它。
(我强调了一些内容)
来源(向上滚动)。
这与 IList 的显式接口实现没有区别。仅仅因为你实现了接口,并不意味着它的成员必须出现在类成员中。 它确实实现了 Count 属性,只是没有在 X[] 上公开它。
Array
类必须用 C# 编写。 - user541686