数组没有实现
IList<T>
,因为它们可以是多维和非零基础的。
然而,在运行时,具有下限为零的单维数组自动实现了
IList<T>
和一些其他通用接口。这种运行时技巧的目的在下面的两个引号中详细说明。
在这里
http://msdn.microsoft.com/en-us/library/vstudio/ms228502.aspx 上说:
“在 C# 2.0 及更高版本中,具有下限为零的单维数组自动实现
IList<T>
。这使您可以创建通用方法,使用相同的代码来迭代数组和其他集合类型。该技术主要用于读取集合中的数据。不能使用
IList<T>
接口向数组添加或删除元素。如果您尝试在此上下文中调用像
RemoveAt
这样的
IList<T>
方法,则会引发异常。”
Jeffrey Richter 在他的书中说:
“CLR 团队不想让
System.Array
实现
IEnumerable<T>
、
ICollection<T>
和
IList<T>
,因为这涉及到多维数组和非零基础数组的问题。在 System.Array 上定义这些接口将为所有数组类型启用这些接口。相反,CLR 执行一个小技巧:当创建单维、零下限数组类型时,CLR 自动使数组类型实现
IEnumerable<T>
、
ICollection<T>
和
IList<T>
(其中
T
是数组的元素类型),并且对于所有数组类型的基类型也实现了三个接口,只要它们是引用类型。”
更深入地挖掘,
SZArrayHelper 是提供这种 “hacky” IList 实现的类,用于单维零基础数组。
以下是该类的描述:
包含实现:
bool Contains<T>(T value) {
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)
{
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) at
System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf(T[] array, T value) at
System.SZArrayHelper.Contains(T value)
现在的问题是我们如何从实现了 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.GenericEqualityComparer
1[DummyStruct]
System.Collections.Generic.GenericEqualityComparer
1[DummyClass]
两者都使用GenericEqualityComparer,它调用IEquatable方法。实际上,Default comparer会调用以下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[]"-对于oref,它可以是"U[]",其中U派生自T)。
所以现在我们几乎知道所有的事情了。唯一剩下的问题是,为什么U没有通过typeof(IEquatable<T>).IsAssignableFrom(c)
的检查呢?
PS:为了更准确,SZArrayHelper Contains实现代码来自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
{
return o as T;
}
现在我想知道三个感叹号在那个神秘的getILIntrinsicImplementation
中是如何发生的。
IEquatable<T>
接口,你必须重写Equals(object)
和GetHashCode()
方法。你不能只实现其中一个并期望一切都能正常工作。 - Jeff MercadoEquals
,无论是任何List<T>
还是基于集合的(Dictionary<K,V>
等)操作,即使没有实现非泛型Equals
。嗯,这一切都是以T[]
足够泛型为前提的。更糟糕的是,如果Animal
是一个结构体,Animal[].Contains
会调用通用Equals
*,这使得T[]
的实现有点奇怪,开发人员应该了解这一点。我会更新我的问题,让它更清楚明确。 - nawfal