本文来之:http://hi.baidu.com/sanlng/item/afa31eed0a383e0e570f1d3e
在一般的应用中,特性(Attribute,以称为属性)好像被使用的不是很多。其实特性是一个很有用的东西,也是.net的一个重要组成部分。
1、特性是什么?
特性是一种向类添加代码的方法,这些代码以声明的形式来修饰程序集、类以及其它代码元素。这种修饰类似于public、private等关键字对一个方法的修饰,与之不同的是,多数特性并不与特定的语言相关,所以在使用特性时可以在不需要改变编译器的情况下扩展语言功能。
2、特性有什么用
特性是.net中一个重要的组成部分,.net在很多地方都使用了特性,见下面的文字。
以上文字来自MSDN。网址:
3、C#中的特性类
C#中的所有特性的基类都是Attribute的子类。特性在使用的时候不同于一般的类。特性在使用的时候需要将其与某种代码元素(如一个类或一个方法)关联起来,并且只有在特性被搜索到时才能实例化和使用。还有有趣的一点是,特性的添加并不会为你的程序添加任何可执行代码,程序在编译时只是将其添加程序集里,所以如果你想使用元数据中的某个特性,那你就得先去搜索。
4、如何使用特性
●使用系统特性类
Obsolete特性
该特性用于标识不再使用的代码元素,见下面的代码(代码片断1)
1 [Obsolete("该方法将在下一版中被删除")] 2 public string ReturnValue(string OutValue) 3 { 4 return OutValue ; 5 }
(代码片断1)
ReturnValue()方法被Obsolete进行标记,这样当包含这个方法的类被实例化且该方法被调用时,编译器将给出警告信息,但程序依旧可以执行。
因为特性是一个类,所以其拥有构造函数,并且还可能具有被重载了的带参数的构造函数,下面的代码使用了带有两个参数的特性构造函数
1 [Obsolete("该方法将在下一版中被删除",false)] 2 public string ReturnValue(string OutValue) 3 { 4 return OutValue ; 5 }
(代码片断2)
Obsolete的这个构造函数中的第二个参数用于表示在编译器编译时是否将过时代码视为错误,我们这里将其指定为false,即不视为错误处理。如果将其指定为true,则对过时方法的调用将引发异常,程序不能被编译。如下代码
1 [Obsolete("该方法将在下一版中被删除",true)] 2 public string ReturnValue(string OutValue) 3 { 4 return OutValue ; 5 }
(代码片断3)
前面讲到过,特性只有被专门的特性搜索工具搜索到之后才能实例化和使用,Obsolete特性由编译器搜索检查并搜索,其它一些特性则由.net架框中的类进行检查和搜索。
.net中还有大量的特性可供使用,如控制对象序列化、标识Web Service之类的特性。按照惯例,.net中,所有的特性其类名都是以Attribute结尾的,包括编译器在搜索一个特性时如果没有找到,会自动在该特性名之后加上Attribute继续搜索。
●自定义特性类
下面我们将自己定义一个特性。定义一个特性与定义一个类没有什么区别,我们只需要指定其从Attribute基继承即可。下面的代码定义了一个特性,该特性用于指定一个代码元素的作者、电话以及该元素的注释内容
1 namespace MyClass 2 { 3 /// <summary> 4 /// 标记一个类的作者、注释与电话 5 /// </summary> 6 public class AuthorAttribute : Attribute 7 { 8 private string _Name; 9 10 private string _Note; 11 12 private string _Tel; 13 14 public AuthorAttribute() 15 { 16 // 17 // TODO: 在此处添加构造函数逻辑 18 // 19 } 20 21 public AuthorAttribute(string Name) 22 { 23 _Name = Name; 24 } 25 26 /// <summary> 27 /// 用于获取或设置代码元素的作者 28 /// </summary> 29 public string Name 30 { 31 get 32 { 33 return _Name; 34 } 35 set 36 { 37 _Name = value; 38 } 39 } 40 41 /// <summary> 42 /// 用于获取或设置代码元素的注释 43 /// </summary> 44 public string Note 45 { 46 get 47 { 48 return _Note; 49 } 50 set 51 { 52 _Note = value; 53 } 54 } 55 56 /// <summary> 57 /// 用于获取或设置代码元素作者的电话 58 /// </summary> 59 public string Tel 60 { 61 get 62 { 63 return _Tel; 64 } 65 set 66 { 67 _Tel = value; 68 } 69 } 70 71 /// <summary> 72 /// 用于获取对代码元素的注释 73 /// </summary> 74 /// <returns></returns> 75 public string GetNote() 76 { 77 if (_Note == null) 78 { 79 return "没有添加任何注释!"; 80 } 81 else 82 { 83 return _Note; 84 } 85 } 86 } 87 }
(代码片断4)
下面的MemberInfo()方法使用了我们上面定义的特性
1 [AuthorAttribute("henry")] 2 public string MemberInfo(string OutValue) 3 { 4 return "该会员名称:" + _Name + ";该会员所属国家:" + _Country ; 5 }
(代码片断5)
上面定义的特性类可以附加在任何代码元素之上,稍后我们将看到如何控制一个特性的具体应用,比如只允许一个特性附加在方法而不是属性上。
5、搜索特性
怎么样判断一个代码元素上是否使用了特性了呢?这里需要使用反射来搜索一个代码元素是否使用了特性。
下面是自定义的一个测试类
1 namespace MyClass 2 { 3 /// <summary> 4 /// 会员类,封装了有关会员信息与操作 5 /// </summary> 6 public class Member 7 { 8 9 private string _Name = ""; 10 11 private string _Country = "中国"; 12 13 14 public string Name 15 { 16 get 17 { 18 return _Name; 19 } 20 21 set 22 { 23 _Name = value; 24 } 25 } 26 27 public string Country 28 { 29 get 30 { 31 return _Country; 32 } 33 34 set 35 { 36 _Country = value; 37 } 38 } 39 40 public Member() 41 { 42 // 43 // TODO: 在此处添加构造函数逻辑 44 // 45 } 46 47 public string MemberName() 48 { 49 if (_Name == "") 50 { 51 return "没有为该会员设置名称!"; 52 } 53 else 54 { 55 return _Name; 56 } 57 } 58 59 public string MemberCountry() 60 { 61 return _Country; 62 } 63 64 65 public string MemberInfo(string OutValue) 66 { 67 return "该会员名称:" + _Name + ";该会员所属国家:" + _Country; 68 } 69 } 70 }
(代码片断6)
现在,我们假设在Member类开始定义的顶部即public class Member一行的上面使用了我们定义好的特性类AuthorAttribute即[AuthorAttribute("henry")],那么我们可以利用以下代码来检查Member类的作者到底是谁。
1 Member TMember = new Member(); 2 Type T = TMember.GetType(); 3 foreach (Attribute attribute in T.GetCustomAttributes(true)) 4 { 5 AuthorAttribute author = attribute as AuthorAttribute; 6 if (author != null) 7 { 8 Response.Write(author.Name); 9 } 10 }
(代码片断7)
最后打印出的结果是“henry”,从而得出Member类应用了AuthorAttribute 类并指定其作者为henry。如果对反射不清楚,我的博客上有关于反射基本应用的文章,不妨参考一下。
上面的代码(代码片断7)可以找出一个特定的类型是否使用了自定义特性。如果我们把AuthorAttribute 特性应用到了Member类的某个方法上而不是Member类上,那么上面的代码能够找到AuthorAttribute 特性吗?
答案是不能的。如果AuthorAttribute 特性应用在了一个方法上,以上代码什么都找不到。以下代码在指定的方法上搜索AuthorAttribute 特性。这里我们假设AuthorAttribute 特性应用到子Member类的MemberInfo()公共方法上,即
1 [Author("henry")] 2 public string MemberInfo(string OutValue) 3 { 4 return "该会员名称:" + _Name + ";该会员所属国家:" + _Country ; 5 }
(代码片断8)
注意[Author("henry")]一句,这里的Author("henry")等同于AuthorAttribute("henry")。
以下代码将在MemberInfo()方法上进行搜索
1 Member TMember = new Member(); 2 Type T = TMember.GetType(); 3 MethodInfo TMethodInfo = T.GetMethod("MemberInfo"); 4 if (TMethodInfo != null) 5 { 6 foreach (Attribute attribute in TMethodInfo.GetCustomAttributes(true)) 7 { 8 AuthorAttribute author = attribute as AuthorAttribute; 9 if (author != null) 10 { 11 Response.Write(author.Name); 12 } 13 } 14 }
(代码片断9)
上面的代码(代码片断9)你完全可以进行进一步的封装以便返回每个公共方法上的AuthorAttribute 的特性。我就不写了。
到这里为止,你可能已经发现另外一个问题了,那就是如果我想指定一个特性只能应用于特定的代码元素上(比如公共方法)该怎么办?
6、控制特性的应用范围
使用AttributeUsage特性可以控件自定义特性的使用。该特性有三个参数:
ValidOn:这是特性应用位置参数,定义了一个自定义特性能够应用到那种编程元素上,是方法还是属性
AllowMultiple:命名参数,它指定一个特性能否在一个程序集中被多次使用
Inherited:命名参数,用于控制一个自定义特性是否能被该类型的子类继承
其中,位置参数ValidOn使用AttributeTargets枚举,该枚举的可取值可以参阅MSDN文档。现在我们使用这个位置参数控制我们上面定义的自定义特性只能用于方法之上。见下面的代码
1 namespace MyClass 2 { 3 /// <summary> 4 /// 标记一个类的作者、注释与电话 5 /// </summary> 6 [AttributeUsage(AttributeTargets.Method)] 7 public class AuthorAttribute : Attribute 8 { 9 private string _Name; 10 11 private string _Note; 12 13 private string _Tel; 14 15 public AuthorAttribute() 16 { 17 // 18 // TODO: 在此处添加构造函数逻辑 19 // 20 } 21 22 public AuthorAttribute(string Name) 23 { 24 _Name = Name; 25 } 26 27 /// <summary> 28 /// 用于获取或设置代码元素的作者 29 /// </summary> 30 public string Name 31 { 32 get 33 { 34 return _Name; 35 } 36 set 37 { 38 _Name = value; 39 } 40 } 41 42 /// <summary> 43 /// 用于获取或设置代码元素的注释 44 /// </summary> 45 public string Note 46 { 47 get 48 { 49 return _Note; 50 } 51 set 52 { 53 _Note = value; 54 } 55 } 56 57 /// <summary> 58 /// 用于获取或设置代码元素作者的电话 59 /// </summary> 60 public string Tel 61 { 62 get 63 { 64 return _Tel; 65 } 66 set 67 { 68 _Tel = value; 69 } 70 } 71 72 /// <summary> 73 /// 用于获取对代码元素的注释 74 /// </summary> 75 /// <returns></returns> 76 public string GetNote() 77 { 78 if (_Note == null) 79 { 80 return "没有添加任何注释!"; 81 } 82 else 83 { 84 return _Note; 85 } 86 } 87 } 88 }
(代码片断10)
然后,我们(代码片断10)上面定义的特性应用到一个方法之上。
1 [Author("henry")] 2 public string MemberInfo(string OutValue) 3 { 4 return "该会员名称:" + _Name + ";该会员所属国家:" + _Country ; 5 }
(代码片断11)
你可以尝试将该特性应用到一个属性会出现什么样的结果。
[AttributeUsage(AttributeTargets.Method)]在使用的时候还可以用“|”将几个不同的AttributeTargets枚举隔开,如
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Property)]
这样,这个特性就可以在属性与方法上同时使用了,如果将AttributeTargets设置为All,则允许这个特性在程序的任何元素上使用。
在默认情况下,特性是不能被子类继承的,如果需要让一个特性可以被子类继承,则需要将AttributeUsage特性的Inherited设置为true,如下代码
[AttributeUsage(AttributeTargets.All,Inherited=true)]
这样,自定义特性将可以被任何程序元素使用,且允许子类继承。
AllowMultiple允许一个特性可被多次的应用到一个代码元素上。但在默认情况下,这是不允许的。见下面的代码
[AttributeUsage(AttributeTargets.ALL,AllowMultiple=true)]
然后在Member类的MemberInfo()应用该特性
1 [Author("Tom")] 2 [Author("henry")] 3 public string MemberInfo(string OutValue) 4 { 5 return "该会员名称:" + _Name + ";该会员所属国家:" + _Country ; 6 }
只能写到这里了,我也很少用这个东西,希望可以与高手一起交流。