首页 > 编程语言 >使用c#强大的表达式树实现对象的深克隆

使用c#强大的表达式树实现对象的深克隆

时间:2024-05-15 11:11:30浏览次数:18  
标签:return 克隆 c# type var new Expression 表达式

使用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# 的表达式树提供了一个强大的机制,可以将代码以数据结构的形式表示出来,使得代码可以在运行时进行检查、修改或执行。这为动态查询生成、代码优化和动态编程提供了很多可能性。

标签:return,克隆,c#,type,var,new,Expression,表达式
From: https://www.cnblogs.com/sexintercourse/p/18193438

相关文章

  • MAC下的homebrew安装及镜像调整
    一、安装未记录 二、镜像调整cd"$(brew--repo)"gitremoteset-urloriginhttps://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git#onlydothisbelowcd"$(brew--repo)/Library/Taps/homebrew/homebrew-core"gitremoteset-urloriginhttps://mi......
  • K8S之yaml 文件详解pod、deployment、service(转)
    原文:https://blog.csdn.net/footless_bird/article/details/125946101作者:墨鸦_Cormorant来源:CSDN K8S中的yaml文件yaml语法学习 Kubernetes支持YAML和JSON格式管理资源对象 JSON格式:主要用于api接口之间消息的传递 YAML格式:用于配置和管理,YAML是一种简......
  • PLC的一些基础介绍
    1、W点和D点D点信号(DataPoints):数据存储:D点通常指的是PLC中的“数据”存储区域,用于存储各种类型的数据,如整数、浮点数、字符串等。变量使用:在PLC程序中,D点可以作为变量使用,用于存储中间结果、计数器值、定时器值等。寻址方式:D点通常有连续的地址编号,如D0、D1、D2...,或者按照数......
  • [C#] [WPF] 在MVVM中实现拖拽功能 - 入门
    拖拽功能是使用频率较高的一种交互,用传统方法去写很简单,但是在mvvm规则下,也没必要弄得很麻烦我的入门案例就选择了使用mvvm重写tutorialspoint-Interaction里的调色盘案例,view如下MainWindow.xaml这里的重点是控件要允许拖拽以及对应的事件目标控件,填充色绑定,......
  • Unrecognized option: --add-opens java.base/java.lang=ALL-UNNAMED
    由于Java中有关反射相关的功能自从JDK9就开始进行了限制,因此如果还想继续在JDK9以及更高的版本中使用反射相关的功能,需要添加JVM启动参数,--add-opensjava.base/java.lang=ALL-UNNAMED。由于现在的项目都是用Docker部署的,所以紧接着在DockerFile中添加了这个JVM启动参数,如下......
  • Percona XtraBackup全备与增量备份脚本
    1、全备脚本#!/bin/bashBAK_DIR_ROOT="/mysql/backup"MYSQL_USERNAME="root"MYSQL_PASSWORD="root"MYSQL_CNF="/mysql/data/3306/my.cnf"BAK_FULL_DIR=$BAK_DIR_ROOT/mysqlbackup`(date+%Y%m%d)`/mysqlfullbackupBAK_LOG="......
  • Cisco Identity Services Engine (ISE) 3.3 Patch 2 - 基于身份的网络访问控制和策略
    CiscoIdentityServicesEngine(ISE)3.3Patch2-基于身份的网络访问控制和策略实施系统思科身份服务引擎(ISE)-下一代NAC解决方案请访问原文链接:CiscoIdentityServicesEngine(ISE)3.3Patch2-基于身份的网络访问控制和策略实施系统,查看最新版。原创作品,转载......
  • Oracle Linux 9.4 正式版发布 - Oracle 提供支持 RHEL 兼容发行版
    OracleLinux9.4正式版发布-Oracle提供支持RHEL兼容发行版OracleLinuxwithUnbreakableEnterpriseKernel(UEK)&RedHatcompatiblekernel(RHCK)请访问原文链接:OracleLinux9.4正式版发布-Oracle提供支持RHEL兼容发行版,查看最新版。原创作品,转载请保留出......
  • Rocky Linux 9.4 正式版发布 - RHEL 100% 1:1 兼容免费发行版
    RockyLinux9.4正式版发布-RHEL100%1:1兼容免费发行版RockyLinux由CentOS项目的创始人GregoryKurtzer领导请访问原文链接:RockyLinux9.4正式版发布-RHEL100%1:1兼容免费发行版,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org以社区方式驱动的企......
  • RetryableException: Read timed out executing导致服务假死无响应
    最近发现Plan这个微服务经常会无响应,后来发现是task微服务会调用plan的某个接口,经常报错如下异常,然后timeout后又30秒再次发起重试而这个业务处理时间1分钟-2小时都有可能所以把plan微服务线程池占满导致无响应2024-05-1314:27:00.282N/A,b1f2a3468b14468abef240bfeddd8b95,0......