c# - T[].Contains for struct 和 class 表现不同

标签 c# arrays generics struct equals

这是一个后续问题: List<T>.Contains and T[].Contains behaving differently

T[].ContainsT 时表现不同是类和结构。假设我有这个结构:

public struct Animal : IEquatable<Animal>
{
    public string Name { get; set; }

    public bool Equals(Animal other) //<- he is the man
    {
        return Name == 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.Contains(new Animal { Name = "Fred" }); // calls Equals(Animal)

在这里,通用 Equals 如我所料被正确调用。

但是对于:

public class Animal : IEquatable<Animal>
{
    public string Name { get; set; }

    public bool Equals(Animal other)
    {
        return Name == other.Name;
    }
    public override bool Equals(object obj) //<- he is the man
    {
        return Equals((Animal)obj);
    }
    public override int GetHashCode()
    {
        return Name == null ? 0 : Name.GetHashCode();
    }
}

var animals = new[] { new Animal { Name = "Fred" } };

animals.Contains(new Animal { Name = "Fred" }); // calls Equals(object)

非通用 Equals 被调用,带走了实现 `IEquatable 的好处。

为什么数组调用Equals struct[] 不同和 class[] , 尽管这两个系列看起来都很普通

数组的怪异令人沮丧,我正在考虑完全避免它......

注意: Equals 的通用版本仅当结构 实现 IEquatable<T> 时调用. 如果类型没有实现 IEquatable<T> , Equals 的非泛型重载无论它是否是 class 都会被调用或 struct .

最佳答案

看起来实际上并不是 Array.IndexOf() 最终被调用。查看源代码,如果是这样的话,我希望在这两种情况下都能调用 Equals(object) 。通过查看调用 Equals 时的堆栈跟踪,可以更清楚地了解为什么会出现您所看到的行为(值类型获取 Equals(Animal),但引用类型获取 Equals(object)。

这是值类型(struct Animal)的堆栈跟踪

at Animal.Equals(Animal other)
at System.Collections.Generic.GenericEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value)
at System.SZArrayHelper.Contains[T](T value)
at System.Linq.Enumerable.Contains[TSource](IEnumerable`1 source, TSource value) 

这是引用类型(对象 Animal)的堆栈跟踪

at Animal.Equals(Object obj)
at System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value, Int32 startIndex, Int32 count)
at System.Array.IndexOf[T](T[] array, T value)
at System.SZArrayHelper.Contains[T](T value)
at System.Linq.Enumerable.Contains[TSource](IEnumerable`1 source, TSource value)

从这里您可以看到调用的不是 Array.IndexOf - 它是 Array.IndexOf[T]。该方法确实最终使用了相等比较器。在引用类型的情况下,它使用调用 Equals(object) 的 ObjectEqualityComparer。对于值类型,它使用调用 Equals(Animal) 的 GenericEqualityComparer,大概是为了避免昂贵的装箱。

如果您在 http://www.dotnetframework.org 查看 IEnumerable 的源代码 它的顶部有这个有趣的部分:

// Note that T[] : IList<t>, and we want to ensure that if you use
// IList<yourvaluetype>, we ensure a YourValueType[] can be used
// without jitting.  Hence the TypeDependencyAttribute on SZArrayHelper.
// This is a special hack internally though - see VM\compile.cpp.
// The same attribute is on IList<t> and ICollection<t>.
[TypeDependencyAttribute("System.SZArrayHelper")]

我不熟悉 TypeDependencyAttribute,但从评论中,我想知道是否有一些特殊的魔法正在发生,这是数组的特殊之处。这可以解释 IndexOf[T] 是如何通过 Array 的 IList.Contains 最终被调用而不是 IndexOf 的。

关于c# - T[].Contains for struct 和 class 表现不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19888123/

相关文章:

c# - 如何使用 XmlSerializer 将字符串序列化为 CDATA?

c# - 当类型参数未知时如何调用泛型类型实例的方法?

c# - 在 ASP.NET 中使用 SecureString 有什么好处吗?

c# - SqlTransaction 中的 SqlDataAdapter.Fill() - 这是一种不好的做法吗?

c - 如何在C中输入char*?

C 编程将字符数组复制到二维字符数组

python - Django:在 GenericStackedInline 上禁止 can_delete

c# - 泛型方法如何返回所有类型 T

c# - 单元测试 : DateTime. 现在

javascript - jquery的图像预加载问题