使用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标识,这里就不介绍表达式目录树是什么了,有兴趣可以自行百度搜索,网上资料还是很多的. 这里主要分享的是如何动态构建表达式目录树. 构建表达式目录树的代码挺简 ...
随机推荐
- 一篇文章了解CI/CD管道全流程
从CI/CD过程开始,包含所有阶段并负责创建自动化和无缝的软件交付的一系列步骤称为CI/CD管道工作流.使用CI/CD管道,软件发布工件可以从代码提交阶段到测试.构建.部署和生产阶段在管道中移动和前进 ...
- LeetCode 3093. 最长公共后缀查询 (二分法)
LeetCode 3093. 最长公共后缀查询 1 题目描述 给你两个字符串数组 wordsContainer 和 wordsQuery . 对于每个 wordsQuery[i] ,你需要从 word ...
- HAProxy适配openGauss使用指导书
一.HAProxy 简介 HAProxy 是一个开源的项目,其代码托管在 Github 上,代码链接如下:HAProxy 代码链接. HAProxy 提供高可用性.负载均衡以及基于 TCP 和 HTT ...
- 关于集群节点timeline不一致的处理方式
关于集群节点 timeline 不一致的处理方式 本文出处:https://www.modb.pro/db/400223 在 PostgreSQL/MogDB/openGauss 数据库日常维护过程中 ...
- 通过 API 快速创建 AlertManager silence
概述 通常我们要 silence 某个 AlertManager 的 alert 时,需要通过 UI 界面操作,如下图: 效率有点低,而且不够自动化,那么是否可以有一种办法快速创建 AlertMana ...
- Android 开发入门(4)
0x06 中级控件 (1)图形绘制 a. 图形 Drawable Drawable 类型包括图片.色块.画板.背景 drawable 目录一般保存描述性 XML 文件,具有具体分辨率的 drawabl ...
- 国内十大活跃报表 BI 产品深度点评
目前国内市场上的报表 BI 工具琳琅满目,看起来也各有特点,这给选型工作带来了一些困扰,本文就一些较活跃的报表 BI 产品进行点评,对于不太熟悉这些产品和技术的同学,可作为参考资料. 这里选了十个产品 ...
- sql 语句系列(多表之链二)[八百章之第四章]
从多个表中返回缺失值 比如说查询每个员工的部门,且查看部门的所有员工. 这里考虑一种情况就是可能有部门没有员工,同样有些员工还没有分配部门. 解析使用 full outer join. select ...
- Detectron2环境配置+Ubantu+CUDA10.1+pytorch1.7.0
Detectron2环境配置 1.创建detectron2 conda环境 conda create -n detectron2 python=3.7 2.激活detectron2 conda环境 c ...
- 【知识点】如何快速开发、部署 Serverless 应用?
简介: 本文将详细介绍如何开发和部署 Serverless 应用,并通过阿里云函数计算控制台与开发者工具 Serverless Devs 进行应用的初始化.部署:最后分享应用的调试,通过科学发布.可观 ...