C#使用表達式樹動態調用方法並實現99乘法表

来源:https://www.cnblogs.com/ckym/archive/2019/10/06/11627622.html
-Advertisement-
Play Games

我們在使用C#編程的時候,經常使用反射來動態調用方法,但有時候需要動態的生成方法,下麵介紹使用表達式樹的方式來自動生成方法,並調用。 首先需要說明什麼是表達式,熟悉Linq的程式猿都用過類似於下麵的代碼:t=>t.Length<=25; 在C#中=>代表這是一個Lambda表達式,它用來對數組進行查 ...


我們在使用C#編程的時候,經常使用反射來動態調用方法,但有時候需要動態的生成方法,下麵介紹使用表達式樹的方式來自動生成方法,並調用。

首先需要說明什麼是表達式,熟悉Linq的程式猿都用過類似於下麵的代碼:t=>t.Length<=25;

在C#中=>代表這是一個Lambda表達式,它用來對數組進行查詢,統計,排序,去重的功能非常有用。而表達式樹就是通過動態的創建一個Lambda的方式來實現相關的功能。

下麵是一個類似於JS中apply函數的示例。

使用表達式樹,一定要引用System.Linq.Expressions;其中的Expression類有很多的方法可以定義一個方法所需要的所有東西。

public class CommonTest

    {

        public object TestMethodCall(int age, string name)

        {

            Console.WriteLine($"{name}'s Age is {age}");

            return true;

        }

        public object TestExpression(MethodInfo method, object[] parameters, CommonTest instance)

        {

            //最終生成的表達式樣式(m,p)=>{return (object)m.method(p);}

            //定義兩個參數表達式

            ParameterExpression mParameter = Expression.Parameter(typeof(CommonTest), "m");//定義一個名稱為m的參數

            ParameterExpression pParameter = Expression.Parameter(typeof(object[]), "p");//定義一個名稱為p的參數

 

            ParameterInfo[] tParameter = method.GetParameters();//獲取到方法的所有參數

            Expression[] rParameter = new Expression[tParameter.Length];//定義一個與方法參數長度相同的表達式容器,因為在調用方法的時候需要使用的是表達式,不是直接使用方法的參數列表

            for (int i = 0; i < rParameter.Length; i++)

            {

                BinaryExpression pExpression = Expression.ArrayIndex(pParameter, Expression.Constant(i));//從方法中獲取到對應索引的參數

                UnaryExpression uExpression = Expression.Convert(pExpression, tParameter[i].ParameterType);//將此參數的類型轉化成實際參數的類型

                rParameter[i] = uExpression;//將對應的參數表達式添加到參數表達式容器中

            }

 

            MethodCallExpression mcExpression = Expression.Call(mParameter,method, rParameter);//調用方法,因為是實例方法所以第一個參數必須是m,如果是靜態方法,那麼第一個參數就應該是null

            UnaryExpression reExpression = Expression.Convert(mcExpression, typeof(object));//將結果轉換成object,因為要動態的調用所有的方法,所以返回值必須是object,如果是無返回值的方法,則不需要這一步

            return Expression.Lambda<Func<CommonTest, object[], object>>(reExpression, mParameter, pParameter).Compile()(instance, parameters);//將方法編譯成一個Func委托,並執行他

        }

}

以上的代碼的調用方式如下:

  CommonTest ct = new CommonTest();

            MethodInfo mi = typeof(CommonTest).GetMethod("TestMethodCall");

            var r = ct.TestExpression(mi, new object[] { 25, "SC" }, ct);

此方法也是C#MVC中調用控制器中的Action的原理代碼,其最大的作用是不管目標Action擁有多少個參數,最後調用都只需要一個object[]的參數,避免了直接使用反射調用,但是不確定參數個數的困難。

使用Expression不僅可以實習以上的類似於MVC原理的代碼,也可以對錶達式樹進行解析,可以實現ORM底層的Sql構成,但此出不再進行詳解,有興趣可以百度查詢表達式樹的解析。

表達式樹實現的缺點是功能實現複雜,調試困難,建議在實現之前先將需要實現的功能使用C#語法編寫出來,再按照對應的格式通過表達式樹來實現,這樣相對簡單一些。

下麵是使用表達式輸出一個99乘法表。

以下是實現的結果

 

 

 

首先是通過正常的方式來實現,代碼如下:

for (int i = 1; i <= 9; i++)

            {

                for (int j = 1; j <= i; j++)

                {

                    int total = i * j;

                    Console.Write($"{i} * {j} = {total}\t");

                }

                Console.WriteLine();

            }

            Console.ReadKey();

下麵是使用表達式樹實現相同功能的代碼:

/// <summary>

        /// 使用表達式樹實現99乘法表

        /// </summary>

        public void TestMultiple()

        {

            LabelTarget labOut = Expression.Label();//用於跳出外部迴圈的標誌

            LabelTarget labIn = Expression.Label();//用於跳出內部迴圈的標誌

 

            ParameterExpression iParameter = Expression.Parameter(typeof(int), "i");//定義外部迴圈的變數,類似於int i;

            ParameterExpression jParameter = Expression.Parameter(typeof(int), "j");//定義內部迴圈的變數,類似於int j;

            ParameterExpression rParameter = Expression.Parameter(typeof(int), "result");//定義用於保存i*j的結果的變數

 

            MethodInfo writeString = typeof(Console).GetMethod("Write", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(string) }, null);//獲取Write方法

            MethodInfo writeInt = typeof(Console).GetMethod("Write", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(int) }, null);//獲取Write方法

 

            Expression expResult = Expression.Block(

                new[] { iParameter, jParameter, rParameter },

                Expression.Assign(iParameter, Expression.Constant(1)),//為i賦初始值,類似於i=1;

                Expression.Loop(Expression.Block(//此處開始外部迴圈,表達式只能實現while迴圈,不能實現for迴圈

                Expression.IfThenElse(Expression.LessThanOrEqual(iParameter, Expression.Constant(9)),//定義執行的條件,類似於if(i<=9){

                                                                                                     //外部if為真的時候執行以下代碼

                  Expression.Block(

                    Expression.Assign(jParameter, Expression.Constant(1)),//為j賦初始值,類似於j=1;

                    Expression.Loop(Expression.Block(//此處開始內部迴圈

                        Expression.IfThenElse(Expression.LessThanOrEqual(jParameter, iParameter),//定義執行的條件,類似於if(j<=i){

                                                                                                 //內部if為真的時候執行以下代碼

                        Expression.Block(

                            Expression.Assign(rParameter, Expression.Multiply(iParameter, jParameter)),//此處用於計算i*j的結果,併進行賦值,類似於result=i*j

                                                                                                       //列印出結果,類似於Console.Write("i * j = " + result + "\t")

                            Expression.Call(null, writeInt, jParameter),

                            Expression.Call(null, writeString, Expression.Constant(" * ")),

                            Expression.Call(null, writeInt, iParameter),

                            Expression.Call(null, writeString, Expression.Constant(" = ")),

                            Expression.Call(null, writeInt, rParameter),

                            Expression.Call(null, writeString, Expression.Constant("\t")),

                            Expression.PostIncrementAssign(jParameter)//j自增長,類似於j++

                            ),

                            //內部if為假的時候執行以下代碼

                            Expression.Break(labIn))//此處跳出內部迴圈)

                        ), labIn),

                    Expression.Block(

                         Expression.Call(null, writeString, Expression.Constant("\n")),//此處列印換行符,類似於Console.WriteLine();

                         Expression.PostIncrementAssign(iParameter))//i自增長,類似於i++

                         )

                        //外部if為假的時候執行以下代碼

                        , Expression.Break(labOut))//此處跳出外部迴圈

                ), labOut));

            Expression.Lambda<Action>(expResult).Compile()();

        }

以上兩段代碼實現的效果相同,可以看出表達式樹實現相同的功能的複雜程度遠遠超出普通的方式,正常10行的代碼,表達式樹整整用了42行代碼才實現。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 公司之前使用Ado.net和Dapper進行數據訪問層的操作, 進行讀寫分離也比較簡單, 只要使用對應的資料庫連接字元串即可. 而最近要遷移到新系統中,新系統使用.net core和EF Core進行數據訪問. 所以趁著國慶假期拿出一兩天時間研究了一下如何EF Core進行讀寫分離. 思路 根 ...
  • 羊脂玉英文(mutton fat jade) 中文名羊脂玉 外文名muttonfatjade 羊脂玉又稱白玉,為軟玉中之上品,極為珍貴。主要含有透閃石(95%)、陽起石和綠簾石。非常潔白,質地細膩,光澤滋潤,狀如凝脂。古傳“白璧無瑕”即指白玉。 羊脂白玉是和田玉中的寶石級材料,是白玉中質純色白的極品 ...
  • 紅尖晶石(rubyspinel或Red spinel)其紅色是因含鉻而致^像紅寶石和紅色石榴子石一樣,紅 尖晶石也曾被叫作紅玉,這就造成了紅色寶石的混亂,因為世界上一些最大的著名“紅寶 石”,如英國王冠珠寶中的“黑王子紅寶石”,其實不是剛玉而是尖晶石; 外觀上,紅尖晶石的顏色有可能像紅寶石一樣呈明亮 ...
  • 金綠石的貓眼石(Cymophanite)是所謂正宗的貓眼石,非常罕有,尤其是5卡以上而質優的,其售價可以高達七萬多港元一卡。 相傳這類貓眼石是寶石學家的寶石,從此可知其地位在珠寶玉石之中的重要性。顏色通常是黃、綠黃和帶灰。最珍貴的顏色是蜜黃色/奶白。 其實「貓眼」是形容一道銀白色活動的光線。石內由很 ...
  • 祖母綠(canutillos)被稱為綠寶石之王,與鮮紅色的烏蘭孖努同樣稀有,國際珠寶界公認的四大名貴寶石之一(紅藍綠寶石以及鑽石)。因其特有的綠色和獨特的魅力,以及神奇的傳說,深受西方人的青睞。 祖母綠象徵著仁慈、信心、善良、永恆、幸運和幸福,佩戴它會給人帶來一生的平安。它也是結婚55周年的紀念石。 ...
  • 紅寶石的英文名稱為barklyite或Ruby,源於拉丁文 Ruber,意思是紅色。紅寶石的日文名稱為ルビー。紅寶石的礦物名稱為剛玉。(註:除紅寶石外,其他顏色的剛玉都屬於藍寶石。如粉紅色剛玉被稱為粉紅色藍寶石)只有由Cr致色的紅色的剛玉才能夠叫做紅寶石。而粉紅色的剛玉不是Cr致色的。 紅寶石屬於剛 ...
  • 名詞bitellos大鑽石 四大鑽石指的就是“攝政王”、“南非之星”、“藍色希望”和“光明之山”四顆鑽石。經過琢磨的鑽石光彩奪目、燦爛無比,歷來被譽為“寶石之王”,科研領域里大顆粒的鑽石叫做bitellos大顆粒的鑽石更是稀世珍寶。 說起鑽石,人們首先想到的就是財富、地位和榮耀。確實,經過琢磨的鑽石 ...
  • <Image Source="pack://application:,,,/Images/Folder-icon.png"/> <Image Source="pack://application:,,,/Assembly;component/Images/Folder-icon.png"/> <Im ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...