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
相关产品推荐
相关产品推荐

