.NET進階篇03-Reflection反射、Attribute特性

jiaxibei96發表於2019-10-26

知識需要不斷積累、總結和沉澱,思考和寫作是成長的催化劑

一、概述

反射其實無處不在,我們用VS進行除錯時候,檢視成員列表、修改變數值都是通過反射來實現的。我們寫業務程式碼可能很少去寫反射,但理解反射是從菜鳥到大牛的必經之路。無論EF還是ASP.NET,幾乎所有框架都用到反射。反射動態建立物件、動態賦值、動態呼叫方法

前面簡單介紹過.NET的第一次編譯,會編譯成IL(中間語言),反射就是利用IL在執行時獲取類的各種資訊(欄位、方法、建構函式等),並且可以動態的建立物件呼叫方法。反射就是通過使用metadata的過程

每一個類對應有一個Type物件,方法對應一個MethodInfo物件,屬性對應一個PropertyInfo,這些都是一個類的後設資料(MetaData)儲存在IL中,所以解析IL可以獲取一個類的各種資訊。

特性就和反射繫結的,沒有反射,特性就無從使用。特性本質就是給類、方法等元素新增一些額外的資訊和行為。特性新增編譯後也產生IL,我們沒法直接使用的,只在MetaData中有記錄,我們只能用過反射得到。

二、反射

1、反射使用

使用System.Reflection。.Net框架提供幫助類庫來進行反射。通常像下面這樣使用,載入絕對/相對路徑下的dll。(注意載入某一個dll,它內部依賴於別的類庫,需要把它們都放在統一路徑下)

Type的獲取,可以從物件獲取,類名獲取或類全路徑獲取(通過配置項例項化類很方便)。Activator.CreateInstance(type)建立類例項。載入dll建立某個例項用的都只是字串,既然是字串那麼就可以放在配置中。像下面這種如果要換個國家的人來打招呼就需要替換紅色字元即可。(前提當然是每個國家的人都繼實現ISayHello打招呼介面)。依賴於具體型別改為依賴字元配置檔案了

最常用的還是資料庫訪問層的封裝,不同資料庫的訪問都實現IDBHelper介面,如果換不同型別的資料庫,就只需要修改配置檔案即可。

2、建立物件

上面通過反射僅呼叫無引數建構函式,那麼有引數的呢。像下面這樣,給個object[]陣列指定引數。

另外需要注意反射建立物件可以呼叫私有的建構函式,這意味著它可以對單例模式造成破壞。(單例模式在設計模式中會詳細瞭解,就是一個類全域性只有一個例項)。具體看下面例項對比。

還有一種泛型型別的反射建立比較特殊。假設有以下泛型類,那麼在GetType時候需要使用“`”(反單引號)佔位符,這個符號鍵盤上一般在ESC鍵下方或數字1鍵的左邊。後接數字表示需要多少個泛型型別,然後指定具體的型別通過MakeGenericType再次建立出Type。

後面在容器裡會看到,IOC型別建立都是利用反射通過註解掃描或配置

3、呼叫方法

像下面註釋說明的那樣,包括類的私有方法也可以反射呼叫。泛型方法和泛型類的建立類似,需要在獲取指定名稱泛型方法基礎上,用型別陣列代替泛型方法型別引數

方法也可以通過字元指定,雖然呼叫起來比較麻煩,但的確很靈活。像MVC框架裡URL的對映就是利用類名+方法名來呼叫後臺的

4、欄位屬性

欄位和屬性的獲取也類似。通過GetFields、GetProperties獲取。

三、特性

Attribute特性標籤,也被叫做註解,用來給我們的類、屬性、方法等附加一些元資訊,這些資訊一般不影響我們程式碼的實際邏輯,起輔助作用,給框架或編譯器去解析的。通過Type物件仍然可以輕鬆獲取註解物件。表格中的內建註解我們不陌生

特性說明
[Obsolete]表明此成員已過時
[ReadOnly(true)]在編輯器中只讀,程式碼賦值不受影響
[DisplayName("姓名")]屬性的顯示名
[Browsable(false)]屬性是否可見
[Serializable]序列化和反序列化

除了.NET框架內建的特性,我們通過繼承Attribute可以自定義一個特性。例子中AttributeUsage是專用於標註自定義特性的特性,可以指定該特性的作用目標是類、方法還是欄位等,AllowMultiple允許同一個元素上可以多重標記該特性。

使用特性時,像下面這樣。在需要標記的元素上用總括號包裹特性類。語法類似呼叫建構函式,先是建構函式的引數(有參、無參),然後可以命名賦值屬性。

編碼時標記的特性,不去呼叫,只直觀的看,是沒有什麼意思的。通過反編譯工具ILSpy檢視IL程式碼,會發現在每個加了特性的元素(類、屬性、方法等)處會生成特性型別的建構函式。既然在IL中存在建構函式,那我們就可以通過反射直接建立類例項。

我們會有一個整體感覺,特性就是給類、屬性、方法等元素新增一個額外的、補充的描述,而沒有破壞原有類的封裝。像實體類屬性上的資料的規範性檢查特性一樣,我們可以定製統一的屬性檢查特性類,然後編碼中只需要在需要檢查的屬性上加上特性標記即可。通過反射就可以輕鬆獲取這些補充資訊,大概像下面這樣,我們對object型別增加Validate擴充套件方法,這樣所有的型別都可以呼叫這個擴充套件方法,然後通過反射查詢型別內部是否標記有需要驗證的屬性。

類似還有列舉的別名描述,字典項指等。獲取某一列舉的中文別名描述資訊或實體類中某一個欄位和資料庫表對應的列名(不一致時)。

四、總結

反射意味著動態。物件導向開發,物件之間協同完成某一功能,本身應該算耦合的,修改一個,其他依賴都需要修改,反射就可以把依賴抽象,依賴於擴充套件,執行時動態構建物件去完成某一功能,使其更加靈活,常見的外掛開發和IOC容器就是典型應用。但反射也有缺點,就是寫起來複雜,而且因為是動態的,避開了編譯器的檢查,效能也肯定不如硬編碼,這種效能我們大可不用太擔心,因為影響程式效能佔比最大的應該還是我們本身的編碼設計能力。

特性是和反射繫結的。.NET框架內建了很多特性,我們可能用不到自己手寫特性,但明白其中的原理是必要的,在Bs開發中,wcf、mvc等都用到特性註解。Java Web中Spring MVC也是通過註解來掃描裝配Bean的。特性和註釋可是完全不同的,註釋就是給人看,對程式一點影響都沒有,特性可以影響程式的執行。

如果手機在手邊,也可以關注下vx:xishaobb,互動或獲取更多訊息。當然這裡也一直更新de。


相關文章