【问题标题】:Boxing type equality and dictionary keys装箱类型相等和字典键
【发布时间】:2022-01-02 19:08:14
【问题描述】:

当涉及到盒装类型时,我对字典如何比较键有点困惑。

using System;
using System.Collections.Generic;
                
public class Program
{
     public static void Main()
     {
           int i = 5;
           int n = 5;
           
           object boxedI = i;
           object boxedN = n;
           
           Console.WriteLine("i == n ? " + (i == n) ); //true
           Console.WriteLine("bI == bN ? " + (boxedI == boxedN) ); //false
           
           Dictionary<object,int> _dict = new Dictionary<object,int> ();
           _dict.Add(boxedI,5);
           
           Console.WriteLine("_dict contains boxedI? " + _dict.ContainsKey(boxedI) ); //true
           Console.WriteLine("_dict contains boxedN? " + _dict.ContainsKey(boxedN) ); //!! also true, surprise me
           
           _dict.Add(boxedN,5);//exception
     }
}

我预计,由于相等运算符“失败”(AFAIK 它基于方法 GetHashCode,与字典用于构建其内部哈希表表单对象的方法相同),那么字典也应该“失败”盒装 I 和 N 的比较,但那是不是这样的。

这是我使用的小提琴:https://dotnetfiddle.net/DW54nN

所以我问是否有人可以向我解释这里附加了什么以及我在我的心智模型中缺少什么。

【问题讨论】:

  • 这是一个引用与值类型的东西,当你使用对象时它会变成一个值,所以当你比较它们时,它们会比较引用(boxedI和boxedN的两个引用不一样)当您使用 _dict.ContainsKey(boxedN) 时,它使用引用类型中的值
  • 嗯,更重要的是它使用 Equals 和 GetHashCode。如果您调用 boxedI.Equals(boxedN) 将返回 true,并且如果您对它们都调用 GetHashCode,它们将返回相同的值。
  • @JonSkeet 所以我的错误是假设 == 操作数等价于 Equals 等价(只需阅读关于 C# corer 的帖子)。感谢简洁明了。

标签: c# dictionary equality boxing


【解决方案1】:

TLDR:使用 == 比较装箱值使用装箱对象的引用相等性,但使用 Equals() 比较装箱值使用基础值的 Equals()


当一个值类型被装箱时,装箱对象的GetHashCode()Equals() 方法实现调用装箱值的版本。​​

也就是说,给定:

  • 正确实现GetHashCode()Equals() 的值类型VT
  • x 的实例 VT
  • VT 的实例 yx 具有相同的值。
  • x 的盒装实例:bx
  • y 的盒装实例:by

情况如下:

x.Equals(y)      == true             // Original values are equal
bx.Equals(by)    == true             // Boxed values are equal
x.GetHashCode()  == y.GetHashCode()  // Original hashes are equal
bx.GetHashCode() == by.GetHashCode() // Boxed hashes are equal
bx.GetHashCode() == x.GetHashCode()  // Original hash code == boxed hash code

但是,== 运算符不是由盒装版本委托,实际上它是使用 reference 相等来实现的,所以:

(x == y)   == true  // Original values are equal using "=="
(bx == by) == false // Boxed values are not equal using "=="
ReferenceEquals(bx, by) == false // References differ

Dictionary 使用 GetHashCode()Equals() 来比较对象,因为它们委托给底层值,所以它可以正常工作。


以下程序证明了这一点:

using System;

namespace Demo
{
    struct MyStruct: IEquatable<MyStruct>
    {
        public int X;

        public bool Equals(MyStruct other)
        {
            return X == other.X;
        }

        public override bool Equals(object obj)
        {
            if (obj is not MyStruct other)
                return false;

            return X == other.X;
        }

        public override int GetHashCode()
        {
            return -X;
        }
    }

    class Program
    {
        static void Main()
        {
            var x = new MyStruct { X = 42 };
            var y = new MyStruct { X = 42 };
            object bx = x;
            object by = y;

            Console.WriteLine(bx.GetHashCode()); // -42
            Console.WriteLine(y.GetHashCode()); // -42

            Console.WriteLine(bx.Equals(by)); // True
            Console.WriteLine(bx == by); // False
            Console.WriteLine(object.ReferenceEquals(bx, by)); // False
        }
    }
}

【讨论】:

    【解决方案2】:

    这是一个引用 vs 值类型的东西:

    int i = 5;
    int n = 5;
    

    这些是值类型并被放入堆栈,因此当我们比较它们时,我们进入堆栈并可以说 i 和 n 的值为 5,这使得它们“相等”。

    object boxedI = i;
    object boxedN = n;
    

    当您将这些值放入object 时,您将创建一个“引用”类型,这意味着将一个值放入堆中,并将一个引用放入堆栈中,因此您可以想象在堆栈中您有:

    #0005 -> boxedI
    #0006 -> boxedN
    

    现在,当你做等于时,你正在比较 #0005 == #0006 是不一样的

    但是,当您将 boxedIboxedN 传递给 ContainsKey 时,该方法知道如何将引用(或指针)跟踪到堆上的值 (5)。

    所以当你要求 ContainsKey(boxedI) 时,你正在做的是要求 ContainsKey(5)(粗略地说)

    这就是为什么这两个是“相等的”

    【讨论】:

    • 是的,我想补充几句关于我的错误是如何产生的。我错误地认为 '==' 运算符只是 Equal() 运算符的简写。前者比较引用,后者比较值,因此行为与我的预期不同。
    • The Stack Is An Implementation Detail完全 与问题无关。这完全是关于引用与值类型的语义,这同样适用于堆栈分配的引用和/或堆分配的值
    • @Charlieface 我在解释为​​什么发帖人看到比较两种值类型和两种引用类型之间存在差异,如果不谈论堆栈,我看不到你是如何做到这一点的,我认为不是正如你所说的“完全不相关”。只是一种简单的方法来解释为什么差异以抽象的方式存在
    • 绝对无关紧要。它们的区别不在于是否入栈,而是引用类型和值类型有不同的语义和定义的行为:一个有引用语义(新值引用旧值)值),其他复制语义(新值被克隆)。无论如何,这不是这个 OP 中的问题,实际问题是 object 类型上的 == 将进行参考比较,而理论上您可以在装箱值类型上调用 Equals 并获得正确输出(虽然 CLR 和 C# 不支持定义为装箱值类型的变量)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    • 2016-05-18
    • 2012-07-18
    相关资源
    最近更新 更多