元数据,就是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。不管是直接还是间接继承,都会成为一个特性类,特性类的声明定义了一种可以放置在声明之上新的特性。
代码如下:
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; namespace CollectionsApplication { //1. 自定义特定: //限定特性类的应用范围 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] //定制MsgAttribute特性类,继承于Attribute public class ClassMsgAttribute : Attribute { //定义_msg字段和Msg属性//Msg属性用于读写msg字段 string _msg; public string Msg { get { return _msg; } set { _msg = value; } } public ClassMsgAttribute() { } //重载构造函数接收一个参数,赋值给_msg字段 public ClassMsgAttribute(string s) { _msg = s; } } //2. 调用: //在Person类上标记ClassMsg特性 [ClassMsg(Msg = "this is the class for name attribute")] class Person { //在_name字段上应用ClassMsg特性 [ClassMsg("this is name attribute.")] string _name; //以下特性无法应用,因为MsgAttribute定义的特性只能用于类和字段 //[ClassMsg("这是读写姓名字段的属性")] public string Name { get { return _name; } set { _name = value; } } } public class Test { //3. 主函数情况: static void Main(string[] args) { //获取Person类的Type对象tp Type tp = typeof(Person); //获取tp对象的特性数组,并将数组赋值给MyAtts object[] MyAtts = tp.GetCustomAttributes(true); //object[] MyAtts = typeof(Person).GetCustomAttributes(false); //遍历并输出MyAtts数组子项的Msg属性 foreach (ClassMsgAttribute m in MyAtts) { Console.WriteLine("Class Person attribute:{0}", m.Msg); } Console.ReadLine(); } //运行结果: //class person attribute:this is the class for name attribute } }
GetCustomAttributes用于获取程序集的特性,也就是这个程序集合中包含了多少个特性
继续来一个简单的例子来说明定制特性:
using System;
public class HelpAttribute: Attribute //定制特性相当于一个类
{
//...
}
不管你是否相信,我们上面已经建立了一个定制特性,现在我们可以用它来装饰现有的类就好像我们使用的Obsolete attribute一样。
[Help()]
public class AnyClass
{
//...
}
注意:对于一个特性类使用Attribute后缀是一个惯例。然而,如果不添加编译器会自动添加匹配。
定义或控制特性的使用
AttributeUsage类是另外一个预定义特性类,它帮助我们控制我们自己的定制特性的使用。它描述一个定制特性如何被使用。
下面通过实例来探讨下AttributeUsage的三个属性
1)我们将会在刚才的Help特性前放置AttributeUsage特性以期待在它的帮助下控制Help特性的使用。
using System; [AttributeUsage(AttributeTargets.Class), AllowMultiple = false, Inherited = false ] public class HelpAttribute : Attribute { public HelpAttribute(String Description_in) { this.description = Description_in; } protected String description; public String Description { get { return this.description; } } }
先让我们来看一下AttributeTargets.Class。它规定了Help特性只能被放在class的前面。这也就意味着下面的代码将会产生错误:
[Help("this is a do-nothing class")] public class AnyClass { [Help("this is a do-nothing method")] //error public void AnyMethod() { } } //编译器报告错误如下: AnyClass.cs: Attribute 'Help' is not valid on this declaration type. 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。它规定了特性不能被重复放置多次。
[Help("this is a do-nothing class")] [Help("it contains a do-nothing method")] public class AnyClass { [Help("this is a do-nothing method")] //error public void AnyMethod() { } } 它产生了一个编译期错误。 AnyClass.cs: Duplicate 'Help' attribute
Ok,现在我们来讨论一下最后的这个属性。Inherited, 表明当特性被放置在一个基类上时,它能否被派生类所继承。
[Help("BaseClass")] public class Base { } public class Derive : Base { } 这里会有四种可能的组合: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false ] [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false ] [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true ] [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。
自定义了一个特性类:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)] class WahAttribute:System.Attribute { private string description; public string Description { get { return description; } set { description = value; } } private string author; public string Author { get { return author; } set { author = value; } } public WahAttribute(string desc) { this.description = desc; } }
运用特性类:
namespace attributeDemo { public class Teacher { private string name; public string Name { get { return name; } set { name = value; } } private int age; public int Age { get { return age; } set { age = value; } } private string sex; public string Sex { get { return sex; } set { sex = value; } } //只有用户名为wah的才可以调用此方法 [Wah("this is my attribute test", Author = "wah", Description = "test")] public string getMessage() { return "好好学习,天天向上"; } [Wah("this is with parameters test",Author="wanggaihui",Description="test with parameters")] public int Test(int a,int b) { return a+b; } } }
处理特性类:
private void button_Click(object sender, EventArgs e) { //得到类型 Type type = typeof(Teacher); //得到此类型所有方法 MethodInfo[] methods = type.GetMethods(); foreach (MethodInfo method in methods) { //得到此方法的所有特性 object[] attributes = method.GetCustomAttributes(false); foreach (object o in attributes) { //判断是否是自己定义的特性 if (o.GetType() == typeof(WahAttribute)) { //强转取得值 WahAttribute waha = (WahAttribute)o; this.listBox1.Items.Add("author=" + waha.Author); this.listBox1.Items.Add("description=" + waha.Description); } } } }
C#特性可以应用于各种类型和成员。加在类前面的是类特性,加在方法前面的是方法特性。无论他们被用在哪里,无论他们之间有什么区别,特性的最主要的目的就是自描述。并且因为特性是可以由自己定制的,而不仅仅局限于.net提供的那几个现成的,因此给C#程序开发带来了很大的灵活性。
我们还是借用生活中的例子来介绍C#的特性机制吧。
假设有一天你去坐飞机,你就必须提前去机场登机处换登机牌。登机牌就是一张纸,上面写着哪趟航班、由哪里飞往哪里以及你的名字、座位号等等信息,其实,这就是特性。它不需要你生理上包含这些属性(人类出现那会儿还没飞机呢),就像上面的HumanBase类没有IsSerializable这样的属性,特性只需要在类或方法需要的时候加上去就行了,就像你不总是在天上飞一样。
拿到了登机牌,就意味着你可以合法地登机起飞了。但此时你还不知道你要坐的飞机停在哪里,不用担心,地勤人员会开车送你过去,但是他怎么知道你是哪趟航班的呢?显然还是通过你手中的登机牌。所以,特性最大的特点就是自描述。
既然是起到描述的作用,那目的就是在于限定。就好比地勤不会把你随便拉到一架飞机跟前就扔上去了事,因为标签上的说明信息就是起到限定的作用,限定了目的地、乘客和航班,任何差错都被视为异常。如果前面的HumanBase不加上Serializable特性就不能在网络上传输。
指定特性参数
如果找到这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到,就生成一个这样的构造函数。如果找到一个这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到就生成一个编译错误。如后面所述,反射会从程序集中读取元数据,并实例化他们表示的特性类。因此,编译器需要确保存在这样的构造函数,才能在运行期间实例化指定的特性。
参考:
https://msdn.microsoft.com/zh-cn/library/67ef8sbd(v=vs.80).aspx
http://zhidao.baidu.com/link?url=FArDop5J7k64OP9APCOZRfRNJ-Y6QcAvI0Y5Dt-4bXh-lxhpImjC1ugmxAv6il9Pc-blnU9PPnsQLpZNr-575q
http://blog.csdn.net/helloguonan/article/details/5912032
https://msdn.microsoft.com/zh-cn/library/z0w1kczw(v=vs.110).aspx
http://www.cnblogs.com/rohelm/archive/2012/04/19/2456088.html