【c#表示式樹】最完善的表示式樹Expression.Dynamic的玩法

四處觀察發表於2023-04-11

引言

    在我第一次寫部落格的時候,寫的第一篇文章,就是關於表示式樹的,連結: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 Get屬性

    下面是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()();

Get Set Index

  

    先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()();

IsEvent

    判斷屬性是不是事件型別的,第二個是屬性名稱,返回值是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);

Invoke

    這個是用來呼叫委託的,我們定義一個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()();

結尾

    下次再見,歡迎大家加群討論,我是四川觀察,感謝各位看官的支援,謝謝大家,我們們下次再見。

 

相關文章