C#掃盲篇(一):反射機制--情真意切的說


在一線編碼已有多年,積累了不少非常實用的技能,最近的更新會逐步的分享出來,希望能幫助到還有一丟丟喜歡.Net的朋友,當然這些都比較適合入門選手,雖然自己已是個精通抄代碼的老猿,但技術造詣仍是渣渣。

猶記得當年,自己憑借滿腔熱血,習得一身Java理論知識,一本《Java從入門到精通》常伴左右。初入大四后,已覺自己羽翼豐滿,可以起飛,於是躍躍欲試,自信滿滿的外出找實習。我拿着自己精心制作的簡歷,上面一眾“圖書管理系統”、“學生成績查詢系統”、“酒店管理系統”、“出入庫管理系統”等熱血參與大制作。想着自己擁有如此豐厚的經歷,offer定是信手拈來。

第一家:五人大公司,深藏居民樓小角落

大胡子:你知道PHP嗎?

我:……我想學java

大胡子:PHP現在是最流行的語言,我們有專人帶你,就看中你的好學。

我:可是我想學java

大胡子:給你一個月開1600,怎么樣?

我:好(真是毫無原則的狗蛋)……

一禮拜后我離職了,他們哪里是在做開發,就是做拼接頁面而已,我也只是整理資料,打掃衛生。

第二家:10人超大公司,一間公寓

小白臉:做過公司系統嗎?

我:(難道我做的都是玩的嗎?)做的少。

小白臉:做學生成績查詢系統時如何考慮並發?

我:……

小白臉:問了你幾個問題,都是理論多,實操很少啊。

我:……

小白臉:給你個建議,別着急找工作,回去再好好學,基礎不扎實么,要多做公司級系統。

我:……

第三家:15人巨頭公司,居民樓

老板:我們現在願意招學生,願意培養,java和.net都一樣,條條大路通羅馬,不用過於追求語言的差別,學好了都是大牛。

我:是的是的(被一語道破心中疑慮,反正我小白一個,用什么技術棧都一樣從零起步)

老板:來我們公司,我帶你……

一如此門深似海,從此Java是路人。

--------以上演義都是本人真實經歷改編,意在告誡各位語言無好壞,只有使用的人才有差別

 我們來看下今天的主題:

聽到反射,很多人應該和我一樣有這么幾個疑問:

1.DLL內容都了解的話,直接引用DLL不就好了嗎,為什么還要反射?
2.DLL里面的內容什么都不知道的話,就算反射的話,也不知道里面的方法是干什么的啊,和直接引用DLL沒區別啊?

這幾個問題先不着急回答,我們繼續分析下。

想要知道反射,就必須先了解一下計算機是如何運行我們寫的代碼的,如下圖:

 對於計算機來講,它只認識01010101之類的二進制代碼,人類寫的高級語言(如C#、JAVA等)計算機是沒法識別的,所以需要將高級語言轉化為01讓計算機可以識別的二進制編碼,中間是有一個過程的。就拿C#來講,VS編譯器會將編寫好的代碼進行編譯,編譯后會生成exe/dll文件,.Net Core里面已經不生成exe了,都是dll。dll和exe還需要CLR/JIT的即時編譯成字節碼,才能最終被計算機執行。有伙伴就會問為什么要編譯2次呢,先編譯到dll,再編譯到字節碼01呢,為什么不能一次性編譯成字節碼呢?因為我們寫的是C#語言,但是真實運行的機器有很多種,可能是32位,也可能是64位,操作系統可能是windows、linux、unix等,不同的計算機不同的操作系統識別字節碼的可能是不一樣的,但是從高級語言編譯成exe/dll這一步是一樣的。所以只要在不同運行環境的計算機上安裝對應的不同的CLR/JIT,就可以運行我們同一個exe/dll了。這里就大概講下這樣一個過程,后面會有章節詳細講解程序如何被計算機執行的。現在我們先關注編譯生成的exe/dll,它包含2部分,分別是中間語言IL和源數據元數據metadata。IL里面包含我們寫的大量的代碼,比如說方法、實體類等。元數據metadata不是我們寫的代碼,它是編譯器在編譯的時候生成的描述,它可能是把命名空間、類名、屬性名記錄了一下,包括特性。

講上面程序的編譯過程跟反射有什么關系呢?我們反射就是讀取metadata里面的數據的,然后去使用它。

反射是.NET中的重要機制,通過反射可以得到*.exe或*.dll等程序集內部的接口、類、方法、字段、屬性、特性等信息,還可以動態創建出類型實例並執行其中的方法。

一、反射的用途:

類型 作用
Assembly 定義和加載程序集,加載程序集清單中列出的模塊,以及從此程序集中查找類型並創建該類型的實例。
Module 了解包含模塊的程序集以及模塊中的類等,還可以獲取在模塊上定義的所有全局方法或其他特定的非全局方法。
ConstructorInfo 了解構造器的名稱、參數、訪問修飾符(如public或private)和實現詳細信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法來調用特定的構造函數。
MethodInfo 了解方法的名稱、返回類型、參數、訪問修飾符(如public或private)和實現詳細信息(如abstract或virtual)等。使用Type的GetMethods或GetMethod方法來調用特定的方法。
FieldInfo 了解字段的名稱、訪問修飾符(如public或private)和實現詳細信息(如static)等,並獲取或設置字段值。
EventInfo 了解事件的名稱、事件處理程序數據類型、自定義特性、聲明類型和反射類型等,並添加或移除事件處理程序。
PropertyInfo 了解屬性的名稱、數據類型、聲明類型、反射類型和只讀或可寫狀態等,並獲取或設置屬性值。
ParameterInfo 了解參數的名稱、數據類型、參數是輸入參數還是輸出參數等,以及參數在方法簽名中的位置等。

二、反射實例

我們通過實際例子來看下反射的用途。

1.首先建立一個控制台程序,並添加一個類庫,里面建立一個AnimalsInfo類

 AnimalsInfo中定義如下屬性和方法: 

public  class AnimalsInfo
    {
        public string Type { get; set; }
        public int Size { get; set; }
        public void CommonMethod()
        {
            Console.WriteLine("我就是一個普通方法");
        }
        public void ParameterMethod(string type)
        {
            Console.WriteLine("我是帶參數方法,我是" + type);
        }

        public void OverrideMethod(int size)
        {
            Console.WriteLine($"我是重載方法,我有{size}大");
        }
        public void OverrideMethod(string name)
        {
            Console.WriteLine("我是重載方法,我叫" + name);
        }
        public void GenericityMethod<T>(T t)
        {
            Console.WriteLine("我是泛型方法方法,類型是" + typeof(T));
        }
        private void PrivateMethod()
        {
            Console.WriteLine("我是私有方法");
        }
        public static void StaticMethod()
        {
            Console.WriteLine("我是靜態方法");
        }
    }

2.利用反射獲取類庫,屬性

using System;
//第一步引用命名空間
using System.Reflection;

namespace ReflectionTest
{
    class Program
    {
        static void Main(string[] args)
        {
       Console.WriteLine("以下是獲取類庫的");
//第二步,動態加載類庫,一定寫要獲取類庫的**絕對路徑** Assembly assembly = Assembly.LoadFile(@"C:\Users\XA-BAU-Lyvin\source\repos\ReflectionTest\ReflectionTest.Model\bin\Debug\netcoreapp3.1\ReflectionTest.Model.dll"); //第三步,動態獲取類型,寫類庫的名稱和類的名稱 Type type = assembly.GetType("ReflectionTest.Model.AnimalsInfo"); Console.WriteLine(type.Name);

        Console.WriteLine("以下是獲取屬性的");
        //遍歷類型的屬性集合
        foreach (var item in type.GetProperties())
        {
        Console.WriteLine("字段名:"+ item.Name + ",類型:" + item.PropertyType);
        }

        }
    }
}

 

 3.通過反射獲取方法

  • 所有的方法都要指定要獲取的方法名稱
  • 創建方法,第一個參數,對象,第二個參數,是一個object對象數組,寫對應的參數類型
  • 私有方法不一樣,一定有看清楚,它指明是父類的私有方法
static void Main(string[] args)
        {
            Console.WriteLine("以下是獲取類庫的");
            //第二步,動態加載類庫,一定寫要獲取類庫的**絕對路徑**
            Assembly assembly = Assembly.LoadFile(@"C:\Users\XA-BAU-Lyvin\source\repos\ReflectionTest\ReflectionTest.Model\bin\Debug\netcoreapp3.1\ReflectionTest.Model.dll");
            //第三步,動態獲取類型,寫類庫的名稱和類的名稱
            Type type = assembly.GetType("ReflectionTest.Model.AnimalsInfo");
            Console.WriteLine(type.Name);

            Console.WriteLine("以下是獲取屬性的");
            //遍歷類型的屬性集合
            foreach (var item in type.GetProperties())
            {
                Console.WriteLine("字段名:"+ item.Name + ",類型:" + item.PropertyType);
            }

            Console.WriteLine("==============普通方法==================");
            //創建一個符合類型的對象
            object oAnimal = Activator.CreateInstance(type);
            //***所有的方法都要指定要獲取的方法名稱
            MethodInfo commonMethod = type.GetMethod("CommonMethod");
            //創建方法,第一個參數,對象,第二個參數,沒有則為空
            commonMethod.Invoke(oAnimal, null);

            Console.WriteLine("==============帶參數的方法==================");
            MethodInfo parameterMethod = type.GetMethod("ParameterMethod");
            //創建方法,第一個參數,對象,第二個參數,是一個object對象數組,寫對應的參數類型
            parameterMethod.Invoke(oAnimal, new object[] { "狗狗" });

            Console.WriteLine("==============重載方法int參數==================");
            MethodInfo overrideMethodInt = type.GetMethod("OverrideMethod", new Type[] { typeof(int) });
            overrideMethodInt.Invoke(oAnimal, new object[] { 18 });

            Console.WriteLine("==============重載方法string參數==================");
            MethodInfo overrideMethodStrint = type.GetMethod("OverrideMethod", new Type[] { typeof(string) });
            overrideMethodStrint.Invoke(oAnimal, new object[] { "喵喵" });

            Console.WriteLine("==============泛型方法==================");
            MethodInfo genericityMethod = type.GetMethod("GenericityMethod").MakeGenericMethod(new Type[] { typeof(int) });
            genericityMethod.Invoke(oAnimal, new object[] { 45 });

            Console.WriteLine("==============私有方法==================");
            //指定要獲取的方法名稱,指明是父類的私有方法
            MethodInfo privateMethod = type.GetMethod("PrivateMethod", BindingFlags.Instance | BindingFlags.NonPublic);
            privateMethod.Invoke(oAnimal, null);

            Console.WriteLine("==============靜態方法=================");
            MethodInfo staticMethod = type.GetMethod("StaticMethod");
            staticMethod.Invoke(null, null);
        }

 

 三、總結

所有的反射應用方法都已經講完了,看完以后感覺其實也沒有什么神秘的,很簡單對不對?

當然,還有個問題要留給大家繼續討論了:如果通過反射還可以訪問私有方法,那么設置私有方法的意義在哪呢?是否和私有類型的設計初衷違背了?

 

首發自:【程序員不帥哥 】公眾號

原文鏈接:https://mp.weixin.qq.com/s/LCPLjBmmbJwXBDWdi3SU1g

掃碼關注,更多精彩內容及時獲取,一起提高,一起加油

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM