天天看點

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

如果通過反射還可以通路私有方法,那麼設定私有方法的意義在哪呢?是否和私有類型的設計初衷違背了?

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

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

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

大胡子:你知道PHP嗎?

我:……我想學java

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

我:可是我想學java

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

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

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

第二家:10人超大公司,一間較高價的電梯大廈

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

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

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

我:……

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

我:……

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

我:……

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

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

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

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

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

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

 我們來看下今天的主題:

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

1.DLL内容都了解的話,直接引用DLL不就好了嗎,為什麼還要反射?

2.DLL裡面的内容什麼都不知道的話,就算反射的話,也不知道裡面的方法是幹什麼的啊,和直接引用DLL沒差別啊?

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

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

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

 對于計算機來講,它隻認識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類

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

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

        }

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

 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);
        }      
C#掃盲篇(一):反射機制--情真意切的說

 三、總結

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

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

首發自:【程式員不帥哥 】公衆号

原文連結:https://mp.weixin.qq.com/s/LCPLjBmmbJwXBDWdi3SU1g

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