C#反射和特性2 特性
特性(attribute)是一种允许我们想程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。
将应用了特性的程序结构叫做目标(target)。
设计用来获取和使用元数据的程序叫做特性的消费者(consumer)。
.Net预定了很多特性,我们也可以声明自定义特性。
开发人员将特性应用于程序,编译器获取源代码并从特性中产生元数据,然后把元数据放到程序集中,消费者程序可以获取特性的元数据以及程序中其他组件的元数据。
特性通常以Attribute作为后缀,当为目标应用特性时,可以不使用后缀。
几个官方特性:
1、Obsolete,用于告知程序已经过期(提示警告或错误)
2、Conditional,允许包括或排斥特定方法的所有调用。为方法声明应用Conditional特性并把编译符作为参数来使用
class Program { [Obsolete("This method is obsolute")] static void PrintOut(string str) { Console.WriteLine(str); } [Conditional("DoTrace")] static void TraceMessage(string str) { Console.WriteLine(str); } static void Main(string[] args) { TraceMessage("Start of Main"); PrintOut("hello world"); TraceMessage("End of Main"); } }
被标注了Obsolete的程序结构会被标注为过期,但代码仍然可以正常运行,只是编译时显示对应的过期警告提示,如果想不删除这个程序结构但也不希望任何人使用,则提供:
[Obsolete("This method is obsolute",true)]
它会在编译时显示该程序结构的调用是错误的。
被标注了Conditional("xx")的程序结构会在编译时被编译器跳过,除非在using上面定义宏
#define Dotrace
3、调用者信息特性
static void MyTrace(string message,[CallerFilePath]string fileName="",[CallerLineNumber]int lineNumber=0,[CallerMemberName]string callingMember="") { Console.WriteLine("File:{0}", fileName); Console.WriteLine("Line:{0}", lineNumber); Console.WriteLine("Called From:{0}", callingMember); Console.WriteLine("Message:{0}", message); } ... MyTrace("Simple message");
File:E:\Study\C#Concepts\Attribute\Attribute\Program.cs
Line:33
Called From:Main
Message:Simple Message
CallerFilePath,CallerLineNumber,CallerMemberName用于访问文件路径、代码行数、调用成员的名称等源代码信息,但这些特性智能用于方法中的可选参数。
4、调试时跳过某些代码
[DebugStepThrough]
5、序列化
[Serializable]特性本身并不序列化,它只是指示这个对象可以被序列化。
6、自定义特性
要声明一个自定义特性,需要作如下工作:
A、声明一个派生自System.Attribute的类,通常还带有sealed保证安全性
B、起一个带有Attribute作为后缀的名字
C、能持有的公共成员只能是字段、属性、构造函数
[AttributeUsage(AttributeTargets.Class)]
public sealed class MyAttributeAttribute:System.Attribute { public string Description { get; set; } public string Version { get; set; } public MyAttributeAttribute(string desc,string version) { Description = desc; Version = version; } }
[Serializable] [MyAttribute("Class Attribute","Version 1.0.0")] class Attribute { public int Id { get; set; } }
在使用自定义特性时,应注意以下实践:
1、特性类应该标识目标结构的一些状态
2、如果特性需要某些字段,可以通过包含具有位置参数的构造函数来收集数据,可选字段可以采用命名参数来按需初始化。
3、除了属性外,不实现公共方法或其他函数成员
4、特性类声明为密封类
5、在特性声明中使用AttributeUsage来显式指定特性目标组
可以通过Type对象的IsDefined来检查某个特性是否应用到了某个类上。
Type t = attribute.GetType(); bool isDefined = t.IsDefined(typeof(MyAttributeAttribute), false); if (isDefined) Console.WriteLine($"MyAttribute is applied to type {t.Name}");
输出:MyAttribute is applied to type Attribute
也可以:
object[] attrArr = t.GetCustomAttributes(false); foreach(MyAttributeAttribute item in attrArr) { MyAttributeAttribute attr = item as MyAttributeAttribute; if(null!=attr) { Console.WriteLine($"Decription:{attr.Description}"); Console.WriteLine($"Version:{attr.Version}"); } }
输出:
Decription:Class Attribute
Version:Version 1.0.0