使用c#强大的表达式树实现对象的深克隆
一、表达式树的基本概念
表达式树是一个以树状结构表示的表达式,其中每个节点都代表表达式的一部分。例如,一个算术表达式 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# 的表达式树提供了一个强大的机制,可以将代码以数据结构的形式表示出来,使得代码可以在运行时进行检查、修改或执行。这为动态查询生成、代码优化和动态编程提供了很多可能性。
使用c#强大的表达式树实现对象的深克隆的更多相关文章
- 干货!表达式树解析"框架"(2)
最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定 ...
- 表达式树解析"框架"
干货!表达式树解析"框架"(2) 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定基础的开发人员才可轻松阅读,如果有难以理解的地方可以跟帖询问,但我也不一定能回 ...
- Lambda表达式和Lambda表达式树
LINQ的基本功能就是创建操作管道,以及这些操作需要的任何状态. 为了富有效率的使用数据库和其他查询引擎,我们需要一种不同的方式表示管道中的各个操作.即把代码当作可在编程中进行检查的数据. Lambd ...
- C#3.0新特性:隐式类型、扩展方法、自动实现属性,对象/集合初始值设定、匿名类型、Lambda,Linq,表达式树、可选参数与命名参数
一.隐式类型var 从 Visual C# 3.0 开始,在方法范围中声明的变量可以具有隐式类型var.隐式类型可以替代任何类型,编译器自动推断类型. 1.var类型的局部变量必须赋予初始值,包括匿名 ...
- C# 快速高效率复制对象另一种方式 表达式树
1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } publi ...
- 【转】对象克隆(C# 快速高效率复制对象另一种方式 表达式树)
原文地址:https://www.cnblogs.com/lsgsanxiao/p/8205096.html 1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: p ...
- 对象克隆(C# 快速高效率复制对象另一种方式 表达式树转)
1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } publi ...
- 【转载】C# 快速高效率复制对象另一种方式 表达式树
1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } publi ...
- C# 高性能对象映射(表达式树实现)
前言 上篇简单实现了对象映射,针对数组,集合,嵌套类并没有给出实现,这一篇继续完善细节. 开源对象映射类库映射分析 1.AutoMapper 实现原理:主要通过表达式树Api 实现对象映射 优点: . ...
- c# 表达式目录树拷贝对象(根据对象类型动态生成表达式目录树)
表达式目录树,在C#中用Expression标识,这里就不介绍表达式目录树是什么了,有兴趣可以自行百度搜索,网上资料还是很多的. 这里主要分享的是如何动态构建表达式目录树. 构建表达式目录树的代码挺简 ...
随机推荐
- Refresh 重构(Refactor)
最近在闲暇之余重(第)温(一..次)此书, 首先能感受到的, 无论你是新程序员还是老程序员, 这本书都已经不具备太多的可读性了. 由于本书成书年代久远, 那个时候软件行业还不够发达, 面向对象还没有被 ...
- #dp#洛谷 2679 子串
题目 有两个仅包含小写英文字母的字符串 \(A\) 和 \(B\). 现在要从字符串 \(A\) 中取出 \(k\) 个互不重叠的非空子串,然后把这 \(k\) 个子串按照其在字符串 \(A\) 中出 ...
- ArkUI中的线程和看门狗机制
一.前言 本文主要分析ArkUI中涉及的线程和看门狗机制. 二.ArkUI中的线程 应用Ability首次创建界面的流程大致如下: 说明: • AceContainer是一个容器类,由前端.任务执行器 ...
- 灵活配置 Spring 集合:List、Set、Map、Properties 详解
使用<property>标签的value属性配置原始数据类型和ref属性配置对象引用的方式来定义Bean配置文件.这两种情况都涉及将单一值传递给Bean 那么如果您想传递多个值,例如Jav ...
- 深入了解图片Base64编码
title: 深入了解图片Base64编码 date: 2024/4/8 10:03:22 updated: 2024/4/8 10:03:22 tags: Base64编码 图片转换 HTTP请求 ...
- Qt调用动态库的三种方式(linux)
本文章主要记录Qt在linux电脑上调用so库的三种调用方式 方式一:静态加载so库 方式二:动态加载so库(QLibrary) 方式三:动态加载so库(dlopen) 其他: 封装的so库叫做: ...
- 一步一步实现 .NET 8 部署到 Docker
一.前言 本文仅针对操作系统为 CentOS 8 的环境下部署方法进行讲述.如有需要,后续将在其他文章中进行其他系统下的部署方式讲解. 二.准备工作 确保服务器已安装 docker. 可以通过命令 d ...
- 面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这
写在开头 在很多的面经中都看到过提问 CountDownLatch 的问题,正好我们最近也在梳理学习AQS(抽象队列同步器),而CountDownLatch又是其中典型的代表,我们今天就继续来学一下这 ...
- Top 20 OpenSSH Server Best Security Practices
Top 20 OpenSSH Server Best Security Practices by NIX Craft on July 24, 2009 · 139 comments· LAST UPD ...
- docker 应用篇————nginx 例子[六]
前言 简单整理一下nginx 例子. 正文 拉取nginx 镜像. docker pull nginx 那么会拉取nginx:latest 这个. 如果需要其他的,可以去官网查询一下. 2.docke ...