在C#开发中,对比两个集合的差异是常见需求,比如本地缓存数据和接口返回数据的同步、数据库存量数据和待更新数据的比对等场景,都需要找出两个集合中需要新增、修改、删除的对象。利用LINQ、Lambda表达式和委托的组合,可以简洁高效地实现这个需求,避免繁琐的嵌套循环逻辑。

核心思路设计
要实现两个集合的差异对比,首先需要明确三个核心判断逻辑:
- 新增对象:存在于新集合,但不存在于旧集合的对象
- 删除对象:存在于旧集合,但不存在于新集合的对象
- 修改对象:同时存在于新旧两个集合,但关键属性发生变化,需要更新数据的对象
这里需要用到委托来定义两个对象的对比规则,以及对象属性是否发生变化的判断规则,这样可以让对比逻辑适配不同的业务对象,提升代码的复用性。
基础实体类定义
首先定义一个通用的业务实体类作为示例,实际使用时可以替换为自己的业务对象:
// 示例用户实体类
public class User
{
// 唯一标识
public int Id { get; set; }
// 用户名称
public string Name { get; set; }
// 用户年龄
public int Age { get; set; }
// 用户邮箱
public string Email { get; set; }
}
通用对比方法实现
接下来封装一个通用的集合对比方法,通过委托传入对比键的逻辑和属性变化的判断逻辑,适配不同的业务场景:
using System;
using System.Collections.Generic;
using System.Linq;
public class CollectionCompareHelper
{
/// <summary>
/// 对比两个集合,找出新增、修改、删除的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="oldList">旧集合</param>
/// <param name="newList">新集合</param>
/// <param name="keySelector">对象唯一键选择委托,用于判断两个对象是否为同一个</param>
/// <param name="isModifiedSelector">对象是否修改的判断委托,传入旧对象和新对象,返回是否发生变化</param>
/// <returns>包含新增、修改、删除对象的元组</returns>
public static (List<T> AddList, List<T> UpdateList, List<T> DeleteList) CompareCollections<T>(
List<T> oldList,
List<T> newList,
Func<T, object> keySelector,
Func<T, T, bool> isModifiedSelector)
{
// 初始化结果集合
List<T> addList = new List<T>();
List<T> updateList = new List<T>();
List<T> deleteList = new List<T>();
// 将旧集合转换为字典,键为对象的唯一标识,提升查询效率
var oldDict = oldList.ToDictionary(keySelector);
// 将新集合转换为字典,键为对象的唯一标识
var newDict = newList.ToDictionary(keySelector);
// 找出新增对象:新集合的键不在旧集合字典中
foreach (var newItem in newDict)
{
if (!oldDict.ContainsKey(newItem.Key))
{
addList.Add(newItem.Value);
}
}
// 找出删除对象:旧集合的键不在新集合字典中
foreach (var oldItem in oldDict)
{
if (!newDict.ContainsKey(oldItem.Key))
{
deleteList.Add(oldItem.Value);
}
}
// 找出修改对象:键同时存在于两个字典,且属性发生变化
foreach (var oldItem in oldDict)
{
if (newDict.TryGetValue(oldItem.Key, out T newItem))
{
// 调用委托判断对象是否修改
if (isModifiedSelector(oldItem.Value, newItem))
{
updateList.Add(newItem);
}
}
}
return (addList, updateList, deleteList);
}
}
方法调用示例
接下来使用上面定义的User实体类和对比方法,演示具体的使用方式:
class Program
{
static void Main(string[] args)
{
// 模拟旧集合数据
List<User> oldUsers = new List<User>
{
new User { Id = 1, Name = "张三", Age = 20, Email = "zhangsan@ipipp.com" },
new User { Id = 2, Name = "李四", Age = 25, Email = "lisi@ipipp.com" },
new User { Id = 3, Name = "王五", Age = 30, Email = "wangwu@ipipp.com" }
};
// 模拟新集合数据
List<User> newUsers = new List<User>
{
new User { Id = 1, Name = "张三", Age = 21, Email = "zhangsan@ipipp.com" }, // 年龄修改,需要更新
new User { Id = 2, Name = "李四", Age = 25, Email = "lisi@ipipp.com" }, // 无变化
new User { Id = 4, Name = "赵六", Age = 28, Email = "zhaoliu@ipipp.com" } // 新增对象
// Id=3的对象不存在,属于删除对象
};
// 调用对比方法,传入键选择委托和修改判断委托
var result = CollectionCompareHelper.CompareCollections(
oldUsers,
newUsers,
// 键选择:使用Id作为唯一标识
u => u.Id,
// 修改判断:对比Name、Age、Email三个属性是否变化,有一个变化就认为需要修改
(oldUser, newUser) => oldUser.Name != newUser.Name
|| oldUser.Age != newUser.Age
|| oldUser.Email != newUser.Email
);
// 输出对比结果
Console.WriteLine("新增对象:");
foreach (var item in result.AddList)
{
Console.WriteLine($"Id:{item.Id}, Name:{item.Name}, Age:{item.Age}");
}
Console.WriteLine("n修改对象:");
foreach (var item in result.UpdateList)
{
Console.WriteLine($"Id:{item.Id}, Name:{item.Name}, Age:{item.Age}");
}
Console.WriteLine("n删除对象:");
foreach (var item in result.DeleteList)
{
Console.WriteLine($"Id:{item.Id}, Name:{item.Name}, Age:{item.Age}");
}
}
}
结果说明
运行上面的示例代码,输出结果如下:
- 新增对象:Id为4的赵六,因为旧集合中不存在该Id
- 修改对象:Id为1的张三,年龄从20变为21,属性发生变化
- 删除对象:Id为3的王五,新集合中不存在该Id
这种实现方式的优势在于对比逻辑和具体业务对象解耦,只需要传入不同的键选择委托和修改判断委托,就可以适配各种集合对比场景,不需要为每个业务对象单独编写对比代码。同时内部使用字典存储集合数据,对比时间复杂度为O(n),比嵌套循环的O(n²)效率更高,适合处理数据量较大的集合场景。
注意事项
- 键选择委托返回的对象需要保证可以作为字典的键,建议返回值类型或者不可变的引用类型,避免出现键重复或者键变化的问题
- 修改判断委托的逻辑需要根据实际业务需求调整,比如有些场景只需要判断部分核心属性,不需要对比所有字段
- 如果集合中存在重复的键,
ToDictionary方法会抛出异常,使用前需要确保集合中的键唯一,或者提前做去重处理
LINQLambda_表达式委托集合比较修改时间:2026-06-29 15:33:31