C#中判断两个 List<T> 的内容是否相等

发布于:2024-11-29 ⋅ 阅读:(11) ⋅ 点赞:(0)

ET实现游戏中邮件系统逻辑思路(服务端)_游戏邮件系统设计-CSDN博客

场景:今天遇到一个BUG,在服务器重启的时候(体验服),玩家之前接收的邮件又重新接收了一次,但是两封邮件的ID是不同的。ID不同说明玩家自身邮件组件向EmailManager组件拉取了两次,同时也说明EmailManager组件在玩家拉取完邮件后没清理保存,于是我先想着在接受邮件之前判断这个邮件是不是已经接收了。


修复BUG的方式很直接暴力,直接用EmailInfo与玩家自身邮件组件做对比,不过我第一时间想到的竟然是   ‘==’ ,又一次被自己气笑了。 

==运算符对于基本数据类型比较值,对于引用类型默认比较引用(内存地址)。 

由于两封邮件内容、时间、奖励等信息相同但是ID不同那只能比较除ID外的内容是否相等。


 引出正题:

1. 使用 SequenceEqual 方法

System.Linq 命名空间中的 Enumerable.SequenceEqual 方法可以用于比较两个序列的内容是否相等。

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        List<int> list1 = new List<int> { 1, 2, 3 };
        List<int> list2 = new List<int> { 1, 2, 3 };
        List<int> list3 = new List<int> { 1, 2, 4 };

        bool areEqual1 = list1.SequenceEqual(list2); // True
        bool areEqual2 = list1.SequenceEqual(list3); // False

        Console.WriteLine($"list1 and list2 are equal: {areEqual1}");
        Console.WriteLine($"list1 and list3 are equal: {areEqual2}");
    }
}

2. 使用 SetEquals 方法

如果你只关心两个集合是否包含相同的元素,而不关心顺序,可以使用 HashSet<T> 的 SetEquals 方法。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> list1 = new List<int> { 1, 2, 3 };
        List<int> list2 = new List<int> { 3, 2, 1 };

        HashSet<int> set1 = new HashSet<int>(list1);
        HashSet<int> set2 = new HashSet<int>(list2);

        bool areEqual = set1.SetEquals(set2); // True,顺序不重要

        Console.WriteLine($"list1 and list2 contain the same elements: {areEqual}");
    }
}

3. 手动比较

如果你需要更复杂的比较逻辑(例如自定义对象),可以手动遍历两个列表并进行比较。

  • 属性 Value:这是一个整型属性,用于存储 MyClass 对象的值。
  • 重写 Equals 方法
    • 该方法用于比较两个 MyClass 对象是否相等。
    • 首先检查传入的对象 obj 是否是 MyClass 的实例。如果是,则比较它们的 Value 属性。
    • 如果 Value 相等,则返回 true,否则返回 false
  • 重写 GetHashCode 方法
    • 该方法返回 Value 属性的哈希码。重写这个方法是为了确保在使用哈希表等数据结构时,MyClass 对象的哈希值是基于其内容的。
using System;
using System.Collections.Generic;

class MyClass
{
    public int Value { get; set; }

    public override bool Equals(object obj)
    {
        if (obj is MyClass other)
        {
            return this.Value == other.Value;
        }
        return false;
    }

    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }
}

class Program
{
    static void Main()
    {
        List<MyClass> list1 = new List<MyClass> { new MyClass { Value = 1 }, new MyClass { Value = 2 } };
        List<MyClass> list2 = new List<MyClass> { new MyClass { Value = 1 }, new MyClass { Value = 2 } };

        bool areEqual = list1.Count == list2.Count && !list1.Except(list2).Any(); // True

        Console.WriteLine($"list1 and list2 are equal: {areEqual}");
    }
}
  • 创建 list1 和 list2
    • list1 和 list2 是两个 List<MyClass>,它们各自包含两个 MyClass 对象,Value 分别为 1 和 2
  • 比较两个列表的内容
    • areEqual 变量的值是通过以下逻辑计算得到的:
      • 首先检查两个列表的元素数量是否相等(list1.Count == list2.Count)。
      • 然后使用 Except 方法找出 list1 中不在 list2 中的元素,如果没有这样的元素(即 !list1.Except(list2).Any() 为 true),则说明两个列表的内容相等。
  • 输出结果
    • 最后,使用 Console.WriteLine 输出两个列表是否相等的结果。

总结

  • 使用 SequenceEqual 是最简单和直接的方法,适用于顺序敏感的比较。
  • 使用 SetEquals 适用于无序比较。
  • 手动比较适用于需要自定义比较逻辑的情况。

其实这个修改BUG的方式是很蠢的,还是要从根源上分析为什么会出现两封一样的邮件。


BUG出现的原因

ET框架新起一个服务及实现服务之间的消息通讯_et startsceneconfig-CSDN博客

先说答案:出现这个BUG的原因是线上版本修改了Data服的配置文件(线上版本最开始的配置和本地文件相同,后来修改了Data服的线上版配置文件导致读取数据不一致) 


EmailManager组件放在了自己起的一个服务器Data服上,在SceneFactory添加组件的时候生成的ID和Data服StartSceneConfig@s.xlsx文件配置的ID相同。

比如你本地Data服配置的ID是10,那EmailManager组件保存到MongoDB中的ID也是10。因为修改了线上版本的配置文件,线上Data服配置的ID改为8,那EmailManager组件保存到MongoDB中的ID就多了一个8。

所以数据库中就会有ID为10的数据和ID为8的数据 。

体验服在程序启动的时候把ID为10和8的数据同时拉取了,之后运行过程中又是以ID为8的数据进行保存,所以服务器重启的时候又拉了一遍ID为10的数据造成不一致,让逻辑误以为邮件没领又重新拉了一遍。


解决办法 

解决办法就是在读取和保存数据的时候按照配置文件的ID进行动态操作(本地就用10,线上就用8)

public static async ETTask LoadEmailInfo(this EmailManagerComponent self)
{
    //按照配置文件ID进行读取数据: d.Id == self.DomainScene().Id  (self.DomainScene()是Data)
    var EmailManagerList =  await DBManagerComponent.Instance.GetZoneDB(self.DomainZone()).Query<EmailManagerComponent>(d => true && d.Id == self.DomainScene().Id,collection:"EmailManagerComponent");
           
}


//也可以通过条件: self.DomainScene().Name == "Data"  来筛选你要操作的数据