Han*_*ant 84
您可能知道,C#中的数组实现
IList<T>了其他接口
嗯,是的,呃不,不是真的.这是.NET 4框架中Array类的声明:
[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable,
IStructuralComparable, IStructuralEquatable
{
// etc..
}
Run Code Online (Sandbox Code Playgroud)
它实现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(); }
}
Run Code Online (Sandbox Code Playgroud)
对于具有"未找到接口"的具体数组类型,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;
}
Run Code Online (Sandbox Code Playgroud)
是的,你当然可以称这条评论为"违反规则":)否则它会变得很方便.非常隐蔽,您可以在SSRI20(CLR的共享源代码分发)中查看.搜索"IList"以查看类型替换发生的位置.查看它的最佳位置是clr/src/vm/array.cpp,GetActualImplementationForArrayGenericIListMethod()方法.
与CLR中允许为WinRT(又称Metro)编写托管代码的语言投影相比,CLR中的这种替换相当温和.几乎任何核心的.NET类型都可以替代它.IList <>映射到IVector <>,例如,完全不受管理的类型.它本身是一个替代,COM不支持泛型类型.
嗯,那是看幕后幕后发生的事情.它可能是非常不舒服,奇怪和陌生的海洋,龙生活在地图的尽头.使地球变平并模拟托管代码中真实情况的不同图像非常有用.将它映射到每个人最喜欢的答案都很舒服.哪个值对于值类型不能很好地工作(不要改变结构!)但是这个很好地隐藏了.GetInterfaceMap()方法失败是我能想到的抽象中唯一的漏洞.
Jon*_*eet 80
根据汉斯的回答新答案
感谢Hans给出的答案,我们可以看到实施比我们想象的要复杂一些.编译器和CLR都非常努力地给出数组类型实现的印象IList<T>- 但是数组方差使得这更加棘手.与Hans的答案相反,数组类型(单维,零基础)确实直接实现了泛型集合,因为任何特定数组的类型都不是 System.Array - 这只是数组的基本类型.如果你问一个数组类型它支持哪些接口,它包括泛型类型:
foreach (var type in typeof(int[]).GetInterfaces())
{
Console.WriteLine(type);
}
Run Code Online (Sandbox Code Playgroud)
输出:
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]
Run Code Online (Sandbox Code Playgroud)
对于单维,从零开始的数组,就语言而言,数组确实也实现IList<T>了.C#规范的第12.1.2节说明了这一点.因此,无论底层实现如何,语言都必须像任何其他接口一样表现为T[]实现的类型IList<T>.从这个角度来看,接口是通过明确实现的一些成员实现的(例如Count).对于正在发生的事情,这是语言层面的最佳解释.
请注意,这仅适用于一维数组(和基于零的数组,而不是C#作为一种语言说明非基于零的数组).T[,] 没有实现IList<T>.
从CLR的角度来看,一些更有趣的事情正在发生.您无法获取通用接口类型的接口映射.例如:
typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))
Run Code Online (Sandbox Code Playgroud)
给出例外:
Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.
Run Code Online (Sandbox Code Playgroud)
那怪小的原因呢?嗯,我相信这真的是由于阵列协方差,这是类型系统中的疣,IMO.即使IList<T>是不协变(和不能安全),数组协方差让这个工作:
string[] strings = { "a", "b", "c" };
IList<object> objects = strings;
Run Code Online (Sandbox Code Playgroud)
...它使它看起来像typeof(string[])工具IList<object>,当它不是真的.
CLI规范(ECMA-335)分区1,第8.7.1节,具有:
当且仅当下列中的至少一个成立时,签名类型T与签名类型U兼容
...
T是基于零的秩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[]被要求执行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
}
}
Run Code Online (Sandbox Code Playgroud)
dle*_*lev 20
IList<T>.Count是明确实现的:
int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);
Run Code Online (Sandbox Code Playgroud)
这样做是为了当你有一个简单的数组变量时,你没有这两个Count并且Length直接可用.
通常,当您希望确保可以以特定方式使用类型时,使用显式接口实现,而不强制该类型的所有使用者以这种方式考虑它.
编辑:哎呀,不好回忆那里.ICollection.Count明确实施.该通用IList<T>名称由Hans descibes处理.
| 归档时间: |
|
| 查看次数: |
10534 次 |
| 最近记录: |