gpt4 book ai didi

c# - List.Contains 和 T[].Contains 的行为不同

转载 作者:IT王子 更新时间:2023-10-29 04:44:39 24 4
gpt4 key购买 nike

假设我有这个类:

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);
}

所以我的问题是:

  1. 为什么应该 List<T>.ContainsT[].Contains表现不同?换句话说,为什么前者调用 generic Equals 后者非泛型 Equals 即使这两个集合都是通用的
  2. 有没有办法让我看到T[].Contains执行?

编辑:为什么这很重要或者我为什么要问这个:

  1. 万一她忘记覆盖非通用Equals,它就会跳闸 在执行 IEquatable<T> 时在这种情况下调用 T[].Contains进行引用相等性检查。尤其是当她希望所有通用集合通用Equals 进行操作时

  2. 您失去了实现 IEquatable<T> 的所有好处(尽管这对引用类型来说不是灾难)。

  3. 如评论中所述,我只想了解内部细节和设计选择。我想不出其他通用情况 非通用 Equals 将是首选,无论是 List<T>或基于集合(Dictionary<K,V> 等)的操作。更糟糕的是,had Animal been a struct, Animal[].Contains calls the generic Equals ,所有这些使得 T[] 实现有点奇怪,开发人员应该知道的事情。

注意: Equals 的通用版本仅在类 实现 IEquatable<T> 时调用. 如果类没有实现 IEquatable<T> , Equals 的非泛型重载无论它是否被 List<T>.Contains 调用都被调用或 T[].Contains .

最佳答案

数组不实现 IList<T>因为它们可以是多维的和非零基的。

但是在运行时,下限为零的一维数组会自动实现 IList<T>和其他一些通用接口(interface)。这个运行时 hack 的目的在下面用 2 个引号详细说明。

在这里http://msdn.microsoft.com/en-us/library/vstudio/ms228502.aspx它说:

In C# 2.0 and later, single-dimensional arrays that have a lower bound of zero automatically implement IList<T>. This enables you to create generic methods that can use the same code to iterate through arrays and other collection types. This technique is primarily useful for reading data in collections. The IList<T> interface cannot be used to add or remove elements from an array. An exception will be thrown if you try to call an IList<T> method such as RemoveAt on an array in this context.

Jeffrey Richter 在他的书中说:

The CLR team didn’t want System.Array to implement IEnumerable<T>, ICollection<T>, and IList<T>, though, because of issues related to multi-dimensional arrays and non-zero–based arrays. Defining these interfaces on System.Array would have enabled these interfaces for all array types. Instead, the CLR performs a little trick: when a single-dimensional, zero–lower bound array type is created, the CLR automatically makes the array type implement IEnumerable<T>, ICollection<T>, and IList<T> (where T is the array’s element type) and also implements the three interfaces for all of the array type’s base types as long as they are reference types.

深入挖掘,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) 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)

现在是错误?或者这里的大问题是我们如何从我们的 DummyClass 中得到 ObjectEqualityComparer,它确实实现了 IEquatable<T>

因为下面的代码:

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.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)

检查!

为什么不呢?好吧,我猜它要么是错误要么是实现细节,由于 SZArrayHelper 描述类中的以下行,它因结构而异:

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.)

所以我们现在几乎什么都知道了。剩下的唯一问题是你怎么没通过 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 中到底是怎么发生的。 .

关于c# - List<T>.Contains 和 T[].Contains 的行为不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19887562/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com