You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何按对象相等性分组并映射至新模型?

如何正确按Diff对象内容分组转换FileList到FileListV2

你有一个FileList对象,包含多个FileDiff实例,需要将其转换为FileListV2,把属性相同的FileDiff的Name归集到Names数组中。但使用Linq的GroupBy按Included、Excluded、Existing分组时未得到预期结果。

原数据模型代码

var obj = new FileList
{
   FileInfo = new List<FileDiff>()
    {
      new ()
      {
         Name = "test1",
         Included = new Diff<int> {Added = new HashSet<int> {1, 2}},
         Excluded = new Diff<int> {Added = new HashSet<int> {1, 2}},
         Existing = new Diff<int> {Added = new HashSet<int> {1, 2, 3}}
      },
      new ()
      {
        Name = "test1",
        Included = new Diff<int> {Added = new HashSet<int> {3, 4, 5}},
        Excluded = new Diff<int> {Added = new HashSet<int> {11, 222}},
        Existing = new Diff<int> {Added = new HashSet<int>()}
      },
      new ()
      {
         Name = "equal to test1",
         Included = new Diff<int> {Added = new HashSet<int> {1, 2}},
         Excluded = new Diff<int> {Added = new HashSet<int> {1, 2}},
         Existing = new Diff<int> {Added = new HashSet<int> {1, 2, 3}}
      }
   }
};

预期转换结果

var obj = new FileListV2
 {
   FileInfo = new List<FileDiff2>()
   {
    new ()
      {
        Names = new string[] {"test1", "equal to test1"},
        Included = new Diff<int> {Added = new HashSet<int> {1, 2}},
        Excluded = new Diff<int> {Added = new HashSet<int> {1, 2}},
        Existing = new Diff<int> {Added = new HashSet<int> {1, 2, 3}}
     },
    new ()
     {
       Names = new string[] {"test1"},
       Included = new Diff<int> {Added = new HashSet<int> {3, 4, 5}},
       Excluded = new Diff<int> {Added = new HashSet<int> {11, 222}},
       Existing = new Diff<int> {Added = new HashSet<int>()}
     }
    }
};

你尝试的无效代码

var obj2 = obj.FileList
    .GroupBy(f => new
    {
        f.Included,
        f.Excluded,
        f.Existing,
    })
    .Select(g => new FileListV2
    {
        Names = g.Select(f => f.Name).ToArray(),
        Included = g.Key.Included,
        Excluded = g.Key.Excluded,
        Existing = g.Key.Existing,
    });

问题原因

Diff<int>是引用类型,默认情况下,匿名类型的相等比较会检查引用是否相同,而非内容是否一致。即使两个Diff<int>实例的Added集合内容完全一致,只要是不同实例,就会被GroupBy判定为不同的分组键,导致无法正确合并。


解决方案

方案1:重写Diff<T>的Equals和GetHashCode方法

让Diff<T>支持基于内容的相等比较,这样GroupBy就能正确识别内容相同的实例:

public class Diff<T>
{
    public HashSet<T> Added { get; set; } = new HashSet<T>();

    public override bool Equals(object? obj)
    {
        if (obj is not Diff<T> other) return false;
        return Added.SetEquals(other.Added);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var item in Added.OrderBy(x => x))
        {
            hash = hash * 31 + item.GetHashCode();
        }
        return hash;
    }
}

修正原Linq代码(注意obj.FileList改为obj.FileInfo)即可正常工作:

var obj2 = new FileListV2
{
    FileInfo = obj.FileInfo
        .GroupBy(f => new { f.Included, f.Excluded, f.Existing })
        .Select(g => new FileDiff2
        {
            Names = g.Select(f => f.Name).ToArray(),
            Included = g.Key.Included,
            Excluded = g.Key.Excluded,
            Existing = g.Key.Existing
        })
        .ToList()
};

方案2:在分组键中使用值类型集合表示

如果无法修改Diff<T>类,可在GroupBy时将Diff<T>的内容转换为可比较的值类型(如排序后的数组):

var obj2 = new FileListV2
{
    FileInfo = obj.FileInfo
        .GroupBy(f => new
        {
            IncludedAdded = f.Included.Added.OrderBy(x => x).ToArray(),
            ExcludedAdded = f.Excluded.Added.OrderBy(x => x).ToArray(),
            ExistingAdded = f.Existing.Added.OrderBy(x => x).ToArray()
        })
        .Select(g => new FileDiff2
        {
            Names = g.Select(f => f.Name).ToArray(),
            Included = g.First().Included,
            Excluded = g.First().Excluded,
            Existing = g.First().Existing
        })
        .ToList()
};

方案3:自定义比较器传给GroupBy

创建实现IEqualityComparer<FileDiff>的比较器,定义分组的相等规则:

public class FileDiffEqualityComparer : IEqualityComparer<FileDiff>
{
    public bool Equals(FileDiff? x, FileDiff? y)
    {
        if (x == null && y == null) return true;
        if (x == null || y == null) return false;
        
        return x.Included.Added.SetEquals(y.Included.Added)
            && x.Excluded.Added.SetEquals(y.Excluded.Added)
            && x.Existing.Added.SetEquals(y.Existing.Added);
    }

    public int GetHashCode(FileDiff obj)
    {
        int hash = 17;
        hash = hash * 31 + GetHashSetHashCode(obj.Included.Added);
        hash = hash * 31 + GetHashSetHashCode(obj.Excluded.Added);
        hash = hash * 31 + GetHashSetHashCode(obj.Existing.Added);
        return hash;
    }

    private int GetHashSetHashCode<T>(HashSet<T> set)
    {
        int hash = 17;
        foreach (var item in set.OrderBy(x => x))
        {
            hash = hash * 31 + item.GetHashCode();
        }
        return hash;
    }
}

在GroupBy中使用该比较器:

var obj2 = new FileListV2
{
    FileInfo = obj.FileInfo
        .GroupBy(f => f, new FileDiffEqualityComparer())
        .Select(g => new FileDiff2
        {
            Names = g.Select(f => f.Name).ToArray(),
            Included = g.Key.Included,
            Excluded = g.Key.Excluded,
            Existing = g.Key.Existing
        })
        .ToList()
};

内容的提问来源于stack exchange,提问作者user122222

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 13:40:34