C#特性学习笔记

元数据,就是C#中封装的一些类,无法修改.类成员的特性被称为元数据中的注释.

1、什么是特性

1)属性与特性的区别

属性(Property):属性是面向对象思想里所说的封装在类里面的数据字段,Get,Set方法。

特性(Attribute): 官方解释:特性是给指定的某一声明的一则附加的声明性信息。 允许类似关键字的描述声明。它对程序中的元素进行标注,如类型、字段、方法、属性等。从.net角度看,特性是一种 类,这些类继承于System.Attribute类,用于对类、属性、方法、事件等进行描述,主要用在反射中。但从面向对象的级别看,其实Attribute是类型级别的,而不是对象级别。

Attributes和.net文件的元素据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响程序的行为。

2、特性的应用

(1).net中特性用来处理多种问题,比如序列化、程序的安全特性、防止即时编译器对程序代码进行优化从而代码容易调试等等。

定植特性的本质上是一个类的元素上去添加附加信息,并在运行其通过反射得到该附加信息(在使用数据实体对象时经常用到)

(2)Attribute 作为编译器的指令时的应用

Conditional:起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译。一般在程序调试的时候使用

DllImport: 用来标记费.net的函数,表明该方法在一个外部的DLL中定义。

Obsolete: 这个属性用来标记当前的方法已经废弃,不再使用

注:Attribute是一个类,因此DllImport也是一个类,Attribute类是在编译的时候实例化,而不是像通常那样在运行时实例化。

CLSCompliant: 保证整个程序集代码遵守CLS,否则编译将报错。

3、自定义特性

使用AttributeUsage,来控制如何应用新定义的特性

[AttributeUsageAttribute(AttributeTargets.All 可以应用到任何元素

,AllowMultiple=true, 允许应用多次,我们的定值特性能否被重复放在同一个程序实体前多次。

,Inherited=false,不继承到派生

)]

特性也是一个类,必须继承于System.Attribute类,命名规范为“类名”+Attribute。不管是直接还是间接继承,都会成为一个特性类,特性类的声明定义了一种可以放置在声明之上新的特性。

public class MyselfAttribute:System.Attribute

自定义特定:

[c-sharp]   view plain copy print ?
  1. //限定特性类的应用范围
  2. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
  3. //定制MsgAttribute特性类,继承于Attribute
  4. public class ClassMsgAttribute : Attribute
  5. {
  6. //定义_msg字段和Msg属性//Msg属性用于读写msg字段
  7. string _msg;
  8. public string Msg { get { return _msg; } set { _msg = value; } }
  9. public ClassMsgAttribute() { }
  10. //重载构造函数接收一个参数,赋值给_msg字段
  11. public ClassMsgAttribute(string s) { _msg = s; } 
  12. }

调用:

  1. //在Person类上标记ClassMsg特性
  2. [ClassMsg(Msg = "这是关于人的姓名信息的类")]
  3. class Person
  4. {
  5. //在_name字段上应用ClassMsg特性
  6. [ClassMsg("这是存储姓名的字段")]
  7. string _name;
  8. //以下特性无法应用,因为MsgAttribute定义的特性只能用于类和字段
  9. //[ClassMsg("这是读写姓名字段的属性")]
  10. public string Name { get { return _name; } set { _name = value; } }
  11. }

主函数的情况

  1. static void Main(string[] args)
  2. {
  3. //获取Person类的Type对象tp
  4. Type tp = Type.GetType("Person");
  5. //获取tp对象的特性数组,并将数组赋值给MyAtts
  6. object[] MyAtts = tp.GetCustomAttributes(false);
  7. //遍历并输出MyAtts数组子项的Msg属性
  8. foreach (ClassMsgAttribute m in MyAtts)
  9. {
  10. Console.WriteLine("Person类的特性:{0}", m.Msg);
  11. }

GetCustomAttributes用于获取程序集的特性,也就是这个程序集合中包含了多少个特性

继续来一个简单的例子来说明定制特性:

using System;

public class HelpAttribute: Attribute //定制特性相当于一个类

{

//...

}

不管你是否相信,我们上面已经建立了一个定制特性,现在我们可以用它来装饰现有的类就好像我们使用的Obsolete attribute一样。

[Help()]

public class AnyClass

{

//...

}

注意:对于一个特性类使用Attribute后缀是一个惯例。然而,如果不添加编译器会自动添加匹配。

定义或控制特性的使用

AttributeUsage类是另外一个预定义特性类,它帮助我们控制我们自己的定制特性的使用。它描述一个定制特性如何被使用。

下面通过实例来探讨下AttributeUsage的三个属性

1)我们将会在刚才的Help特性前放置AttributeUsage特性以期待在它的帮助下控制Help特性的使用。  

  1. using System;
  2.   [AttributeUsage(AttributeTargets.Class), AllowMultiple = false,
  3.   Inherited = false ]
  4.   public class HelpAttribute : Attribute
  5.   {
  6.   public HelpAttribute(String Description_in)
  7.   {
  8.   this.description = Description_in;
  9.   }
  10.   protected String description;
  11.   public String Description
  12.   {
  13.   get
  14.   {
  15.   return this.description;
  16.   }
  17.   }
  18.   }

先让我们来看一下AttributeTargets.Class。它规定了Help特性只能被放在class的前面。这也就意味着下面的代码将会产生错误:

[c-sharp]   view plain copy print ?
  1. [Help("this is a do-nothing class")]
  2. public class AnyClass
  3. {
  4. [Help("this is a do-nothing method")] //error
  5. public void AnyMethod()
  6. {
  7. }
  8. }
  9. //编译器报告错误如下:  
  10. AnyClass.cs: Attribute 'Help' is not valid on this declaration type.
  11. It is valid on 'class' declarations only.   

我们可以使用AttributeTargets.All来允许Help特性被放置在任何程序实体前。可能的值是:

   Assembly,
   Module,
   Class,
   Struct,
   Enum,
   Constructor,
   Method,
   Property,
   Field,
   Event,
   Interface,
   Parameter,
   Delegate,
   All = Assembly   Module   Class   Struct   Enum   Constructor   Method   Property   Field   Event   Interface   Parameter   Delegate,
   ClassMembers = Class   Struct   Enum   Constructor   Method   Property   Field   Event   Delegate   Interface )

下面考虑一下AllowMultiple = false。它规定了特性不能被重复放置多次。  

  1. [Help("this is a do-nothing class")]
  2.   [Help("it contains a do-nothing method")]
  3.   public class AnyClass
  4.   {
  5.   [Help("this is a do-nothing method")] //error
  6.   public void AnyMethod()
  7.   {
  8.   }
  9.   }
  10.   它产生了一个编译期错误。 
  11.   AnyClass.cs: Duplicate 'Help' attribute 

Ok,现在我们来讨论一下最后的这个属性。Inherited, 表明当特性被放置在一个基类上时,它能否被派生类所继承。  

[c-sharp]   view plain copy print ?
  1. [Help("BaseClass")]
  2. public class Base
  3. {
  4. }
  5. public class Derive : Base
  6. {
  7. }
  8. 这里会有四种可能的组合: 
  9. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ]
  10. [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ]
  11. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ]
  12. [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true ]

  第一种情况:  

   如果我们查询(Query)(稍后我们会看到如何在运行期查询一个类的特性)Derive类,我们将会发现Help特性并不存在,因为inherited属性被设置为false。   

   第二种情况:  

   和第一种情况相同,因为inherited也被设置为false。  

   第三种情况:  

   为了解释第三种和第四种情况,我们先来给派生类添加点代码:  

   [Help("BaseClass")]
   public class Base
   {
   }
   [Help("DeriveClass")]
   public class Derive : Base
   {
   }

   现在我们来查询一下Help特性,我们只能得到派生类的属性,因为inherited被设置为true,但是AllowMultiple却被设置为false。因此基类的Help特性被派生类Help特性覆盖了。  

  
 第四种情况: 

   在这里,我们将会发现派生类既有基类的Help特性,也有自己的Help特性,因为AllowMultiple被设置为true.

实例探讨:

自定义了一个特性类:

[c-sharp]   view plain copy print ?
  1. [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
  2. class WahAttribute:System.Attribute
  3. {
  4. private string description;
  5. public string Description
  6. {
  7. get { return description; }
  8. set { description = value; }
  9. }
  10. private string author;
  11. public string Author
  12. {
  13. get { return author; }
  14. set { author = value; }
  15. }
  16. public WahAttribute(string desc)
  17. {
  18. this.description = desc;
  19. }
  20. }

运用特性类:

[c-sharp]   view plain copy print ?
  1. namespace attributeDemo
  2. {
  3. public class Teacher
  4. {
  5. private string name;
  6. public string Name
  7. {
  8. get { return name; }
  9. set { name = value; }
  10. }
  11. private int age;
  12. public int Age
  13. {
  14. get { return age; }
  15. set { age = value; }
  16. }
  17. private string sex;
  18. public string Sex
  19. {
  20. get { return sex; }
  21. set { sex = value; }
  22. }
  23. //只有用户名为wah的才可以调用此方法
  24. [Wah("this is my attribute test", Author = "wah", Description = "test")]
  25. public string getMessage()
  26. {
  27. return "好好学习,天天向上";
  28. }
  29. [Wah("this is with parameters test",Author="wanggaihui",Description="test with parameters")]
  30. public int Test(int a,int b)
  31. {
  32. return a+b;
  33. }
  34. }
  35. }

处理特性类:

[c-sharp]   view plain copy print ?
  1. private void button_Click(object sender, EventArgs e)
  2. {
  3. //得到类型
  4. Type type = typeof(Teacher);
  5. //得到此类型所有方法
  6. MethodInfo[] methods = type.GetMethods();
  7. foreach (MethodInfo method in methods)
  8. {
  9. //得到此方法的所有特性
  10. object[] attributes = method.GetCustomAttributes(false);
  11. foreach (object o in attributes)
  12. {
  13. //判断是否是自己定义的特性
  14. if (o.GetType() == typeof(WahAttribute))
  15. {
  16. //强转取得值
  17. WahAttribute waha = (WahAttribute)o;
  18. this.listBox1.Items.Add("author=" + waha.Author);
  19. this.listBox1.Items.Add("description=" + waha.Description);
  20. }
  21. }
  22. }
  23. }

C#特性可以应用于各种类型和成员。加在类前面的是类特性,加在方法前面的是方法特性。无论他们被用在哪里,无论他们之间有什么区别,特性的最主要的目的就是自描述。并且因为特性是可以由自己定制的,而不仅仅局限于.net提供的那几个现成的,因此给C#程序开发带来了很大的灵活性。

我们还是借用生活中的例子来介绍C#的特性机制吧。

假设有一天你去坐飞机,你就必须提前去机场登机处换登机牌。登机牌就是一张纸,上面写着哪趟航班、由哪里飞往哪里以及你的名字、座位号等等信息,其实,这就是特性。它不需要你生理上包含这些属性(人类出现那会儿还没飞机呢),就像上面的HumanBase类没有IsSerializable这样的属性,特性只需要在类或方法需要的时候加上去就行了,就像你不总是在天上飞一样。

拿到了登机牌,就意味着你可以合法地登机起飞了。但此时你还不知道你要坐的飞机停在哪里,不用担心,地勤人员会开车送你过去,但是他怎么知道你是哪趟航班的呢?显然还是通过你手中的登机牌。所以,特性最大的特点就是自描述。

既然是起到描述的作用,那目的就是在于限定。就好比地勤不会把你随便拉到一架飞机跟前就扔上去了事,因为标签上的说明信息就是起到限定的作用,限定了目的地、乘客和航班,任何差错都被视为异常。如果前面的HumanBase不加上Serializable特性就不能在网络上传输。

http://developer.51cto.com/art/200908/147097.htm

http://www.bccn.net/Article/net/cs/jszl/200709/6160_2.html

指定特性参数

如果找到这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到,就生成一个这样的构造函数。如果找到一个这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到就生成一个编译错误。如后面所述,反射会从程序集中读取元数据,并实例化他们表示的特性类。因此,编译器需要确保存在这样的构造函数,才能在运行期间实例化指定的特性。

---------------------

可选参数 vs. 命名参数

可选参数是attribute类构造函数的参数。它们是强制的,必须在每次在attribute绑定至某语言元素时提供一个值。而另一方面,命名参数倒是真正的可选参数,不是在attribute构造函数的参数。

为了更加详细的解释,让我们在Help类中添加另外的属性。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false ,

Inherited
 = false
)]

public class
  HelpAttribute : Attribute

{

public
  HelpAttribute(String Description_in)

{

this.description =
  Description_in;

this.verion = "No Version is defined for this class"
;

}

protected
  String description;

public
  String Description

{

get
 

{

return this
.description;

}

}

protected
  String version;

public
  String Version

{

get
 

{

return this
.version;

}

//
if we ever want our attribute user to set this property,

//we must specify set method for it
 

set  

{

this.verion =
  value;

}

}

}

[Help(
"This is Class1"
)]

public class
  Class1

{

}



[Help(
"This is Class2", Version = "1.0"
)]

public class
  Class2

{

}



[Help(
"This is Class3", Version = "2.0"
,

Description
 = "This is do-nothing class"
)]

public class
  Class3

{

}

当我们在

Class1中查询Help attribute已经它的属性,我们将得到:

Help.Description : This is Class1

Help.Version :No Version is defined for this class

因为我们没有为Version这个属性定义任何任何值,所以在构造函数中设定的值被我们查询出来了。如果没有定义任何值,那么就会赋一个该类型的默认值(例如:如果是int型,默认值就是0)。

现在,查询Class2的结果是:

Help.Description : This is Class2

Help.Version : 1.0

我们不能为了可选参数而使用多个构造函数,应该用已命名参数来代替。我们之所以称它们为已命名的,是因为当我们在构造函数为它们提供值时,我们必须命名它们。例如,在第二个类中,我们如是定义Help

[Help("This is Class2", Version = "1.0")]

 AttributeUsage 例子中, 参数”ValidOn”是可选参数,而“Inherited““AllowMultiple“ 是命名参数。

注意:为了在attribute的构造函数中设定命名参数的值,我们必须为相应的属性提供一个set方法否则会引起编译期错误:

'Version' : Named attribute argument can't be a read only property

现在,我们在Class3中查找Help attribute 及其属性会发生什么呢?结果是跟上面提到的相同的编译期错误。

'Desciption' : Named attribute argument can't be a read only property

现在我们修改下Help类,为属性”Description”加一个set方法。现在的输出就是:

Help.Description : This is do-nothing class

Help.Version : 2.0

在屏幕后面究竟发生了什么呢?首先带有可选参数的构造函数被调用,然后,每个命名参数的set方法被调用,在构造函数中赋给命名参数的值被set方法所覆写。

参数类型

一个attribute类的参数类型被限定在如下类型中:

  • bool,
  • byte,
  • char,
  • double,
  • float,
  • int,
  • long,
  • short,
  • string
  • System.Type
  • object
  • An enum type, provided that it and any types in which it is nested are publicly accessible. A one-dimensional array involving any of the types listed above

Attributes 标记

假设,我们想把Help attribute 绑定至元素 assembly。第一个问题是我们要把Help attribute 放在哪儿才能让编译器确定该attribute是绑定至整个assembly呢?考虑另一种情况,我们想把attribute绑定至一个方法的返回类型上,怎样才能让编译器确定我们是把attribute绑定至方法的返回类型上,而不是整个方法呢?

为了解决诸如此类的含糊问题,我们使用attribute标识符,有了它的帮助,我们就可以确切地申明我们把attribute 绑定至哪一个语言元素。

例如:

[assembly: Help("this a do-nothing assembly")]

这个在Help attribute 前的assembly标识符确切地告诉编译器,该attribute被绑定至整个assembly。可能的标识符有:

  • assembly
  • module
  • type
  • method
  • property
  • event
  • field
  • param
  • return

在运行时查询Attributes

现在我们明白怎么创建attribtes和把它们绑定至语言元素。是时候来学习类的使用者该如何在运行时查询这信息。

为了查询一语言元素上绑定的attributes,我们必须使用反射。反射有能力在运行时发现类型信息。

我们可以使用.NET Framework Reflection APIs 通过对整个assembly元数据的迭代,列举出assembly中所有已定义的类,类型,还有方法。

记住那旧的Help attribute AnyClass 类。

using   System;

using
  System.Reflection;

using
  System.Diagnostics;



//attaching Help attribute to entire assembly


[assembly : Help(
"This Assembly demonstrates custom attributes  

creation and their run
-time query.")]



//our custom attribute class

public class   HelpAttribute : Attribute

{

public
  HelpAttribute(String Description_in)

{

//


// TODO: Add constructor logic here

this.description =   Description_in;

//

}

protected   String description;

public
  String Description

{

get
 

{

return this
.deescription;



}

}

}

//attaching Help attribute to our AnyClass


[HelpString(
"This is a do-nothing Class." )]

public class
  AnyClass

{

//attaching Help attribute to our AnyMethod


[Help(
"This is a do-nothing Method." )]

public void
  AnyMethod()

{

}

//attaching Help attribute to our AnyInt Field


[Help(
"This is any Integer." )]

public int
  AnyInt;

}

class
  QueryApp

{

public static void
  Main()

{

}

}

我们将在接下来的两节中在我们的

Main方法中加入attribute查询代码。

查询程序集的Attributes

在接下来的代码中,我们先得到当前的进程名称,然后用Assembly类中的LoadForm()方法加载程序集,再有用GetCustomAttributes()方法得到被绑定至当前程序集的自定义attributes,接下来用foreach语句遍历所有attributes并试图把每个attribute转型为Help attribute(即将转型的对象使用as关键字有一个优点,就是当转型不合法时,我们将不需担心会抛出异常,代之以空值(null)作为结果),接下来的一行就是检查转型是否有效,及是不是为空,跟着就显示Help attribute的“Description”属性。

class   QueryApp

{

public static void
  Main()

{

HelpAttribute HelpAttr;


//Querying Assembly Attributes


String assemblyName;

Process p
  =   Process.GetCurrentProcess();

assemblyName
 = p.ProcessName + ".exe"
;


Assembly a
 =
  Assembly.LoadFrom(assemblyName);


foreach (Attribute attr in a.GetCustomAttributes(true
))
{

HelpAttr
 = attr as
  HelpAttribute;

if (null !=
  HelpAttr)

{

Console.WriteLine(
"Description of {0}:\n{1}"
,

assemblyName,HelpAttr.Description);

}

}

}

}

程序输出如下:

Description of QueryAttribute.exe:

This Assembly demonstrates custom attributes creation and

their run-time query.

Press any key to continue

查询类、方法、类成员的Attributes

下面的代码中,我们惟一不熟悉的就是Main()方法中的第一行。

Type type = typeof(AnyClass);

它用typeof操作符得到了一个与我们AnyClass类相关联的Type型对象。剩下的查询类attributes代码就与上面的例子是相似的,应该不要解释了吧(我是这么想的)。

为查询方法和类成员的attributes,首先我们得到所有在类中存在的方法和成员,然后我们查询与它们相关的所有attributes,这就跟我们查询类attributes一样的方式。

class   QueryApp

{

public static void
  Main()

{



Type type
 = typeof
(AnyClass);

HelpAttribute HelpAttr;





//Querying Class Attributes


foreach (Attribute attr in type.GetCustomAttributes(true ))

{

HelpAttr
 = attr as
  HelpAttribute;

if (null !=
  HelpAttr)

{

Console.WriteLine(
"Description of AnyClass:\n{0}"
,

HelpAttr.Description);

}

}

//Querying Class-Method Attributes
 

foreach(MethodInfo method in   type.GetMethods())

{

foreach (Attribute attr in method.GetCustomAttributes(true
))

{

HelpAttr
 = attr as
  HelpAttribute;

if (null !=
  HelpAttr)

{

Console.WriteLine(
"Description of {0}:\n{1}"
,

method.Name,

HelpAttr.Description);

}

}

}

//Querying Class-Field (only public) Attributes


foreach(FieldInfo field in   type.GetFields())

{

foreach (Attribute attr in field.GetCustomAttributes(true
))

{

HelpAttr
= attr as
  HelpAttribute;

if (null !=
  HelpAttr)

{

Console.WriteLine(
"Description of {0}:\n{1}"
,

field.Name,HelpAttr.Description);

}

}

}

}

}

The output of the following program is.

Description of AnyClass:

This is a do-nothing Class.

Description of AnyMethod:

This is a do-nothing Method.

Description of AnyInt:

This is any Integer.

Press any key to continue

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值