通过这篇文章,不仅可以了解到Attribute的工作原理,还可以了解到GetcustomeAttribute是的内部执行流程。最后,你会看到,使用缓存机制可以极大的优化反射Attribute的性能。

本文结构:

  1.为什么在对象上标记Attribute性能很慢。

  2.编译器如何编译带有Attribute标记的类型

  3.定义解析器,在运行时获取并解析对象上的Attribute

  4.GetCustomeAttributes方法的工作原理

  5.优化Attribute

  6.Attribute性能优化完整代码

  7.总结

参考资料:

关于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详细说明:

  .     C us to mAt tr i b ut e :  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.) coded index)
? Type (an index into the MethodDef or MemberRef table; more precisely, a CustomAttributeType
(§24.2.) coded index)
? Value (an index into the Blob heap)
Partition II
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 (§).

ECMA-CLI CustomeAttribute简介

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[];
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[];
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[];
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;
}
}

Attribute性能优化完整代码

7.总结

在使用Attribute特性时,编译器会在最终的程序集中生成一个CustomeAttribute元数据表,这个表实际上是一个映射表,其中的type字段和parent字段将特性和对象相关联。

仅仅定义个Attribute是没有任何实际作用的,我们需要定义一个解析器用于获取对象上的Attribute并根据其属性值采取相应的动作。

在运行的时候,想要获取对象上的某特性时,需要调用Type的GetCustomeAttributes方法。这个方法在内部使用了反射来获取特性并实例化特性。

由于反射很浪费性能,并且对于类型的每一个对象,与其关联的Attribute都是相同的,所以可以利用字典只保存其中的一个。由于反射很浪费性能,所以可以考虑将创建好的Attribute缓存起来。

如果这个缓存需要被多个线程修改,需要使用锁来同步,这里为了提供性能,使用了双锁机制和Monitor。

最新文章

  1. JS核心系列:浅谈原型对象和原型链
  2. 简单深入SpringMvc
  3. linux源代码安装程序
  4. Ubuntu 14.10安装手记
  5. ViewHolder简洁写法
  6. java中使用正则表达式
  7. [Unity3D]Unity3D游戏开发之使用EasyTouch虚拟摇杆控制人物移动
  8. HTML特殊字符大全
  9. QLCDNumber设置背景色和显示数字颜色
  10. 转 fiddler教程
  11. 处理Properties文件中key包含空格的情况
  12. AllPairs运用遇到的问题及解决办法分享
  13. 【Android Studio安装部署系列】十四、Android studio移除工程和删除项目
  14. ln语法
  15. (Dijkstra) POJ2387 Til the Cows Come Home
  16. FreeMaker使用HashMap
  17. PAT 1063 计算谱半径
  18. python 网页爬虫,下载网络图片
  19. Collection FrameWork
  20. python StringIO类

热门文章

  1. Pyqt 中__init__(self,parent==None) parent理解
  2. .Learning.Python.Design.Patterns.2nd.Edition之单实例模式
  3. 无废话ExtJs 入门教程三[窗体:Window组件]
  4. 7-11使用UNION合并查询
  5. js判断手机端Android手机还是iPhone手机
  6. hdu 4731 2013成都赛区网络赛 找规律
  7. 深入剖析iLBC的丢包补偿技术(PLC)
  8. xcrun: error: active developer path (&quot;/XX&quot;) does not exist
  9. ubuntu下整合eclipse和javah生成jni头文件开发android的native程序(转)
  10. ObCallback回调钩子检测