数组不实现IList<T>,因为它们可以是多维的且基于非零的。
但是,在运行时,下限为零的一维数组会自动实现 IList<T> 和其他一些通用接口。下面用 2 个引号详细说明了此运行时 hack 的目的。
这里http://msdn.microsoft.com/en-us/library/vstudio/ms228502.aspx 说:
在 C# 2.0 及更高版本中,具有下限的一维数组
零自动实现IList<T>。这使您能够创建
可以使用相同代码遍历数组的泛型方法
和其他集合类型。该技术主要用于
读取集合中的数据。 IList<T> 接口不能用于
从数组中添加或删除元素。如果出现异常会抛出
您尝试在数组中调用 IList<T> 方法,例如 RemoveAt
这个上下文。
杰弗里·里希特在他的书中说:
CLR 团队不希望 System.Array 实现 IEnumerable<T>,
不过,ICollection<T> 和 IList<T>,因为与
多维数组和非零基数组。定义这些
System.Array 上的接口将为所有用户启用这些接口
数组类型。相反,CLR 执行了一个小技巧:当
创建一维零下界数组类型,CLR
自动使数组类型实现IEnumerable<T>,
ICollection<T> 和 IList<T>(其中 T 是数组的元素类型)和
还为所有数组类型的基实现了三个接口
类型,只要它们是引用类型。
深入挖掘,SZArrayHelper 是为单维零基数组提供这种“hacky”IList 实现的类。
这是类的描述:
//----------------------------------------------------------------------------------------
// ! 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.)
//----------------------------------------------------------------------------------------
并包含实现:
bool Contains<T>(T value) {
//! Warning: "this" is an array, not an SZArrayHelper. See comments above
//! or you may introduce a security hole!
T[] _this = this as T[];
BCLDebug.Assert(_this!= null, "this should be a T[]");
return Array.IndexOf(_this, value) != -1;
}
所以我们调用下面的方法
public static int IndexOf<T>(T[] array, T value, int startIndex, int count) {
...
return EqualityComparer<T>.Default.IndexOf(array, value, startIndex, count);
}
到目前为止一切顺利。但现在我们进入了最好奇/最有问题的部分。
考虑以下示例(基于您的后续问题)
public struct DummyStruct : IEquatable<DummyStruct>
{
public string Name { get; set; }
public bool Equals(DummyStruct other) //<- he is the man
{
return Name == other.Name;
}
public override bool Equals(object obj)
{
throw new InvalidOperationException("Shouldn't be called, since we use Generic Equality Comparer");
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
public class DummyClass : IEquatable<DummyClass>
{
public string Name { get; set; }
public bool Equals(DummyClass other)
{
return Name == other.Name;
}
public override bool Equals(object obj)
{
throw new InvalidOperationException("Shouldn't be called, since we use Generic Equality Comparer");
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
我已经在两个非 IEquatable<T>.Equals() 实现中植入了异常抛出。
惊喜是:
DummyStruct[] structs = new[] { new DummyStruct { Name = "Fred" } };
DummyClass[] classes = new[] { new DummyClass { Name = "Fred" } };
Array.IndexOf(structs, new DummyStruct { Name = "Fred" });
Array.IndexOf(classes, new DummyClass { Name = "Fred" });
此代码不会引发任何异常。我们直接进入 IEquatable Equals 实现!
但是当我们尝试下面的代码时:
structs.Contains(new DummyStruct {Name = "Fred"});
classes.Contains(new DummyClass { Name = "Fred" }); //<-throws exception, since it calls object.Equals method
第二行抛出异常,堆栈跟踪如下:
DummyClass.Equals(Object obj) 在
System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[] 数组,T 值,Int32 startIndex,Int32 计数)
在 System.Array.IndexOf(T[] 数组,T 值) 在
System.SZArrayHelper.Contains(T 值)
现在的错误?或者这里的大问题是我们如何从实现 IEquatable<T> 的 DummyClass 获得 ObjectEqualityComparer?
因为下面的代码:
var t = EqualityComparer<DummyStruct>.Default;
Console.WriteLine(t.GetType());
var t2 = EqualityComparer<DummyClass>.Default;
Console.WriteLine(t2.GetType());
生产
System.Collections.Generic.GenericEqualityComparer1[DummyStruct]
System.Collections.Generic.GenericEqualityComparer1[DummyClass]
两者都使用调用 IEquatable 方法的 GenericEqualityComparer。
实际上默认比较器调用以下 CreateComparer 方法:
private static EqualityComparer<T> CreateComparer()
{
RuntimeType c = (RuntimeType) typeof(T);
if (c == typeof(byte))
{
return (EqualityComparer<T>) new ByteEqualityComparer();
}
if (typeof(IEquatable<T>).IsAssignableFrom(c))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(GenericEqualityComparer<int>), c);
} // RELEVANT PART
if (c.IsGenericType && (c.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
RuntimeType type2 = (RuntimeType) c.GetGenericArguments()[0];
if (typeof(IEquatable<>).MakeGenericType(new Type[] { type2 }).IsAssignableFrom(type2))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(NullableEqualityComparer<int>), type2);
}
}
if (c.IsEnum && (Enum.GetUnderlyingType(c) == typeof(int)))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(EnumEqualityComparer<int>), c);
}
return new ObjectEqualityComparer<T>(); // CURIOUS PART
}
好奇的部分用粗体显示。显然,对于带有 Contains 的 DummyClass,我们到了最后一行,但没有通过
typeof(IEquatable).IsAssignableFrom(c)
检查!
为什么不呢?好吧,我猜它要么是错误,要么是实现细节,由于 SZArrayHelper 描述类中的以下行,因此结构不同:
“T”将反映用于调用方法的接口。实际运行时 "this" 将是可转换为 "T[]" 的数组(即对于原始类型和值类型,它将是 >>完全是 "T[]" - 对于 orefs,它可能是"U[]" 其中 U 派生自 T。)
所以我们现在几乎什么都知道了。剩下的唯一问题是,U 怎么没有通过typeof(IEquatable<T>).IsAssignableFrom(c) 检查?
PS:更准确地说,SZArrayHelper 包含的实现代码来自 SSCLI20。目前的实现似乎发生了变化,导致反射器为此方法显示以下内容:
private bool Contains<T>(T value)
{
return (Array.IndexOf<T>(JitHelpers.UnsafeCast<T[]>(this), value) != -1);
}
JitHelpers.UnsafeCast 显示来自 dotnetframework.org 的以下代码
static internal T UnsafeCast<t>(Object o) where T : class
{
// The body of this function will be replaced by the EE with unsafe code that just returns o!!!
// See getILIntrinsicImplementation for how this happens.
return o as T;
}
现在我想知道三个感叹号以及它究竟是如何发生在那个神秘的getILIntrinsicImplementation 中的。