經過這篇文章,不只能夠了解到Attribute的工做原理,還能夠了解到GetcustomeAttribute是的內部執行流程。最後,你會看到,使用緩存機制能夠極大的優化反射Attribute的性能。數據庫
本文結構:緩存
1.爲何在對象上標記Attribute性能很慢。性能優化
2.編譯器如何編譯帶有Attribute標記的類型ide
3.定義解析器,在運行時獲取並解析對象上的Attribute函數
4.GetCustomeAttributes方法的工做原理性能
5.優化Attribute優化
6.Attribute性能優化完整代碼this
7.總結spa
參考資料:線程
關於Attribute的緩存思想請參見後面的連接,固然這篇文章裏還介紹了依賴注入以及ORM中的一些優化,連接以下:http://www.codeproject.com/Articles/503527/Reflection-optimization-techniques
關於CustomeAttribute的介紹請參見ECMA-CLI文檔第二部分Partition_II_Metadata的第22章第10節,但我再後面仍是給出了這個介紹。
1.爲何在對象上標記Attribute性能很慢。
首先請看以下代碼:
[TableAttribute("student")] class StudentModel { public string Id{get;set;} } class TableAttribute : Attribute { string name; public string Name { get { return name; } } public TableAttribute(string name) { this.name = name; } }
性能損耗1:每建立一個StudentModel對象,都要利用反射建立一個TableAttribute對象。
性能損耗2:上一步驟中建立的全部TableAttribute都是徹底相同的,值都是」student」。換句話說,若是建立了n 個StudentModel,那麼就會具備n個徹底同樣的TableAttribute。
2.編譯器如何編譯帶有Attribute標記的類型
編譯器在編譯上述代碼時,發現StudentModel有一個TableAttribute特性,就會在最終的程序集中生成一個customeAttribute元數據表,這個表有三個字段:parent,type,value。其中parent指向StudentModel類,type指向TableAttribute的構造函數,value值是要傳遞給TableAttribute的參數,這個裏的參數就是」student」。以下圖:
3.定義解析器,在運行時獲取並解析對象上的Attribute
若是對象上僅僅是標記了Attribute,那麼是不會有什麼性能損失的,由於咱們並無使用它。
每當咱們自定義一個Attribute時,都要同時建立一個用於解析這個Attribute的解析器。這個解析器須要作兩件事:第一獲取對象上標記的Attribute,其次根據Attribute中的屬性值來執行相應的動做。代碼以下:
class AttributeInterpreter { public static void Interprete() { TableAttribute[] attributes = typeof(StudentModel).GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[]; foreach(var eachAttribute in attributes) { string tableName = eachAttribute.TableName; //根據tableName訪問數據庫中的對應表,而後讀取數據並建立StudentModel對象。 } } }
這裏,首先獲取StudentModel的Type,而後調用Type的GetCustomeAttributes方法,並傳遞了typeof(TableAttribute)參數。最後GetCustomeAttributes方法返回StudentModel上標記的TableAttribue對象。
附註:ECMA-CLI中的CustomeAttribute詳細說明:
2 2 . 1 0 C us to mAt tr i b ut e : 0 x0 C The CustomAttribute table has the following columns: ? Parent (an index into any metadata table, except the CustomAttribute table itself; more precisely, a HasCustomAttribute (§24.2.6) coded index) ? Type (an index into the MethodDef or MemberRef table; more precisely, a CustomAttributeType (§24.2.6) coded index) ? Value (an index into the Blob heap) Partition II 119 The CustomAttribute table stores data that can be used to instantiate a Custom Attribute (more precisely, an object of the specified Custom Attribute class) at runtime. The column called Type is slightly misleading—it actually indexes a constructor method—the owner of that constructor method is the Type of the Custom Attribute. A row in the CustomAttribute table for a parent is created by the .custom attribute, which gives the value of the Type column and optionally that of the Value column (§21).
4.GetCustomeAttributes方法的工做原理
在運行時,當調用GetCustomeAttributes時,CLR會遍歷customeAttribute元數據表,查找parent=StudentModle和type=TableAttribute的CustomeAttribute元數據表。找到以後,根據type字段找到TableAttribute,而後建立他的實例,調用構造函數,併爲構造函數傳遞參數「student」。
5.優化Attribute
優化的第一步就是利用緩存機制來存儲已經建立好的TableAttribute,當須要對象時直接從緩存裏取,這樣只有在第一次建立StudentModel時,纔會反射建立TableAttribute對象。以下
private static Dictionary<Type, TableInfo> cache = new Dictionary<Type, TableInfo>(); public StudentModel() { Type studentModelType = this.GetType(); Type tableAttType=typeof(TableAttribute); if(!cache.TryGetValue(tableAttType, out tableINfo)) { TableAttribute[] tableAttributes = studentModelType.GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[]; if(tableAttributes == null) return; TableAttribute tableAttribute = tableAttributes[0]; cache.Add(tableAttType, new TableInfo(tableAttribute.TableName)); }
使用緩存須要考慮鎖的問題。當幾個線程同時修改緩存時,必須保證在同一時刻只有一個線程修改,這裏使用雙鎖機制來進行線程同步。以下:
public StudentModel() { Type studentModelType = this.GetType(); Type tableAttType=typeof(TableAttribute); TableInfo tableInfo; if(!cache.TryGetValue(tableAttType, out tableInfo)) { lock(this) { if(!cache.TryGetValue(tableAttType, out tableInfo)) { TableAttribute[] tableAttributes = studentModelType.GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[]; if(tableAttributes == null) return; TableAttribute tableAttribute = tableAttributes[0]; cache.Add(tableAttType, new TableInfo(tableAttribute.TableName)); } } } }
優化的結果:只在第一次建立對象時反射Attribute,減小了內存的使用。
6.Attribute性能優化完整代碼
[Table("student")] class StudentModel { public string Id{get;set;} //定義緩存,存儲已經建立好的TableAttribute對象。 private static Dictionary<Type, TableInfo> cache = new Dictionary<Type, TableInfo>(); public StudentModel() { Type studentModelType = this.GetType(); Type tableAttType=typeof(TableAttribute); TableInfo tableInfo; if(!cache.TryGetValue(tableAttType, out tableInfo)) { lock(this) { if(!cache.TryGetValue(tableAttType, out tableInfo)) { TableAttribute[] tableAttributes = studentModelType.GetCustomAttributes(typeof(TableAttribute), true) as TableAttribute[]; if(tableAttributes == null) return; TableAttribute tableAttribute = tableAttributes[0]; cache.Add(tableAttType, new TableInfo(tableAttribute.TableName)); } } } } } //AlloMultiple爲true,代表這個特性不能被重複標記,緣由很簡單,一個實體對象不能映射到兩個表。 [AttributeUsage(AttributeTargets.Class,AllowMultiple=false)] class TableAttribute : Attribute { string tableName; public string TableName { get { return tableName; } } public TableAttribute(string name) { this.tableName = name; } }
7.總結
在使用Attribute特性時,編譯器會在最終的程序集中生成一個CustomeAttribute元數據表,這個表其實是一個映射表,其中的type字段和parent字段將特性和對象相關聯。
僅僅定義個Attribute是沒有任何實際做用的,咱們須要定義一個解析器用於獲取對象上的Attribute並根據其屬性值採起相應的動做。
在運行的時候,想要獲取對象上的某特性時,須要調用Type的GetCustomeAttributes方法。這個方法在內部使用了反射來獲取特性並實例化特性。
因爲反射很浪費性能,而且對於類型的每個對象,與其關聯的Attribute都是相同的,因此能夠利用字典只保存其中的一個。因爲反射很浪費性能,因此能夠考慮將建立好的Attribute緩存起來。
若是這個緩存須要被多個線程修改,須要使用鎖來同步,這裏爲了提供性能,使用了雙鎖機制和Monitor。