×

C#两个 List(T为类)使用 Contains 比较元素的方法

独孤求败 独孤求败 发表于2026-03-23 14:59:30 浏览23 评论0

抢沙发发表评论

核心问题List<T> 中的 T 为引用类型时,Contains() 默认比较的是引用地址而非对象内容,导致即使属性值完全相同的对象也会被判定为"不相等"。


🔍 问题背景

// 两个内容相同但引用不同的对象
var user1 = new UserInfo { UserName = "aaa", Age = 20 };
var user2 = new UserInfo { UserName = "aaa", Age = 20 };

Console.WriteLine(user1 == user2);           // False(引用比较)
Console.WriteLine(list.Contains(user2));     // False(默认行为)

根本原因Contains() 方法内部调用的是 Equals() 方法,而引用类型默认实现的是引用相等性比较。


✅ 解决方案:重写 Equals 和 GetHashCode

让类实现 IEquatable<T> 接口,并重写相关方法,实现基于值的相等性判断

public classUserInfo : IEquatable<UserInfo>
{
    publicstring UserName { getset; }
    publicint Age { getset; }

    // 🔹 重写 Object.Equals(object)
    public override bool Equals(object obj)
    {
        if (obj == nullreturnfalse;
        if (obj is UserInfo usr)
            return Equals(usr);
        returnfalse;
    }

    // 🔹 重写 GetHashCode(⚠️ 必须与 Equals 配套重写)
    public override int GetHashCode()
    {
        // 推荐方式:使用 HashCode.Combine(.NET Core 2.1+)
        // return HashCode.Combine(UserName, Age);
        
        // 兼容方式:异或组合(注意空值处理)
        return (UserName?.GetHashCode() ?? 0) ^ Age.GetHashCode();
    }

    // 🔹 实现 IEquatable<T>.Equals(T)
    public bool Equals(UserInfo other)
    {
        if (other == nullreturnfalse;
        return UserName == other.UserName && Age == other.Age;
    }
}

⚠️ 重要提醒

  1. GetHashCode() 必须与 Equals() 逻辑一致:相等的对象必须返回相同的哈希码
  2. 注意 UserName 可能为 null,需做空值保护
  3. 若用于 HashSetDictionary 等哈希集合,GetHashCode 的实现尤为关键

🧪 使用示例:对比两个 List

static void Main(string[] args)
{
    var users1 = new List<UserInfo>
    {
        new UserInfo { UserName = "aaa", Age = 20 },
        new UserInfo { UserName = "bbb", Age = 30 },
    };

    var users2 = new List<UserInfo>
    {
        new UserInfo { UserName = "aaa", Age = 20 },
        new UserInfo { UserName = "bbb", Age = 40 },  // Age 不同
    };

    // 找出 users1 中有、users2 中没有的元素
    users1.ForEach(m =>
    {
        if (!users2.Contains(m))
            Console.WriteLine($"users1 有,users2 无: {m.UserName},{m.Age}");
    });
    // 输出: bbb,30

    // 找出 users2 中有、users1 中没有的元素
    users2.ForEach(m =>
    {
        if (!users1.Contains(m))
            Console.WriteLine($"users2 有,users1 无: {m.UserName},{m.Age}");
    });
    // 输出: bbb,40

    Console.ReadKey();
}

💡 进阶建议(可选方案)

方案
适用场景
优点
注意事项
重写 Equals/GetHashCode
通用、需频繁比较
一劳永逸,支持所有集合
需确保逻辑严谨
实现 IEqualityComparer
临时/多种比较策略
灵活,不修改原类
每次使用需传入比较器
使用 LINQ + Select + Join
仅需比较部分字段
简洁直观
性能略低,可读性依赖写法
.NET 6+ record 类型
纯数据模型
自动实现值相等
仅适用于不可变/简单场景

📌 示例:使用自定义比较器(不修改原类)

public classUserInfoComparer : IEqualityComparer<UserInfo>
{
    public bool Equals(UserInfo x, UserInfo y)
    {
        return x?.UserName == y?.UserName && x?.Age == y?.Age;
    }

    public int GetHashCode(UserInfo obj)
    {
        return (obj.UserName?.GetHashCode() ?? 0) ^ obj.Age.GetHashCode();
    }
}

// 使用方式:
if (!users2.Contains(m, new UserInfoComparer())) { ... }

总结:当 List<T> 的元素为自定义引用类型时,若需基于内容而非引用进行 ContainsRemoveDistinct 等操作,必须重写 Equals 和 GetHashCode,或提供自定义 IEqualityComparer<T>。这是 C# 集合操作中常见且关键的实践点。

群贤毕至

访客