- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- 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#强大的表达式树实现对象的深克隆的文章就讲到这里了,如果你想了解更多关于使用c#强大的表达式树实现对象的深克隆的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我的一位教授给了我们一些考试练习题,其中一个问题类似于下面(伪代码): a.setColor(blue); b.setColor(red); a = b; b.setColor(purple); b
我似乎经常使用这个测试 if( object && object !== "null" && object !== "undefined" ){ doSomething(); } 在对象上,我
C# Object/object 是值类型还是引用类型? 我检查过它们可以保留引用,但是这个引用不能用于更改对象。 using System; class MyClass { public s
我在通过 AJAX 发送 json 时遇到问题。 var data = [{"name": "Will", "surname": "Smith", "age": "40"},{"name": "Wil
当我尝试访问我的 View 中的对象 {{result}} 时(我从 Express js 服务器发送该对象),它只显示 [object][object]有谁知道如何获取 JSON 格式的值吗? 这是
我有不同类型的数据(可能是字符串、整数......)。这是一个简单的例子: public static void main(String[] args) { before("one"); }
嗨,我是 json 和 javascript 的新手。 我在这个网站找到了使用json数据作为表格的方法。 我很好奇为什么当我尝试使用 json 数据作为表时,我得到 [Object,Object]
已关闭。此问题需要 debugging details 。目前不接受答案。 编辑问题以包含 desired behavior, a specific problem or error, and the
我听别人说 null == object 比 object == null check 例如: void m1(Object obj ) { if(null == obj) // Is thi
Match 对象 提供了对正则表达式匹配的只读属性的访问。 说明 Match 对象只能通过 RegExp 对象的 Execute 方法来创建,该方法实际上返回了 Match 对象的集合。所有的
Class 对象 使用 Class 语句创建的对象。提供了对类的各种事件的访问。 说明 不允许显式地将一个变量声明为 Class 类型。在 VBScript 的上下文中,“类对象”一词指的是用
Folder 对象 提供对文件夹所有属性的访问。 说明 以下代码举例说明如何获得 Folder 对象并查看它的属性: Function ShowDateCreated(f
File 对象 提供对文件的所有属性的访问。 说明 以下代码举例说明如何获得一个 File 对象并查看它的属性: Function ShowDateCreated(fil
Drive 对象 提供对磁盘驱动器或网络共享的属性的访问。 说明 以下代码举例说明如何使用 Drive 对象访问驱动器的属性: Function ShowFreeSpac
FileSystemObject 对象 提供对计算机文件系统的访问。 说明 以下代码举例说明如何使用 FileSystemObject 对象返回一个 TextStream 对象,此对象可以被读
我是 javascript OOP 的新手,我认为这是一个相对基本的问题,但我无法通过搜索网络找到任何帮助。我是否遗漏了什么,或者我只是以错误的方式解决了这个问题? 这是我的示例代码: functio
我可以很容易地创造出很多不同的对象。例如像这样: var myObject = { myFunction: function () { return ""; } };
function Person(fname, lname) { this.fname = fname, this.lname = lname, this.getName = function()
任何人都可以向我解释为什么下面的代码给出 (object, Object) 吗? (console.log(dope) 给出了它应该的内容,但在 JSON.stringify 和 JSON.parse
我正在尝试完成散点图 exercise来自免费代码营。然而,我现在只自己学习了 d3 几个小时,在遵循 lynda.com 的教程后,我一直在尝试确定如何在工具提示中显示特定数据。 This code
我是一名优秀的程序员,十分优秀!