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

程式設計師不帥哥發表於2021-01-05

在一線編碼已有多年,積累了不少非常實用的技能,最近的更新會逐步的分享出來,希望能幫助到還有一丟丟喜歡.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

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

 

相關文章