C#中的数组如何部分实现IList <t>?</t>

时间:2012-06-22 20:01:11

标签: c# .net arrays list interface

您可能知道,C#中的数组实现IList<T>以及其他接口。但不知何故,他们在没有公开实现IList<T>的Count属性的情况下这样做了!数组只有一个Length属性。

这是一个明显的例子,C#/ .NET打破了自己关于接口实现的规则,还是我错过了什么?

6 个答案:

答案 0 :(得分:83)

  

您可能知道,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&lt;&gt;。它不能,Array不通用。通用IEnumerable&lt;&gt;也是如此。和ICollection&lt;&gt;接口

但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&lt;&gt;没有问题。

这是一种类似于鸭子的庸医打字。这种类型的输入会产生一种错觉,即每个值类型都派生自从Object派生的ValueType。编译器和CLR都具有数组类型的特殊知识,就像它们对值类型一样。编译器看到您尝试转换为IList&lt;&gt;并说“好吧,我知道怎么做!”。并发出castclass IL指令。 CLR没有问题,它知道如何提供IList&lt;&gt;的实现。适用于底层数组对象。它具有其他隐藏的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;
    }

是的,您当然可以将该评论称为“违反规则”:)否则就会非常方便。非常隐蔽,您可以在SSRI20(CLR的共享源代码分发)中查看。搜索“IList”以查看类型替换发生的位置。查看它的最佳位置是clr / src / vm / array.cpp,GetActualImplementationForArrayGenericIListMethod()方法。

与CLR中允许为WinRT(又称Metro)编写托管代码的语言投影相比,CLR中的这种替换相当温和。几乎任何核心的.NET类型都可以替代它。的IList&LT;&GT;映射到IVector&lt;&gt;例如,完全不受管理的类型。它本身就是一种替代,COM不支持泛型类型。

嗯,那是看幕后幕后发生的事情。它可能是非常不舒服,奇怪和陌生的海洋,龙生活在地图的尽头。使地球变平并模拟托管代码中真实情况的不同图像非常有用。将它映射到每个人最喜欢的答案都很舒服。哪个值对于值类型不能很好地工作(不要改变结构!)但是这个很好地隐藏了。 GetInterfaceMap()方法失败是我能想到的抽象中唯一的漏洞。

答案 1 :(得分:78)

根据汉斯的回答新答案

感谢Hans给出的答案,我们可以看到实施比我们想象的要复杂一些。编译器和CLR都尝试非常难以给人一种数组类型实现IList<T>的印象 - 但是数组方差使得这种方法更加棘手。与Hans的答案相反,数组类型(单维,零基础)确实直接实现了泛型集合,因为任何特定数组的类型不是 System.Array - 这只是数组的 base 类型。如果你问一个数组类型它支持哪些接口,它包括泛型类型:

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;

...这使{em>看起来像typeof(string[])一样实现IList<object>,但实际上并非如此。

CLI规范(ECMA-335)分区1,第8.7.1节,具有:

  

签名类型T与签名类型U兼容,当且仅当至少满足下列条件之一时

...

  

T是从零开始的rank-1数组V[]UIList<W>,而V与数组元素兼容 - 与W。

(它实际上没有提到我认为是规范中的错误的ICollection<W>IEnumerable<W>。)

对于非差异,CLI规范直接与语言规范一起使用。从分区1的第8.9.1节开始:

  

此外,元素类型为T的创建向量实现接口System.Collections.Generic.IList<U>,其中U:= T.(§8.7)

(A vector 是一个零基数的一维数组。)

现在就实现细节来说,显然CLR正在做一些时髦的映射以保持赋值兼容性:当string[]被要求实现{{1}时},它无法正常处理相当。这是否算作显式接口实现?我认为以这种方式对待它是合理的,因为除非你直接要求接口映射,否则它总是从语言的角度行为

ICollection<object>.Count怎么样?

到目前为止,我已经讨论过泛型接口,但是那里有非ICollection.Count的{​​{1}}属性。这次我们可以获取接口映射,实际上接口是由System.Array直接实现的。 ICollectionICollection.Count属性实现的文档说明它是通过显式接口实现实现的。

如果有人能想到这种显式接口实现与“普通”显式接口实现不同的方式,我很乐意进一步研究它。

关于显式接口实现的旧答案

尽管如上所述,由于对数组的了解更为复杂,您仍然可以通过explicit interface implementation使用相同的可见效果执行某些操作。

这是一个简单的独立示例:

Count

答案 2 :(得分:20)

IList<T>.Count已实施explicitly

int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);

这样做是为了当你有一个简单的数组变量时,你不能直接使用CountLength

通常,当您希望确保以特定方式使用类型时,可以使用显式接口实现,而不必强制该类型的所有使用者以这种方式考虑它。

编辑:糟糕,糟糕的回忆。 ICollection.Count已明确实施。通用IList<T>的处理方式为Hans descibes below

答案 3 :(得分:10)

Explicit interface implementation。简而言之,您将其声明为void IControl.Paint() { }int IList<T>.Count { get { return 0; } }

答案 4 :(得分:1)

它与IList的显式接口实现没有什么不同。仅仅因为您实现接口并不意味着其成员需要作为类成员出现。 实现Count属性,它只是不在X []上公开它。

答案 5 :(得分:1)

有参考资料来源:

//----------------------------------------------------------------------------------------
// ! 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,找到相应的泛型方法(简单匹配   按方法名称),将其实例化为类型并执行它。

(强调我的)

Source(向上滚动)。