使用C#表達(dá)式樹(shù)實(shí)現(xiàn)對(duì)象的深克隆(實(shí)例詳解)
一、表達(dá)式樹(shù)的基本概念
表達(dá)式樹(shù)是一個(gè)以樹(shù)狀結(jié)構(gòu)表示的表達(dá)式,其中每個(gè)節(jié)點(diǎn)都代表表達(dá)式的一部分。例如,一個(gè)算術(shù)表達(dá)式 a + b
可以被表示為一個(gè)樹(shù),其中根節(jié)點(diǎn)是加法運(yùn)算符,它的兩個(gè)子節(jié)點(diǎn)分別是 a
和 b
。在 LINQ(語(yǔ)言集成查詢)中,表達(dá)式樹(shù)使得能夠?qū)?C# 中的查詢轉(zhuǎn)換成其他形式的查詢,比如 SQL 查詢。這樣,同樣的查詢邏輯可以用于不同類型的數(shù)據(jù)源,如數(shù)據(jù)庫(kù)、XML 文件等。由于表達(dá)式樹(shù)可以在運(yùn)行時(shí)創(chuàng)建和修改,同樣的它們非常適合需要根據(jù)運(yùn)行時(shí)數(shù)據(jù)動(dòng)態(tài)生成或改變代碼邏輯的場(chǎng)景。這對(duì)于需要重復(fù)執(zhí)行的邏輯(比如本文提到的深克?。┦欠浅S杏玫?,因?yàn)樗鼈兛梢员粌?yōu)化和緩存,從而提高效率。
二、創(chuàng)建和使用表達(dá)式樹(shù)
在 C# 中,我們可以通過(guò) System.Linq.Expressions
命名空間中的類來(lái)創(chuàng)建和操作表達(dá)式樹(shù)。以下是一個(gè)創(chuàng)建簡(jiǎn)單表達(dá)式樹(shù)的示例:
// 創(chuàng)建一個(gè)表達(dá)式樹(shù)表示 a + b ParameterExpression a = Expression.Parameter(typeof(int), "a"); ParameterExpression b = Expression.Parameter(typeof(int), "b"); BinaryExpression body = Expression.Add(a, b); // 編譯表達(dá)式樹(shù)為可執(zhí)行代碼 var add = Expression.Lambda<Func<int, int, int>>(body, a, b).Compile(); // 使用表達(dá)式 Console.WriteLine(add(1, 2)); // 輸出 3
當(dāng)我們定義了一個(gè)類型后,我們可以通過(guò)一個(gè)匿名委托進(jìn)行值拷貝來(lái)實(shí)現(xiàn)深克?。?/p>
//自定義類型 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);//實(shí)現(xiàn)深克隆
那么想要自動(dòng)化的創(chuàng)建這一匿名委托就會(huì)用到表達(dá)式樹(shù),通過(guò)自動(dòng)化的方式來(lái)實(shí)現(xiàn)匿名委托的自動(dòng)化創(chuàng)建,這樣就可以實(shí)現(xiàn)復(fù)雜的自動(dòng)化表達(dá)式創(chuàng)建從而不必依賴反射、序列化/反序列化等等比較消耗性能的方式來(lái)實(shí)現(xiàn)。核心的業(yè)務(wù)邏輯部分如下:首先我們需要知道表達(dá)式樹(shù)通過(guò)反射來(lái)遍歷對(duì)象的屬性,來(lái)實(shí)現(xiàn)x = old.x這樣的賦值操作。而對(duì)于不同的屬性比如數(shù)組、字典、值類型、自定義類、字符串,其賦值方案是不同的,簡(jiǎn)單的值類型和字符串我們可以直接通過(guò)=賦值,因?yàn)檫@兩者的賦值都是“深”克隆。也就是賦值后的變量修改不會(huì)影響原始變量。而復(fù)雜的字典、數(shù)組、對(duì)象如果使用=賦值,則只會(huì)得到對(duì)象的引用,所以針對(duì)不同的情況需要不同的處理。
首先我們需要定義一個(gè)接口ICloneHandler,針對(duì)不同情況使用繼承該接口的處理類來(lái)處理:
interface ICloneHandler { bool CanHandle(Type type);//是否可以處理當(dāng)前類型 Expression CreateCloneExpression(Expression original);//生成針對(duì)當(dāng)前類型的表達(dá)式樹(shù) }
接著我們定義一個(gè)擴(kuò)展類和擴(kuò)展函數(shù),用于處理深拷貝:
public static class DeepCloneExtension { //創(chuàng)建一個(gè)線程安全的緩存字典,復(fù)用表達(dá)式樹(shù) private static readonly ConcurrentDictionary<Type, Delegate> cloneDelegateCache = new ConcurrentDictionary<Type, Delegate>(); //定義所有可處理的類型,通過(guò)策略模式實(shí)現(xiàn)了可擴(kuò)展 private static readonly List<ICloneHandler> handlers = new List<ICloneHandler> { //在此處添加自定義的類型處理器 }; /// <summary> /// 深克隆函數(shù) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="original"></param> /// <returns></returns> public static T DeepClone<T>(this T original) { if (original == null) return default; // 獲取或創(chuàng)建克隆表達(dá)式 var cloneFunc = (Func<T, T>)cloneDelegateCache.GetOrAdd(typeof(T), t => CreateCloneExpression<T>().Compile()); //調(diào)用表達(dá)式,返回結(jié)果 return cloneFunc(original); } /// <summary> /// 構(gòu)建表達(dá)式樹(shù)的主體邏輯 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> private static Expression<Func<T, T>> CreateCloneExpression<T>() { //反射獲取類型 var type = typeof(T); // 創(chuàng)建一個(gè)類型為T的參數(shù)表達(dá)式 'x' var parameterExpression = Expression.Parameter(type, "x"); // 創(chuàng)建一個(gè)成員綁定列表,用于稍后存放屬性綁定 var bindings = new List<MemberBinding>(); // 遍歷類型T的所有屬性,選擇可讀寫(xiě)的屬性 foreach (var property in type.GetProperties().Where(prop => prop.CanRead && prop.CanWrite)) { // 獲取原始屬性值的表達(dá)式 var originalValue = Expression.Property(parameterExpression, property); // 初始化一個(gè)表達(dá)式用于存放可能處理過(guò)的屬性值 Expression valueExpression = null; // 標(biāo)記是否已經(jīng)處理過(guò)此屬性 bool handled = false; // 遍歷所有處理器,查找可以處理當(dāng)前屬性類型的處理器 foreach (var handler in handlers) { // 如果找到合適的處理器,使用它來(lái)創(chuàng)建克隆表達(dá)式 if (handler.CanHandle(property.PropertyType)) { valueExpression = handler.CreateCloneExpression(originalValue); handled = true; break; } } // 如果沒(méi)有找到處理器,則使用原始屬性值 if (!handled) { valueExpression = originalValue; } // 創(chuàng)建屬性的綁定 var binding = Expression.Bind(property, valueExpression); // 將綁定添加到綁定列表中 bindings.Add(binding); } // 使用所有的屬性綁定來(lái)初始化一個(gè)新的T類型的對(duì)象 var memberInitExpression = Expression.MemberInit(Expression.New(type), bindings); // 創(chuàng)建并返回一個(gè)表達(dá)式樹(shù),它表示從輸入?yún)?shù) 'x' 到新對(duì)象的轉(zhuǎn)換 return Expression.Lambda<Func<T, T>>(memberInitExpression, parameterExpression); } }
接下來(lái)我們就可以添加一些常見(jiàn)的類型處理器:
數(shù)組處理:
class ArrayCloneHandler : ICloneHandler { Type elementType; public bool CanHandle(Type type) { //數(shù)組類型要特殊處理獲取其內(nèi)部類型 this.elementType = type.GetElementType(); return type.IsArray; } public Expression CreateCloneExpression(Expression original) { //值類型或字符串,通過(guò)值類型數(shù)組賦值 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); } } //引用類型數(shù)組賦值 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]);//調(diào)用該類型的深克隆表達(dá)式 } return clonedArray; } //值類型數(shù)組賦值 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; } }
自定義類型處理(其實(shí)就是調(diào)用該類型的深克隆):
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(),//數(shù)組 new DictionaryCloneHandler(),//字典 new ClassCloneHandler()//類 ... };
最后我們可以通過(guò)簡(jiǎn)單的進(jìn)行調(diào)用就可以實(shí)現(xiàn)深克隆了
var a = new TestDto() { Id = 1, Name = "小明", Child = new TestDto() { Id = 2, Name = "小紅" }, Record = new Dictionary<string, int>() { { "1年級(jí)", 1 }, { "2年級(jí)", 2 } }, Scores = [100, 95] }; var b = a.DeepClone();
總之,C# 的表達(dá)式樹(shù)提供了一個(gè)強(qiáng)大的機(jī)制,可以將代碼以數(shù)據(jù)結(jié)構(gòu)的形式表示出來(lái),使得代碼可以在運(yùn)行時(shí)進(jìn)行檢查、修改或執(zhí)行。這為動(dòng)態(tài)查詢生成、代碼優(yōu)化和動(dòng)態(tài)編程提供了很多可能性。
到此這篇關(guān)于使用C#強(qiáng)大的表達(dá)式樹(shù)實(shí)現(xiàn)對(duì)象的深克隆的文章就介紹到這了,更多相關(guān)C#對(duì)象的深克隆內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#數(shù)據(jù)結(jié)構(gòu)與算法揭秘二 線性結(jié)構(gòu)
本文中,我們討論了什么是線性結(jié)構(gòu),線性結(jié)構(gòu)有哪些特點(diǎn),并且詳細(xì)介紹了一個(gè)最簡(jiǎn)單線性結(jié)構(gòu)順序表,并且通過(guò)源代碼對(duì)她進(jìn)行一些列的分析,最后還舉了兩個(gè)例子,讓我們更好的理解順序表2012-11-11C#字符串如何提取數(shù)值(帶小數(shù)點(diǎn))
這篇文章主要介紹了C#字符串如何提取數(shù)值問(wèn)題(帶小數(shù)點(diǎn)),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05WindowsForm實(shí)現(xiàn)警告消息框的實(shí)例代碼
這篇文章主要介紹了WindowsForm如何實(shí)現(xiàn)警告消息框,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07C#中Entity Framework常見(jiàn)報(bào)錯(cuò)匯總
給大家總結(jié)了C#中Entity Framework常見(jiàn)報(bào)錯(cuò),以及處理這些錯(cuò)誤的方法,希望能夠?yàn)槟闾峁┑綆椭?/div> 2017-11-11C#條件拼接Expression<Func<T, bool>>的使用
本文主要介紹了C#條件拼接Expression<Func<T, bool>>的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02最新評(píng)論