如您所知,C#中的数组实现IList<T>
了其他接口。尽管不知何故,他们没有公开实现Count的Count属性就这样做了IList<T>
。数组只有Length属性。
这是C#/。NET违反其关于接口实现的规则的公然示例还是我遗漏了一些东西?
Array
是一个“魔术”类,无法使用C#或任何其他针对.net的语言来实现。但是此特定功能在C#中可用。
如您所知,C#中的数组实现IList<T>
了其他接口。尽管不知何故,他们没有公开实现Count的Count属性就这样做了IList<T>
。数组只有Length属性。
这是C#/。NET违反其关于接口实现的规则的公然示例还是我遗漏了一些东西?
Array
是一个“魔术”类,无法使用C#或任何其他针对.net的语言来实现。但是此特定功能在C#中可用。
Answers:
根据汉斯的答案的新答案
感谢汉斯给出的答案,我们可以看到实现比我们想象的要复杂一些。编译器和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[])
工具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的向量将实现interface
System.Collections.Generic.IList<U>
,其中U:=T。(第8.7节)
(一个向量是基数为零的一维数组。)
现在,在条款实施细则,明确了CLR做一些时髦的映射,以保持分配兼容性这里:当string[]
被要求执行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
}
}
Count
罚款是有区别的,但是Add
因为数组是固定大小的,所以总是会抛出错误。
如您所知,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 <>。不能,数组不是通用的。通用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(aka Metro)编写托管代码的语言投影中发生的情况相比,CLR中的这种替代相当温和。几乎所有核心.NET类型都在那里被替换。例如,IList <>映射到IVector <>,这是一个完全不受管的类型。COM本身是替代品,不支持泛型类型。
好吧,那是看幕后发生的事情。这可能是非常不舒服,陌生和陌生的海洋,并且地图的尽头有龙。使地球变得平坦并为托管代码中实际发生的事情制作不同的图像非常有用。将它映射到每个人都喜欢的答案很舒服。对于值类型,这不太好用(不要变异一个结构!),但是这个隐藏得很好。GetInterfaceMap()方法失败是我能想到的唯一的泄漏。
Array
该类的声明,而不是数组的类型。它是数组的基本类型。C#中的一维数组确实实现了IList<T>
。非泛型类型肯定可以实现泛型接口...之所以起作用,是因为存在许多不同的类型- typeof(int[])
!= typeof(string []), so
typeof(int [])`实现IList<int>
和typeof(string[])
Implements IList<string>
。
Array
(如您所显示的是一个抽象类,因此不可能是数组对象的实际类型)和结论(未实现IList<T>
)都是不正确的IMO。该方法在其实现IList<T>
是不寻常的,有趣的,我会同意-但是这纯粹是一种实现细节。声称T[]
未实现IList<T>
会误导IMO。它违反规范和所有观察到的行为。
IList<T>
,因为 Array
不?这种逻辑是我不同意的很大一部分。除此之外,我想我们不得不对意味着什么类型实现一个接口定义达成一致:在我看来,数组类型显示所有观察到的功能的它们实现的类型IList<T>
,比其他GetInterfaceMapping
。再次说明,实现方式对我而言并不那么重要,就像我很好地说这System.String
是不可变的,即使实现细节不同。
IList<T>
才能起作用。
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的描述处理。
string
)。
ICollection
声明了Count
,如果其中不带单词“ collection”的类型不使用:) ,那将更加令人困惑Count
。做出这些决定时总是要权衡取舍。
IList<T>
尽管语言和CLI规范似乎相反,但数组类型甚至没有实现。我敢说接口实现的工作方式可能很复杂,但是在许多情况下就是这种情况。您是否还会System.String
因为内部运作易变而拒绝有人说这是不可变的?出于所有实际目的(当然就C#语言而言),这是明确的隐含含义。
//----------------------------------------------------------------------------------------
// ! 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,找到相应的通用方法(仅 通过方法名称进行匹配),实例化其类型并执行它。
(强调我的)
来源(向上滚动)。
Array
必须使用C#编写该类!