说我有这个班:
public class Animal : IEquatable<Animal>
{
public string Name { get; set; }
public bool Equals(Animal other)
{
return Name.Equals(other.Name);
}
public override bool Equals(object obj)
{
return Equals((Animal)obj);
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
这是测试:
var animals = new[] { new Animal { Name = "Fred" } };
现在,当我这样做时:
animals.ToList().Contains(new Animal { Name = "Fred" });
它调用正确的通用Equals
重载。问题在于数组类型。假设我这样做:
animals.Contains(new Animal { Name = "Fred" });
它调用非通用Equals
方法。实际上T[]
没有公开ICollection<T>.Contains
方法。在上述情况下,调用IEnumerable<Animal>.Contains
扩展重载,然后调用ICollection<T>.Contains
。以下是IEnumerable<T>.Contains
的实施方式:
public static bool Contains<TSource>(this IEnumerable<TSource> source, TSource value)
{
ICollection<TSource> collection = source as ICollection<TSource>;
if (collection != null)
{
return collection.Contains(value); //this is where it gets done for arrays
}
return source.Contains(value, null);
}
所以我的问题是:
List<T>.Contains
和T[].Contains
表现不一样?换句话说,为什么前者调用泛型Equals
和后者非泛型Equals
,即使这两个集合都是通用的?T[].Contains
实施? 编辑:为什么这很重要或为什么我这样问:
如果在实施Equals
时忘记覆盖非通用IEquatable<T>
,它会跳起来,在这种情况下,T[].Contains
之类的调用会引用参考平等检查。特别是当她希望所有通用集合在通用Equals
上运行时。
您失去了实施IEquatable<T>
的所有好处(即使它不是参考类型的灾难)。
正如评论中所述,只是对了解内部细节和设计选择感兴趣。没有其他一般情况我可以想到非泛型Equals
将首选,无论是任何List<T>
还是基于集合(Dictionary<K,V>
等)操作。更糟糕的是,had Animal been a struct, Animal[].Contains calls the generic Equals
,使T []实现有点奇怪,开发人员应该知道的一切。
注意:仅当类实现Equals
时才调用IEquatable<T>
的通用版本。如果该类未实现{{ 1}},调用IEquatable<T>
的非泛型重载,无论它是Equals
还是List<T>.Contains
调用。
答案 0 :(得分:9)
数组不实现IList<T>
,因为它们可以是多维的,非零的。
但是在运行时,具有零下限的单维数组会自动实现IList<T>
和其他一些通用接口。这个运行时hack的目的在下面的2个引号中详细说明。
这里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执行一个小技巧:当a 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
第二行抛出异常,后面是stacktrace:
DummyClass.Equals(Object obj)at System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T []数组,T值,的Int32的startIndex,的Int32计数) 在System.Array.IndexOf(T []数组,T值)at 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.GenericEqualityComparer
1[DummyStruct] System.Collections.Generic.GenericEqualityComparer
1 [DummyClass]
两者都使用GenericEqualityComparer,它调用IEquatable方法。 事实上,默认比较器调用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)中强>
检查!
为什么不呢?好吧,我猜它是一个bug或实现细节,由于SZArrayHelper描述类中的以下行,因结构而不同:
“T”将反映用于调用方法的接口。实际的运行时“this”将是可转换为“T []”的数组(即对于原始值和值类型,它将是&gt;&gt; 完全“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
中究竟是如何发生的。
答案 1 :(得分:1)
Arrays确实实现了通用接口IList<T>
,ICollection<T>
和IEnumerable<T>
,但实现是在运行时提供的,因此文档构建工具不可见(这就是为什么你要'请参阅ICollection<T>.Contains
的msdn文档中的Array
。
我怀疑运行时实现只调用数组已经存在的非泛型IList.Contains(object)
因此,您的类中的非泛型Equals
方法被称为。
答案 2 :(得分:0)
Array没有名称为contains的方法,这是Enumerable类的扩展方法。
Enumerable.Contains方法,您在数组中使用它,
正在使用默认相等比较器。
默认的相等比较器,需要覆盖Object.Equality方法。
这是因为向后兼容。
列表有自己的特定实现,但Enumerable应与任何Enumerable兼容,从.NET 1到.NET 4.5
祝你好运