导读:本期聚焦于小伙伴创作的《C#如何重写Equals和GetHashCode实现自定义值类型相等性判断》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#如何重写Equals和GetHashCode实现自定义值类型相等性判断》有用,将其分享出去将是对创作者最好的鼓励。

在C#中,值类型的默认相等性判断逻辑是逐字段比较,但当我们的自定义值类型包含复杂逻辑,或者需要忽略某些字段进行相等性判断时,就需要手动重写Equals和GetHashCode方法来实现符合业务需求的判断规则。

为什么需要重写Equals和GetHashCode

System.ValueType作为所有值类型的基类,已经重写了Equals方法,实现了逐字段比较的逻辑,但这种默认实现存在两个问题。首先是性能问题,默认实现会使用反射来获取类型的所有字段,反射操作会带来额外的性能开销。其次是灵活性问题,如果我们的自定义值类型有特殊的相等判断规则,比如只需要比较部分字段,默认实现就无法满足需求。

而GetHashCode方法的作用是生成对象的哈希值,当值类型实例被用作字典的键或者放入哈希集合时,哈希值的正确性直接影响这些集合的正常工作。如果只重写Equals而不重写GetHashCode,会导致两个相等的实例生成不同的哈希值,进而引发哈希集合的判断错误。

重写Equals方法的核心规则

重写Equals方法需要遵循以下核心规则,保证逻辑的正确性和一致性:

  • 自反性:实例与自身比较必须返回true,即x.Equals(x) == true
  • 对称性:如果x.Equals(y)返回true,那么y.Equals(x)也必须返回true
  • 传递性:如果x.Equals(y)和y.Equals(z)都返回true,那么x.Equals(z)也必须返回true
  • 一致性:如果两个实例的状态没有被修改,多次调用Equals的结果必须一致
  • 与null比较必须返回false:值类型不能为null,所以重写时需要处理传入参数为null的情况

Equals方法的正确重写步骤

我们以自定义的坐标值类型Point为例,需求是两个Point的X和Y字段都相等时,判定两个实例相等,下面是正确的重写实现:

using System;

public struct Point : IEquatable<Point>
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    // 重写Object.Equals方法
    public override bool Equals(object obj)
    {
        // 值类型obj不可能是null,所以只需要判断类型是否匹配
        if (!(obj is Point))
        {
            return false;
        }
        // 转换为Point类型后调用泛型版本的Equals
        return Equals((Point)obj);
    }

    // 实现IEquatable<Point>接口的泛型Equals方法,避免装箱操作
    public bool Equals(Point other)
    {
        // 比较所有需要参与相等性判断的字段
        return X == other.X && Y == other.Y;
    }
}

这里额外实现了IEquatable<Point>接口,定义泛型版本的Equals方法,这样可以避免值类型在比较时发生装箱操作,提升性能。当调用Equals(object)方法时,内部会转换为调用泛型版本,保证逻辑统一。

重写GetHashCode方法的核心规则

GetHashCode的重写需要遵循一个核心原则:如果两个实例通过Equals方法判断相等,那么它们的GetHashCode返回值必须相等。反之则不要求,不同的实例可以返回相同的哈希值,但应该尽量降低哈希冲突的概率。

常见的哈希值计算方式是组合所有参与相等性判断的字段的哈希值,使用乘法和异或运算组合,避免简单的加法导致更多冲突。比如上面的Point类型,GetHashCode可以这样实现:

public struct Point : IEquatable<Point>
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Point))
        {
            return false;
        }
        return Equals((Point)obj);
    }

    public bool Equals(Point other)
    {
        return X == other.X && Y == other.Y;
    }

    // 重写GetHashCode方法
    public override int GetHashCode()
    {
        // 组合X和Y的哈希值,使用质数相乘减少冲突
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + X.GetHashCode();
            hash = hash * 23 + Y.GetHashCode();
            return hash;
        }
    }
}

这里使用unchecked关键字是因为哈希计算过程中可能出现整数溢出,而溢出在哈希计算中是允许的,不需要抛出异常。17和23是常用的质数,用来降低不同字段组合产生相同哈希值的概率。

完整示例与验证

下面是完整的Point类型代码,以及验证相等性判断逻辑的测试代码:

using System;
using System.Collections.Generic;

public struct Point : IEquatable<Point>
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Point))
        {
            return false;
        }
        return Equals((Point)obj);
    }

    public bool Equals(Point other)
    {
        return X == other.X && Y == other.Y;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + X.GetHashCode();
            hash = hash * 23 + Y.GetHashCode();
            return hash;
        }
    }

    // 可选:重写==和!=运算符,让值类型的相等判断更符合使用习惯
    public static bool operator ==(Point left, Point right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(Point left, Point right)
    {
        return !(left == right);
    }
}

class Program
{
    static void Main()
    {
        Point p1 = new Point(1, 2);
        Point p2 = new Point(1, 2);
        Point p3 = new Point(3, 4);

        // 验证Equals判断
        Console.WriteLine(p1.Equals(p2)); // 输出True
        Console.WriteLine(p1.Equals(p3)); // 输出False

        // 验证哈希值一致
        Console.WriteLine(p1.GetHashCode() == p2.GetHashCode()); // 输出True

        // 验证哈希集合的正常使用
        Dictionary<Point, string> dict = new Dictionary<Point, string>();
        dict.Add(p1, "第一个点");
        Console.WriteLine(dict.ContainsKey(p2)); // 输出True,因为p1和p2相等,哈希值也相同
    }
}

可以看到,重写后的Point类型可以正确判断相等性,也可以正常作为字典的键使用。另外,可选重写==和!=运算符,让值类型的相等判断更符合开发者的使用习惯,注意运算符的重写必须和Equals方法的逻辑保持一致。

常见误区提醒

  • 不要只重写Equals而不重写GetHashCode,否则值类型实例作为哈希集合键时会出现逻辑错误
  • GetHashCode中使用的字段必须和Equals中比较的字段完全一致,否则会违反相等实例哈希值必须相等的规则
  • 不要在GetHashCode中引入可变字段,如果值类型的字段是可变的,修改字段后哈希值会变化,导致哈希集合中无法找到该实例
  • 值类型的Equals重写不需要判断obj是否为null,因为值类型本身不能为null,强制转换时如果obj是null会抛出异常,但我们的代码中先判断了类型,所以不会出现这个问题

C#EqualsGetHashCode值类型相等性判断修改时间:2026-07-02 23:18:19

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。