在我第一次寫部落格的時候,寫的第一篇文章,就是關於表示式樹的,連結:https://www.cnblogs.com/1996-Chinese-Chen/p/14987967.html,其中,當時一直沒有研究Expression.Dynamic的使用方法(因為網上找不到資料),就瞭解到是程式執行時動態去構建表示式樹,舉個例子,例如我們需要在我們的查詢條件中去構建他是等於或者不等於,這個時候,雖然我們可以定義等於或者不定於 的BinaryExpression,然後在程式碼中通過switch去進行判斷,使用的是Equal還是NotEqual,這中間還需要我們自己去寫一個switch,如果使用了Dynamic的方法,我們就只需要找到對應的ExpressionType然後傳入建立Binder的方法中,在呼叫Dynamic方法就可以動態的實現,各種判斷操作,或者其他的呼叫方法,靈活度比switch更高,接下來,我們就看看如何使用Expression.Dynamic方法來實現各種操作吧,一下所有程式碼操作需要引入Microsoft.CSharp.RuntimeBinder,nuget搜尋Microsoft.CSharp即可。方便測試,我新建了一個Test的類,下面會用到
public class Test { private List<string> Strings = new List<string>(); public event Action TestEvent; public Test(int a,int b) { A = a; B = b; Strings.Add("1"); Strings.Add("2"); Strings.Add("3"); } public string this[int Index] { get=> Strings[Index]; set=> Strings[Index]=value; } public int A { get; set; } public int B { get; set; } public int Add() { return A+B; } public static int AddOne() { return 15; } }
下面的程式碼實現一個二元運算,首先Dynamic方法是需要CallBinder引數的,而對應的實現有如下的Binder,我們首先需要去建立對應的Binder,二元運算就使用BinaryOperation方法建立,CSharpBinderFlags是一個列舉型別,它用於指定動態繫結操作的行為,裡面可以定義在動態繫結的時候需要執行的一些特殊操作,例如,運算應該在已經檢查的上下文中執行,或者使用Invoke等需要使用的一些特殊操作,或者轉換的時候等等。第二個引數是ExpressionType,標明我們是那一個二元運算,第三個是當前程式碼執行的主體型別 that indicates where this operation is used.即這個指示了這個操作被用在哪些地方。第三個是一個CSharpArgumentInfo集合,是我們建立這個站點的時候需要使用的引數數量,如果是呼叫方法的時候,或者獲取範例屬性的時候,第一個引數是為範例引數,UseCompileTimeType型別是編譯期間確定型別,其中還有IsStaticType,IsRef,IsOUt等各種,供我們使用。
然後我們建立一個dynamic的Expression,傳入binder,返回型別是object,然後傳入需要計算的兩個引數10和1,最後得到委託,執行委託即可。
CallSiteBinder binder = Binder.BinaryOperation( CSharpBinderFlags.None, ExpressionType.LeftShift, typeof(Program), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }); var dynamic = Expression.Dynamic(binder, typeof(object), Expression.Constant(10), Expression.Constant(1)); Func<int> func = Expression.Lambda<Func<int>>(Expression.Convert(dynamic, typeof(int))).Compile(); Console.WriteLine(func());
從上面的Test類看到,我們定義了兩個入參,可能有的人會問了為什麼入參是兩個Binder為什麼定義了三個呢,這是因為,建立性的Binder在建立的時候 引數第一個必須是型別引數,所以此處第一個引數必須是Test的type,然後後面是Static型別的引數,
最後一個引數就是3,呼叫Dynamic,第二個為返回型別的引數,然後傳入對應的引數即可建立物件。
static int A = 5;
var constructorBinder = Binder.InvokeConstructor(CSharpBinderFlags.None, typeof(Program), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType,null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType,null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType,null) }); var createInstance = Expression.Dynamic(constructorBinder, typeof(Test), Expression.Constant(typeof(Test)), Expression.Constant(A), Expression.Constant(3)); var instance = Expression.Lambda<Func<Test>>(createInstance).Compile()();
實體方法,使用InvokeMember,第二個引數是呼叫的方法名稱,第三個引數是引數型別,由於我沒有定義引數所以為null,然後實體方法我們需要定義一個範例引數,在CSharpArgumentInfo定義,然後呼叫Dynamic,返回型別必須是Object,因為這塊扯犢子的是他直接寫死的,如果需要轉只有自己到表示式樹那塊Convert轉,呼叫然後生成委託,返回結果。
var invokeBinder = Binder.InvokeMember( CSharpBinderFlags.None, "Add", null, typeof(Program), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var invokeDynamic=Expression.Dynamic(invokeBinder, typeof(object),Expression.Constant(instance)); var returnVal = Expression.Lambda<Func<object>>(invokeDynamic).Compile()(); Console.WriteLine(returnVal);
大體上沒有區別,在引數型別需要標記為StaticType。傳入的引數不再是範例,而是靜態方法所屬的型別下,可以看到,返回型別必須是Object,我自己在最後Convert了,原始碼中的Binder預設寫死Object
var invokeStaticBinder = Binder.InvokeMember( CSharpBinderFlags.None, "AddOne", null, typeof(Test), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType, null) }); var invokeStaticDynamic = Expression.Dynamic(invokeStaticBinder, typeof(object),Expression.Constant(typeof(Test))); var Val = Expression.Lambda<Func<int>>(Expression.Convert(invokeStaticDynamic,typeof(int))).Compile()(); Console.WriteLine(Val);
將int轉換為Object型別。
var bindConvert = Binder.Convert(CSharpBinderFlags.None,typeof(object),typeof(Program)); var expressConvert = Expression.Dynamic(bindConvert,typeof(object),Expression.Constant(A)); var funcVal=Expression.Lambda<Func<object>>(expressConvert).Compile()();
下面是Set,第二個引數是設定的屬性名稱,引數型別是範例,以及設定的屬性值,最後生成委託,然後呼叫即可。
var bindSet = Binder.SetMember(CSharpBinderFlags.None, "A", typeof(Program), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var setExpress = Expression.Dynamic(bindSet,typeof(void), Expression.Constant(instance),Expression.Constant(100)); var action = Expression.Lambda<Action>(setExpress).Compile(); action();
然後是Get,引數是範例的,然後返回就行了。
var bindGet = Binder.GetMember(CSharpBinderFlags.None, "A", typeof(Program), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var getExpress = Expression.Dynamic(bindGet, typeof(object), Expression.Constant(instance)); var getFunc= Expression.Lambda<Func<object>>(getExpress).Compile()(); Console.WriteLine(getFunc);
一元運算的ExpressionType,引數的定義,Binder和表示式樹繫結,生成委託。
var NegateBinder = Binder.UnaryOperation(CSharpBinderFlags.None,ExpressionType.Negate,typeof(Program),new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }); var NegateExpress = Expression.Dynamic(NegateBinder, typeof(object), Expression.Constant(10)); var NegateVal = Expression.Lambda<Func<object>>(NegateExpress).Compile()();
先Set,第一個引數自變數,第二個為索引,第三個是具體的值,然後表示式樹和Binder繫結,生成委託,呼叫,即可,可以看到上面Test我們定義了一個Index的。
var setIndex = Binder.SetIndex(CSharpBinderFlags.None, typeof(Test), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var setIndexExpress = Expression.Dynamic(setIndex,typeof(void),Expression.Constant(instance),Expression.Constant(1),Expression.Constant("cxd")); var SetIndexaction = Expression.Lambda<Action>(setIndexExpress).Compile(); SetIndexaction();
然後是get,自變數,索引,生成委託,返回索引的值。
var getIndex= Binder.GetIndex(CSharpBinderFlags.None, typeof(Program), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var getIndexExpress = Expression.Dynamic(getIndex, typeof(object), Expression.Constant(instance), Expression.Constant(0)); var getIndexaction = Expression.Lambda<Func<object>>(getIndexExpress).Compile()();
判斷屬性是不是事件型別的,第二個是屬性名稱,返回值是bool。
var isevent = Binder.IsEvent(CSharpBinderFlags.None, "TestEvent", typeof(Program));//換成非Event就不行 var iseventExpress = Expression.Dynamic(isevent,typeof(bool),Expression.Constant(instance)); var res=Expression.Lambda<Func<bool>>(iseventExpress).Compile()(); Console.WriteLine(res);
這個是用來呼叫委託的,我們定義一個Func的委託,可惜的是,返回值還是隻能是object,然後引數引數,然後呼叫委託,就返回了111。
var actions= new Func<object>(()=>111); var invokeOtherBinder = Binder.Invoke(CSharpBinderFlags.None,typeof(Program),new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null), }); var expression = Expression.Dynamic(invokeOtherBinder, typeof(object),Expression.Constant(actions)); var ra=Expression.Lambda<Func<object>>(expression).Compile()();
下次再見,歡迎大家加群討論,我是四川觀察,感謝各位看官的支援,謝謝大家,咱們下次再見。