Attribute
2016-07-12 22:41
218 查看
C#基础之Attribute
1.什么是Attribute特性简单点理解就是为目标元素添加一些附加信息,这些附加信息我们可以在运行期间以反射的方式拿到。目标元素指的是程序集、模块、类、参数、属性等元素,附加信息指的是特性类中的成员。可以看出特性类其实就是一个数据结构,我们可以将各种各样的信息放入这个类中,并将特性类关联到指定目标元素中,在目标元素中每关联一个特性就创建一个特性类的实例,当然它的作用还不止如此。下面是使用特性的3段代码,分别是3个类。第一个是MyAttribute特性类,第二个是与特性类关联的目标类MyClass,第3个类是主程序类Program。在第一个类中,我使用了AttributeUsage系统特性类,.net还为我们提供了很多固定特性类,比如Serializable、Conditional、DllImport、Flags等。AttributeUsage类的参数作用我代码里已经有了注释,可以添加这个特性也可以不添加,但这个特性类在我这个程序中必须添加。原因是我在第二个类MyClass中添加了4个MyAttribute特性实例,而默认情况下只允许添加一个实例。因此我得在AttributeUsage中指定AllowMultiple为true,如果不指定编译会报错。
在AttributeUsage特性类的参数中,并不是完全以传值的形式创建的实例,其中还可以直接给参数赋值比如AllowMultiple和Inherited。对于前者一般是构造函数中的参数,我们必须给构造函数赋值否则就不能初始化了,因此这种类型的参数是固定参数。对于后者则是可选参数,细心点会发现它就是特性类中的属性,比如在第二个类MyClass中就给Hobby属性赋值了。正因为参数里有可选参数,故MyClass可以同时关联4个特性实例。在第三个类中,我通过GetCustomAttribute拿到定制的特性数组,其实就是MyAttribute的实例,这样就可以通过实例对象获取里面的数据成员了。这便是Attribute的基本使用,由于使用时需要调用构造函数,因此定制特性类必须有公共构造函数。将程序的exe文件放入Reflector中可以很清楚的看到特性就是一个类,使用特性其实就是调用特性的构造函数。
//特性也可以用在特性上 [AttributeUsage( AttributeTargets.All, //目标元素可以是任何元素 AllowMultiple=true, //多个特性可以加在一个元素上,为false时一个元素上只允许有这个特性的唯一实例 Inherited=true)] //特性可被子类继承,为false时不可以被继承 class MyAttribute : Attribute { //字段 public string name="默认名字"; public int age = 0; string hobby="默认爱好"; //属性 public string Hobby { get { return hobby; } set { hobby = value; } } //构造方法 public MyAttribute() { } public MyAttribute(string name, int age) { this.name = name; this.age = age; } //实例方法 public void haha() { Console.WriteLine("哈哈"); } }
[My()] [My(Hobby="足球")] [My("小方",20)] [My("小白",30,Hobby="篮球")] class MyClass { public void haha() { Console.WriteLine("我是MyClass类"); } }
class Program { static void Main(string[] args) { /* * 本来想看一下会不会默认拿第一个实例,结果执行时报错:找到同一个类型的多个实例 //拿到MyClass上的一个特性实例 MyAttribute myAttribute = (MyAttribute)Attribute.GetCustomAttribute(typeof(MyClass), typeof(MyAttribute)); //看看这个实例是哪一个 if (myAttribute!=null) { Console.WriteLine(myAttribute.name); Console.WriteLine(myAttribute.age); Console.WriteLine(myAttribute.Hobby); myAttribute.haha(); } */ //拿到MyClass上的特性实例数组,这里有4个MyAttribute的实例 MyAttribute[] myAttributes = (MyAttribute[])Attribute.GetCustomAttributes(typeof(MyClass), typeof(MyAttribute)); MyAttribute myAttribute = myAttributes[0]; if (myAttribute != null) { Console.WriteLine(myAttribute.name); Console.WriteLine(myAttribute.age); Console.WriteLine(myAttribute.Hobby); myAttribute.haha(); } Console.ReadLine(); } } /*执行结果: 小白 30 篮球 哈哈 */
[AttributeUsage(AttributeTargets.Property,Inherited=false,AllowMultiple=false)] public class TrimAttribute : Attribute { //字段与属性 readonly Type myType; public Type MyType { get { return this.myType; } } //构造函数 public TrimAttribute(Type type) { myType = type; } }
class MyClass { [TrimAttribute(typeof(string))] public string Name { get; set; } [TrimAttribute(typeof(string))] public string Hobby { get; set; } [TrimAttribute(typeof(string))] public string Address { get; set; } }
//扩展方法必须是静态类,静态方法。 public static class TrimAttributeExtension { public static void Trim(this object obj) { Type t = obj.GetType(); //得到myclass实例对象的所有属性 foreach (PropertyInfo prop in t.GetProperties()) { //得到某个属性上的所有特性 foreach(var attr in prop.GetCustomAttributes(typeof(TrimAttribute),true)) { TrimAttribute trimAttribute = (TrimAttribute)attr; //获得obj的prop属性的值 object o=prop.GetValue(obj, null); //如果o不为null且这个属性上的特性实例的MyType属性是string类型 if (o!= null && (trimAttribute.MyType == typeof(string))) { //重新给这个属性赋值,也就是已经Trim()后的,可以看到GetPropertyValue(obj, prop.Name)其实就是o。 object newValue = GetPropertyValue(obj, prop.Name).ToString().Trim(); prop.SetValue(obj, newValue, null); } } } } //拿到属性本身所表示的值 private static object GetPropertyValue(object instance, string propertyName) { //首先得到instance的Type对象,然后调用InvokeMember方法, //这个方法的第一个参数意思是你需要调用的属性、方法、字段的”名字“,第二个参数是你调用propertyName是要干什么, //这里是拿到属性,第四个是要操作的实例。最后是需要传入的参数,这里调用属性因此不需要参数我就设置为null了。 return instance.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, instance, null); } }
class Program { static void Main(string[] args) { MyClass myclass = new MyClass(); myclass.Name = "小方 "; myclass.Hobby = " 篮球 "; myclass.Address = " 湖北"; myclass.Trim(); /* 执行到这里会看到上面三个属性的值中空格全部都没有了 myclass.Name = "小方"; myclass.Hobby = "篮球"; myclass.Address = "湖北"; */ Console.ReadLine(); } }
class Program { static void Main(string[] args) { MyClass myclass = new MyClass(); myclass.Name = "小方 "; myclass.Hobby = " 篮球 "; myclass.Address = " 湖北"; myclass.Trim(); /* 执行到这里会看到上面三个属性的值中空格全部都没有了 myclass.Name = "小方"; myclass.Hobby = "篮球"; myclass.Address = "湖北"; */ Console.ReadLine(); } }
3.总体上认识Attribute
特性,这个描述信息的数据类所描述的信息其实就是元数据。当我们在VS中生成解决方案时,在debug文件夹中就会出现一个exe文件,在windows中它称为可迁移可执行文件PE。PE由3部分组成:PE标头、IL、元数据。PE头主要作用是标识此文件是PE文件并说明在内存中执行程序的入口点。IL不用多说,但有一点要注意IL指令中常有元数据标记。元数据包含元数据表和堆数据结构。一个程序中会有很多类,这些类在PE中都会记录在一个记录类型的元数据表中,此外还有记录方法、字段等成员的元数据表,元数据表也可以引用其他的表和堆。可将这些表理解为数据库中的表,表之间通过主外键来建立一种约束与联系。不过我不知道这些表是如何创建的,是程序中某种成员的所有数据全部放在一起,还是有些数据比如字段是以类为划分的。元数据的堆数据结构有4种,分别是字符串、Blob、用户字符串、GUID。在IL中还有一个元数据标记,可以理解为一个指向元数据的指针,它包含4字节。第一个字节说明这个指针指向的类型,比如是指向类表呢还是指向方法表呢。后3个字节说明指向目标表中的位置,这种感觉有点像zigbee编程。再来看元数据的作用,在程序中定义的所有成员以及外部引入的成员都将在元数据中进行说明,这样在JIT生成机器指令时正是通过元数据中的信息来完成即时编译的。元数据中存储程序中程序集的说明(名称、版本、依赖的其他程序集等),类型的说明(类成员、可访问性、继承实现关系等),特性。到这里可以理解特性是属于PE中的元数据的一部分,具体到物理结构上我觉得是有一个元数据特性表,比如类型元数据表的一个类有一个指针指向它的元数据特性表,这个特性表记录着与这个类关联的所有特性。另外由于特性是作为元数据的一部分,因此特性类将会在编译时就实例化,而不是运行期动态实例化。
相关文章推荐
- hdu 1004 Let the Balloon Rise
- 考勤率
- 表值函数,标量值函数
- sql 联合查询速度慢,需要对其进行分组
- shell中的数学运算
- 解决No enclosing instance of type * is accessible
- 改善C++程序的建议:语法篇1<从C继承而来的特性>
- μC/OS-II微小内核分析
- lcd framebuffer
- 深拷贝与浅拷贝
- 固定VMWare虚拟机中linux系统的IP地址
- 协议分析工具学习TCP/IP(一)
- 『Data Science』R语言学习笔记,使用Swirl包学习R
- 精确小数点
- 神奇的幻方【够造奇数阶的魔方阵】
- 封装按钮
- 单个驱动的DEBUGMSG
- java面试题训练160712
- 第四天
- Transformations