C# 特性详解
特性提供功能强大的方法,用以将元数据或声明信息与代码(程序集、类型、方法、属性等)相关联。这些元数据是在编译过程中创建,并嵌入到程序集中。特性与程序实体关联后,即可在运行时使用名为“反射”的技术查询特性。反射是个普通术语,它描述了在运行过程中检查和处理程序元素的功能。
特性具有以下属性:
-
特性可向程序中添加元数据。元数据是有关在程序中定义的类型的信息。所有的 .NET 程序集都包含指定的一组元数据,这些元数据描述在程序集中定义的类型和类型成员。可以添加自定义特性,以指定所需的任何附加信息。
-
可以将一个或多个特性应用到整个程序集、模块或较小的程序元素(如类和属性)。
-
特性可以与方法和属性相同的方式接受参数。
- 程序可以使用反射检查自己的元数据或其他程序内的元数据。
元数据是什么?
你注意过程序及编译的时候的pdb文件了吗?pdb文件里面存储了,关于程序集内部的所有的成员信息,例如,成员的数据类型,属性类型,方法返回值,方法入参类型,就是程序及内部所有的定义信息的数据信息,是存储定义信息的一类数据信息,程序集里面的所有的关于声明类的数据信息,包括方法间调用,都是存储在元数据里面。
详细查看:http://www.cnblogs.com/DswCnblog/p/5344119.html
特性可以放置在几乎所有的声明中。在 C# 中,特性的指定方法为:将括在方括号中的特性名置于其应用到的实体的声明上方,一个元素上面可以放置多个特性
[System.Serializable] public class SampleClass { // Objects of this type can be serialized. }
根据约定,所有特性名称都以单词“Attribute”结束,以便将它们与“.NET Framework”中的其他项区分。在代码中使用特性,C#编译器会把字符串Attribute追加到这个特性名称后面,然后在其搜索路径的所有名称空间中搜索指定名称的类。如果该特性的名称已字符串Attribute结尾,编译器就不会把字符串加到组合名称中。
预定义特性(Attribute)
.Net 框架提供了三种预定义特性:
- AttributeUsage
- Conditional
- Obsolete
AttributeUsage特性
预定义特性 AttributeUsage 主要用于标示自定义特性可以应用到哪些类型的程序元素上,这个信息由第一个参数给出。
规定该特性的语法如下:
[AttributeUsage( validon, //规定特性可被放置的语言元素,它是枚举器 AttributeTargets 的值的组合,默认值是 AttributeTargets.All AllowMultiple=allowmultiple, //如果为 true,则该特性可以在同一个元素多次使用,默认值是 false(不可多次使用) Inherited=inherited //如果为 true,则该特性可被派生类继承,默认值是 false(不被继承) )]
例如:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
AttributeTargets :
成员名称 | 说明 | |
---|---|---|
Assembly | 可以对程序集应用特性。 | |
Module | 可以对模块应用特性。
注意
Module 指的是可移植的可执行文件(.dll 或 .exe),而非 Visual Basic 标准模块。
|
|
Class | 可以对类应用特性。 | |
Struct | 可以对结构应用特性,即值类型。 | |
Enum | 可以对枚举应用特性。 | |
Constructor | 可以对构造函数应用特性。 | |
Method | 可以对方法应用特性。 | |
Property | 可以对属性应用特性。 | |
Field | 可以对字段应用特性。 | |
Event | 可以对事件应用特性。 | |
Interface | 可以对接口应用特性。 | |
Parameter | 可以对参数应用特性。 | |
Delegate | 可以对委托应用特性。 | |
ReturnValue | 可以对返回值应用特性。 | |
GenericParameter | 可以对泛型参数应用特性。
注意
目前,此特性可以应用仅于 C#、Microsoft 中间语言 (MSIL) 和发出的代码。
|
|
All | 可以对任何应用程序元素应用特性。 |
按照上面的经验,再次开始动手来熟悉这一切,我指定了该自定义的特性不可继承,就在不解释别的了只是为了证明一下命名参数Inherited定性成功与否,总之还是很简单的。
namespace { class Program { static void Main(string[] args) { GetAttributeInfo(typeof(OldClass)); Console.WriteLine("=============="); GetAttributeInfo(typeof(NewClass)); Console.ReadKey(); } public static void GetAttributeInfo(Type t) { OldAttribute myattribute = (OldAttribute)Attribute.GetCustomAttribute(t, typeof(OldAttribute)); if (myattribute == null) { Console.WriteLine(t.ToString()+"类中自定义特性不存在!"); } else { Console.WriteLine("特性描述:{0}\n加入事件{1}", myattribute.Discretion, myattribute.date); } } } [AttributeUsage(AttributeTargets.Class,Inherited=false)] //设置了定位参数和命名参数 class OldAttribute : Attribute //继承自Attribute { private string discretion; public string Discretion { get { return discretion; } set { discretion = value; } }
public DateTime date; public OldAttribute(string discretion) { this.discretion = discretion; date = DateTime.Now; } }
//现在我们定义两类 [Old("这个类将过期")]//使用定义的新特性 class OldClass { public void OldTest() { Console.WriteLine("测试特性"); } }
class NewClass:OldClass { public void NewTest() { Console.WriteLine("测试特性的继承"); } } //我们写一个方法用来获取特性信息 }
运行效果:
Conditional
这个预定义特性标记了一个条件方法,其执行依赖于它顶的预处理标识符。
它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。例如,当调试代码时显示变量的值。
#define DEBUG //定义DEBUG宏,则下面有输出,不定义则没有输出 using System; using System.Diagnostics; public class Myclass { [Conditional("DEBUG")] public static void Message(string msg) { Console.WriteLine(msg); } }
class Test { static void function1() { Myclass.Message("In Function 1."); function2(); } static void function2() { Myclass.Message("In Function 2."); } public static void Main() { Myclass.Message("In Main function."); function1(); Console.ReadKey(); } }
ObsoleteAttribute
这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。
public ObsoleteAttribute(string message, bool error)
参数 类型:
message System ..::.String 描述可选的变通方法的文本字符串。
error System ..::.Boolean 指示是否将使用已过时的元素视为错误的布尔值。
using System; public class MyClass { [Obsolete("Don't use OldMethod, use NewMethod instead", true)] static void OldMethod() { Console.WriteLine("It is the old method"); } public static void Main() { OldMethod(); //将会报错 } }
自定义特性
通过定义一个特性类,该特性类直接或间接地从Attribute派生,有助于方便快捷地在元数据中标识特性定义。特性类本身用一个特性-System.AttributeUsage特性来标记,这个是Microsoft定义的特性,AttributeUsage主要用于表示自定义特性可以应用到哪些类型的程序上。这些信息由它的第一个参数给出,该参数是必选的,其类型是枚举类型AttributeTarget。
一个新的自定义特性应派生自 System.Attribute 类,例如:
// 一个自定义特性 BugFix 被赋给类及其成员 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute //已经声明了一个名为 DeBugInfo 的自定义特性。
让我们构建一个名为 DeBugInfo 的自定义特性,该特性将存储调试程序获得的信息。它存储下面的信息:
- bug 的代码编号
- 辨认该 bug 的开发人员名字
- 最后一次审查该代码的日期
- 一个存储了开发人员标记的字符串消息
我们的 DeBugInfo 类将带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(property)。所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,消息将是一个可选的命名(named)参数。
每个特性必须至少有一个构造函数。必需的定位( positional)参数应通过构造函数传递。下面的代码演示了 DeBugInfo 类:
// 一个自定义特性 BugFix 被赋给类及其成员 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class DeBugInfo : System.Attribute { private int bugNo; private string developer; private string lastReview; public string message; public DeBugInfo(int bg, string dev, string d) { this.bugNo = bg; this.developer = dev; this.lastReview = d; } public int BugNo { get { return bugNo; } } public string Developer { get { return developer; } } public string LastReview { get { return lastReview; } } public string Message { get { return message; } set { message = value; } } }
应用自定义特性
通过把特性放置在紧接着它的目标之前,来应用该特性:
[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")] [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")] class Rectangle { // 成员变量 protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")] public double GetArea() { return length * width; } [DeBugInfo(56, "Zara Ali", "19/10/2012")] public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }