假设我有这样一个类:
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
的行为应该不同?换句话说,即使两个集合都是genericEquals
,后者调用非泛型Equals
,以一种我可以看到T[].Contains
实现的方式?编辑:为什么重要,或者为什么我问这个:
IEquatable<T>
时忘记覆盖非泛型Equals
,在这种情况下,像T[].Contains
这样的调用会进行引用相等性检查。特别是当她期望所有泛型集合都在泛型Equals
.IEquatable<T>
的所有好处(即使这对引用类型来说并不是灾难)。Equals
,可以是任何基于List<T>
或set (Dictionary<K,V>
等)的操作。更糟糕的是,had Animal been a struct, Animal[].Contains calls the ,所有这些使得T[]实现有点奇怪,这是开发人员应该知道的。注意:只有在类实现IEquatable<T>
__时才会调用Equals
的泛型版本。如果该类不实现IEquatable<T>
,则无论是由List<T>.Contains
还是由T[].Contains
调用,都会调用Equals
的非泛型重载。
发布于 2013-11-10 16:33:42
数组不实现IList<T>
,因为它们可以是多维的,也可以不是从零开始的。
但是,在运行时,下限为零的一维数组会自动实现IList<T>
和其他一些通用接口。这个运行时攻击的目的在下面的两个引号中进行了详细阐述。
这里是http://msdn.microsoft.com/en-us/library/vstudio/ms228502.aspx,它说:
C# 2.0及更高版本中的
,下限为零的一维数组会自动实现
IList<T>
。这使您能够创建泛型方法,这些方法可以使用相同的代码循环访问数组和其他集合类型。此技术主要用于读取集合中的数据。IList<T>
接口不能用于在数组中添加或删除元素。如果您尝试在此上下文中的数组上调用诸如RemoveAt
之类的IList<T>
方法,将会抛出异常。
杰弗里·里希特在他的书中说:
尽管如此,CLR团队不希望
System.Array
实现IEnumerable<T>
、ICollection<T>
和IList<T>
,因为与多维数组和非零基数组相关的问题。在System.Array上定义这些接口将为所有数组类型启用这些接口。相反,CLR执行一个小技巧:当创建一维的零下限数组类型时,CLR会自动使数组类型实现IEnumerable<T>
、ICollection<T>
和IList<T>
(其中T
是数组的元素类型),并为所有数组类型的基类型实现这三个接口(只要它们是引用类型)。
深入挖掘,SZArrayHelper是为基于零的一维数组提供这种“老套”IList实现的类。
下面是类的描述:
//---------------------------------------------------------------------------------------- //!在学习本课程之前,请先阅读本文。/此类上的方法必须非常小心地编写,以避免引入安全漏洞。//这是因为它们是用特殊的"this“调用的!所有这些方法的"this“对象//都不是SZArrayHelper对象。相反,它们的类型是U[] //,其中U[]是可转换为T[]的。不会实例化任何实际的SZArrayHelper对象。因此,您将//看到许多转换为"this“"T[]”的表达式。/此类是允许T[]类型的SZ数组公开IList、// IList等一直到IList所必需的。当//进行以下调用时:/ ((IList) (new Un)).SomeIListMethod() /接口存根调度器将此视为特殊情况,加载SZArrayHelper,//找到相应的泛型方法(仅通过方法名匹配),实例化//它的类型并执行它。/ "T“将反映用于调用该方法的接口。实际运行时"this“将是//可转换为"T[]”的数组(即,对于原语和值类型,它将恰好是// "T[]“-对于oref,它可以是U从T派生的"U[]“。)//--------------------------------------
并包含实现:
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);
}
到目前一切尚好。但现在我们到了最令人好奇/ But的部分。
考虑以下示例(基于您的后续问题)
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
第二行抛出异常,堆栈跟踪如下:
System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[]的
数组(Object DummyClass.Equals),T的值,System.Array.IndexOf(T[]的数组,T的值)System.SZArrayHelper.Contains(T的值)的Int32开始索引,Int32的计数)
现在是bug了吗?这里最大的问题是,我们是如何从实现了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
1DummyClass
两者都使用调用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派生)
。
所以我们现在知道了几乎所有的事情。剩下的唯一问题是,为什么你没有通过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
中发生的。
发布于 2013-11-10 16:45:54
数组确实实现了通用接口IList<T>
、ICollection<T>
和IEnumerable<T>
,但实现是在运行时提供的,因此对于文档构建工具是不可见的(这就是为什么您在Array
的msdn文档中看不到ICollection<T>.Contains
)。
我怀疑运行时实现只是调用数组已有的非泛型IList.Contains(object)
。
因此,类中的非泛型Equals
方法被调用。
发布于 2013-11-10 16:37:59
数组没有名为contains的方法,这是可枚举类的扩展方法。
Enumerable.Contains方法,您在数组中使用该方法,
正在使用默认相等比较器。
默认相等比较器需要重写Object.Equality方法。
这是因为向后兼容。
列表有它们自己的特定实现,但是Enumerable应该与从.NET 1到.NET 4.5的任何Enumerable兼容
祝好运
https://stackoverflow.com/questions/19887562
复制相似问题