如何用LINQ连接不同对象列表生成指定新列表?有无非Concat+包装类方案?
这是个典型的跨集合关联查询场景,用LINQ的Join方法就能轻松搞定,它专门针对这种基于键匹配的场景做了优化,内部会用哈希表降低查询复杂度,性能比嵌套循环好很多。下面是几种不用自定义包装类的实现方式:
1. 匿名类型(当前上下文直接用)
如果只在当前方法里使用结果,匿名类型是最省事的选择——不用额外写类,语法也简洁:
var output = objAs.Join( objBs, a => a.ObjBId, // ObjA的关联键 b => b.Id, // ObjB的关联键 (a, b) => new { ObjAId = a.Id, ObjBTitle = b.Title } // 映射需要的字段 ).ToList();
唯一的限制是匿名类型不能跨方法传递,只能在当前代码块里用。
2. ValueTuple(跨方法友好,C#7.0+支持)
如果需要把结果传到其他方法,又不想写包装类,ValueTuple完美适配——它是值类型,还能给字段命名,可读性拉满:
var output = objAs.Join( objBs, a => a.ObjBId, b => b.Id, (a, b) => (ObjAId: a.Id, ObjBTitle: b.Title) ).ToList();
返回的List<(int ObjAId, string ObjBTitle)>可以直接给其他方法用,调用方一眼就能看懂字段含义。
3. Tuple(兼容旧版C#)
如果项目还在用C#7.0之前的版本,Tuple是传统替代方案,不过它的字段只能用Item1、Item2访问,可读性差一点:
var output = objAs.Join( objBs, a => a.ObjBId, b => b.Id, (a, b) => Tuple.Create(a.Id, b.Title) ).ToList();
取值的时候要用item.Item1(对应ObjA的Id)和item.Item2(对应ObjB的Title)。
先说明下:Concat本来就不适合这个场景——它只是简单合并两个集合,根本做不到基于键的关联,所以你排除它是完全合理的。下面是两种不用包装类的替代实现:
1. Select + FirstOrDefault(适合小数据量)
如果你的数据集很小,性能要求不高,可以直接遍历ObjA,逐个匹配ObjB:
var output = objAs.Select(a => new { a.Id, Title = objBs.FirstOrDefault(b => b.Id == a.ObjBId)?.Title }).ToList();
⚠️ 注意:这种方式是嵌套循环逻辑,时间复杂度O(n*m),数据量大的时候性能会很差,只推荐小数据集用。
2. 字典映射 + Select(性能接近Join)
先把ObjB转成以Id为键的字典,再用Select做匹配,查询效率是O(1),整体性能和Join差不多,逻辑也更直观:
var objBDict = objBs.ToDictionary(b => b.Id, b => b.Title); var output = objAs.Select(a => new { a.Id, Title = objBDict.TryGetValue(a.ObjBId, out var title) ? title : null }).ToList();
这种方式适合数据量较大的场景,而且手动控制映射逻辑,灵活性更高。
- 追求性能和标准写法,优先用
Join配合匿名类型/ValueTuple; - 小数据量可以用
Select+FirstOrDefault快速实现; - 想手动控制关联逻辑且兼顾性能,字典+
Select是不错的选择; - 以上所有方案都不需要自定义包装类,完全满足你的需求。
内容的提问来源于stack exchange,提问作者Bhav

