知識需要不斷積累、總結和沉淀,思考和寫作是成長的催化劑
內容目錄
一、概述二、反射1、反射使用2、創建對象3、調用方法4、字段屬性三、特性四、總結
一、概述
反射其實無處不在
,我們用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。