来源: 使用c#强大的表达式树实现对象的深克隆 – a1010 – 博客园
一、表达式树的基本概念
表达式树是一个以树状结构表示的表达式,其中每个节点都代表表达式的一部分。例如,一个算术表达式 a + b
可以被表示为一个树,其中根节点是加法运算符,它的两个子节点分别是 a
和 b
。在 LINQ(语言集成查询)中,表达式树使得能够将 C# 中的查询转换成其他形式的查询,比如 SQL 查询。这样,同样的查询逻辑可以用于不同类型的数据源,如数据库、XML 文件等。由于表达式树可以在运行时创建和修改,同样的它们非常适合需要根据运行时数据动态生成或改变代码逻辑的场景。这对于需要重复执行的逻辑(比如本文提到的深克隆)是非常有用的,因为它们可以被优化和缓存,从而提高效率。
二、创建和使用表达式树
在 C# 中,我们可以通过 System.Linq.Expressions
命名空间中的类来创建和操作表达式树。以下是一个创建简单表达式树的示例:
// 创建一个表达式树表示 a + b ParameterExpression a = Expression.Parameter(typeof(int), "a"); ParameterExpression b = Expression.Parameter(typeof(int), "b"); BinaryExpression body = Expression.Add(a, b); // 编译表达式树为可执行代码 var add = Expression.Lambda<Func<int, int, int>>(body, a, b).Compile(); // 使用表达式 Console.WriteLine(add(1, 2)); // 输出 3
当我们定义了一个类型后,我们可以通过一个匿名委托进行值拷贝来实现深克隆:
//自定义类型 public class TestDto { public int Id { get; set; } public string Name { get; set; } } //匿名委托 Func<TestDto, TestDto> deepCopy = x => new TestDto() { Id = x.Id, Name = x.Name }; //使用它 var a =new TestDto(){//赋值}; var b = deepCopy(a);//实现深克隆
那么想要自动化的创建这一匿名委托就会用到表达式树,通过自动化的方式来实现匿名委托的自动化创建,这样就可以实现复杂的自动化表达式创建从而不必依赖反射、序列化/反序列化等等比较消耗性能的方式来实现。核心的业务逻辑部分如下:首先我们需要知道表达式树通过反射来遍历对象的属性,来实现x = old.x这样的赋值操作。而对于不同的属性比如数组、字典、值类型、自定义类、字符串,其赋值方案是不同的,简单的值类型和字符串我们可以直接通过=赋值,因为这两者的赋值都是“深”克隆。也就是赋值后的变量修改不会影响原始变量。而复杂的字典、数组、对象如果使用=赋值,则只会得到对象的引用,所以针对不同的情况需要不同的处理。
首先我们需要定义一个接口ICloneHandler,针对不同情况使用继承该接口的处理类来处理:
interface ICloneHandler { bool CanHandle(Type type);//是否可以处理当前类型 Expression CreateCloneExpression(Expression original);//生成针对当前类型的表达式树 }
接着我们定义一个扩展类和扩展函数,用于处理深拷贝:
public static class DeepCloneExtension { //创建一个线程安全的缓存字典,复用表达式树 private static readonly ConcurrentDictionary<Type, Delegate> cloneDelegateCache = new ConcurrentDictionary<Type, Delegate>(); //定义所有可处理的类型,通过策略模式实现了可扩展 private static readonly List<ICloneHandler> handlers = new List<ICloneHandler> { //在此处添加自定义的类型处理器 }; /// <summary> /// 深克隆函数 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="original"></param> /// <returns></returns> public static T DeepClone<T>(this T original) { if (original == null) return default; // 获取或创建克隆表达式 var cloneFunc = (Func<T, T>)cloneDelegateCache.GetOrAdd(typeof(T), t => CreateCloneExpression<T>().Compile()); //调用表达式,返回结果 return cloneFunc(original); } /// <summary> /// 构建表达式树的主体逻辑 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> private static Expression<Func<T, T>> CreateCloneExpression<T>() { //反射获取类型 var type = typeof(T); // 创建一个类型为T的参数表达式 'x' var parameterExpression = Expression.Parameter(type, "x"); // 创建一个成员绑定列表,用于稍后存放属性绑定 var bindings = new List<MemberBinding>(); // 遍历类型T的所有属性,选择可读写的属性 foreach (var property in type.GetProperties().Where(prop => prop.CanRead && prop.CanWrite)) { // 获取原始属性值的表达式 var originalValue = Expression.Property(parameterExpression, property); // 初始化一个表达式用于存放可能处理过的属性值 Expression valueExpression = null; // 标记是否已经处理过此属性 bool handled = false; // 遍历所有处理器,查找可以处理当前属性类型的处理器 foreach (var handler in handlers) { // 如果找到合适的处理器,使用它来创建克隆表达式 if (handler.CanHandle(property.PropertyType)) { valueExpression = handler.CreateCloneExpression(originalValue); handled = true; break; } } // 如果没有找到处理器,则使用原始属性值 if (!handled) { valueExpression = originalValue; } // 创建属性的绑定 var binding = Expression.Bind(property, valueExpression); // 将绑定添加到绑定列表中 bindings.Add(binding); } // 使用所有的属性绑定来初始化一个新的T类型的对象 var memberInitExpression = Expression.MemberInit(Expression.New(type), bindings); // 创建并返回一个表达式树,它表示从输入参数 'x' 到新对象的转换 return Expression.Lambda<Func<T, T>>(memberInitExpression, parameterExpression); } }
接下来我们就可以添加一些常见的类型处理器:
数组处理:
class ArrayCloneHandler : ICloneHandler { Type elementType; public bool CanHandle(Type type) { //数组类型要特殊处理获取其内部类型 this.elementType = type.GetElementType(); return type.IsArray; } public Expression CreateCloneExpression(Expression original) { //值类型或字符串,通过值类型数组赋值 if (elementType.IsValueType || elementType == typeof(string)) { return Expression.Call(GetType().GetMethod(nameof(DuplicateArray), BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(elementType), original); } //否则使用引用类型赋值 else { var arrayCloneMethod = GetType().GetMethod(nameof(CloneArray), BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(elementType); return Expression.Call(arrayCloneMethod, original); } } //引用类型数组赋值 static T[] CloneArray<T>(T[] originalArray) where T : class, new() { if (originalArray == null) return null; var length = originalArray.Length; var clonedArray = new T[length]; for (int i = 0; i < length; i++) { clonedArray[i] = DeepClone(originalArray[i]);//调用该类型的深克隆表达式 } return clonedArray; } //值类型数组赋值 static T[] DuplicateArray<T>(T[] originalArray) { if (originalArray == null) return null; T[] clonedArray = new T[originalArray.Length]; Array.Copy(originalArray, clonedArray, originalArray.Length); return clonedArray; } }
自定义类型处理(其实就是调用该类型的深克隆):
class ClassCloneHandler : ICloneHandler { Type elementType; public bool CanHandle(Type type) { this.elementType = type; return type.IsClass && type != typeof(string); } public Expression CreateCloneExpression(Expression original) { var deepCloneMethod = typeof(DeepCloneExtension).GetMethod(nameof(DeepClone), BindingFlags.Public | BindingFlags.Static).MakeGenericMethod(elementType); return Expression.Call(deepCloneMethod, original); } }
接着我们就可以在之前的DeepCloneExtension中添加这些handles
private static readonly List<ICloneHandler> handlers = new List<ICloneHandler> { new ArrayCloneHandler(),//数组 new DictionaryCloneHandler(),//字典 new ClassCloneHandler()//类 ... };
最后我们可以通过简单的进行调用就可以实现深克隆了
var a = new TestDto() { Id = 1, Name = "小明", Child = new TestDto() { Id = 2, Name = "小红" }, Record = new Dictionary<string, int>() { { "1年级", 1 }, { "2年级", 2 } }, Scores = [100, 95] }; var b = a.DeepClone();
总之,C# 的表达式树提供了一个强大的机制,可以将代码以数据结构的形式表示出来,使得代码可以在运行时进行检查、修改或执行。这为动态查询生成、代码优化和动态编程提供了很多可能性。