一、表示式樹的基本概念
表示式樹是一個以樹狀結構表示的表示式,其中每個節點都代表表示式的一部分。例如,一個算術表示式 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# 的表示式樹提供了一個強大的機制,可以將程式碼以資料結構的形式表示出來,使得程式碼可以在執行時進行檢查、修改或執行。這為動態查詢生成、程式碼最佳化和動態程式設計提供了很多可能性。