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

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

时间:2024-05-11 16:41:37浏览次数:28  
标签:return 克隆 c# type var new Expression 表达式

一、表达式树的基本概念

表达式树是一个以树状结构表示的表达式,其中每个节点都代表表达式的一部分。例如,一个算术表达式 a + b 可以被表示为一个树,其中根节点是加法运算符,它的两个子节点分别是 ab。在 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/gmmy/p/18186750

相关文章

  • Codeforces 832E Vasya and Shifts
    考虑到这个操作实际上就是\(5\)进制的不进位加法,其实也就是\(5\)进制下的异或。同时因为是\(5\)进制,对于\(x\in[1,4]\),\(x\times0,\cdots,x\times4\)刚好可以表示出\(0\sim4\)。于是可以考虑类似\(2\)进制的线性基弄个\(5\)进制的线性基。即令\(w_i\)为......
  • centos7下redis集群部署
    1、环境准备安装redis所需依赖环境#在线:yum-yinstallepel-releasegcc #离线:#下载yuminstall--downloadonly--downloaddir=/home/filesepel-releasegcc#将依赖包放到离线服务器,进入目录cd/home/files#执行安装rpm-Uvh./*.rpm--node......
  • JavaScript 原型链与继承问答(第一天)
    1、在编程语言中,什么叫语法糖在编程语言中,"语法糖"是指一种编程语言的语法功能,它并不改变语言的功能,但可以让代码更易读、更易写。它是一种语法上的简化,让程序员能够用更少的代码表达相同的逻辑。比如es6中,一个对象想复用另外一个对象的所有字段,可以这样constobj2={...obj......
  • CF207C3 Game with Two Trees
    CF207C3GamewithTwoTrees妙到家的树上字符串问题。约定树\(1\):\(t_1\)。树\(2\):\(t_2\)。\(S_{1/2}(i,l)\)为树\(1/2\)上节点\(i\)沿父亲走经过\(l\)​条边所构成的字符串。\(E_{1/2}(u,v)\)为树\(1/2\)上,连接节点\(u,v\)​的边的字符。\(fa_{......
  • ReentrantLock分为公平锁和非公平锁,那底层分别是如何实现的
    ReentrantLock在Java中是通过AbstractQueuedSynchronizer(AQS)框架实现的,它提供了公平锁(FairSync)和非公平锁(NonfairSync)两种模式。这两种锁的实现主要区别在于获取锁的策略。importjava.util.concurrent.locks.ReentrantLock;publicclassReentrantLockDemo{//非公平......
  • el-cascader设置为任意一级选项,去除单选按钮以及点击关闭下拉选择
    1、标签组件:<el-cascaderref="cascaderRef1"popper-class="popper-cascader"@change="handleChangeCascader(cascaderRef1)"></el-cascader>2、给popper-cascader设置样式,在element-ui,scss里编写.popper-cascader.el-cascader-panel......
  • C#委托
    目录C#委托委托是什么?基本语法委托的常见用法总结引用C#委托委托是什么?**委托定义一种类型,该类型封装一个或多个方法(一个或多个方法指向委托实例)。**委托是一种指向方法的引用。它允许您将方法存储在变量中,并像调用普通方法一样调用它们。委托通常用于事件处理和异步编程......
  • 双核、DSPIC33CH128MP203-I/M5 DSPIC33CH128MP203-H/M5 DSPIC33CH128MP203-E/M5数字信
    产品简介dsPIC33CH双核数字信号控制器在单个芯片中集成了两个dsPICDSC内核,一个设计用作主器件,而另一个则设计用作从器件。从内核用于执行专用、时间关键型控制代码,而主内核则用于运行用户界面、系统监测和通信功能以及最终应用的定制。dsPIC33CH器件优化用于高性能数字电源、电......
  • ReentrantLock分为公平锁和非公平锁,那底层分别是如何实现的
    ReentrantLock在Java中是通过AbstractQueuedSynchronizer(AQS)框架实现的,它提供了公平锁(FairSync)和非公平锁(NonfairSync)两种模式。这两种锁的实现主要区别在于获取锁的策略。importjava.util.concurrent.locks.ReentrantLock;publicclassReentrantLockDemo{//非公平......
  • 用友U8 Cloud smartweb2.RPC.d 存在XXE漏洞
    用友U8Cloud漏洞复现1.Cloudsmartweb2.RPC.d存在XXE漏洞漏洞描述用友U8Cloudsmartweb2.RPC.d存在xml外部实体注入漏洞,攻击者可以通过此漏洞读取系统文件,获取敏感信息等。fofa语句:app="用友-U8-Cloud"漏洞:POST/hrss/dorado/smartweb2.RPC.d?__rpc=trueHTTP/1.1Ho......