微信网站界面,网站建设拍金手指排名贰拾,海外服务器哪家好,室内装修设计师学什么专业如果您现在对反射还不太了解的话#xff0c;那么可以先看看这篇博文#xff0c;来粗略的了解一下反射吧。什么是反射 反射特性#xff08;Attribute#xff09; 1. C#内置特性介绍 特性是一个对象#xff0c;它可以加载到程序集及程序集的对象中#xff0c;这些对象…如果您现在对反射还不太了解的话那么可以先看看这篇博文来粗略的了解一下反射吧。什么是反射 反射特性Attribute 1. C#内置特性介绍 特性是一个对象它可以加载到程序集及程序集的对象中这些对象包括 程序集本身、模块、类、接口、结构、构造函数、方法、方法参数等加载了特性的对象称作特性的目标。特性是为程序添加元数据(描述数据的数据)的一种机制通过它可以给编译器提供指示或者提供对数据的说明。 注意特性的英文名称叫做Attribute在有的书中将它翻译为“属性”另一些书中将它翻译为“特性”由于通常我们将含有get和/或set访问器的类成员称为“属性”(英文Property),所以本文中我将使用“特性”这个名词以区分“属性”(Property)。 上面这个提示是在VS中的大家在编程的过程中应该有遇到过的。 下面我们就引入第一个特性 1.1 System.ObsoleteAttribute 特性 我们通过如图示这个例子来看一下特性是如何解决上面的问题我们可以给旧的SendMsg()方法上面加上Obsolete特性来告诉编译器这个方法已经过时然后当编译器发现当程序中有地方在使用这个用Obsolete标记过的方法时就会给出一个警告信息。 namespace TestObsolete
{class Program{public class Message { }public class TestClass{// 添加Obsolete特性[Obsolete(请使用新的SendMsg(Message msg)重载方法)]public static void ShowMsg(){Console.WriteLine(这是旧的SendMsg()方法);}public static void ShowMsg(Message msg){Console.WriteLine(新SendMsg()方法);}}static void Main(string[] args){TestClass.ShowMsg();TestClass.ShowMsg(new Message());}}
}简单的代码如上。现在运行这段代码我们会发现编译器给出了一个警告警告CS0618: “Attribute.TestClass.ShowMsg()”已过时:“请使用新的SendMsg(Message msg)重载方法”。通过使用特性我们可以看到编译器给出了警告信息告诉客户程序存在一个新的方法可供使用这样程序员在看到这个警告信息后便会考虑使用新的SendMsg()方法。 1.2 特性的使用方法 通过上面的例子我们已经大致看到特性的使用方法首先是有一对方括号“[]”在左方括号“[”后紧跟特性的名称比如Obsolete随后是一个圆括号“()”。和普通的类不同这个圆括号不光可以写入构造函数的参数还可以给类的属性赋值在Obsolete的例子中仅传递了构造函数参数。 注意 实际上当你用鼠标框选住Obsolete然后按下F12转到定义会发现它的全名是ObsoleteAttribute继承自Attribute类。但是这里却仅用Obsolete来标记方法这是.Net的一个约定所有的特性应该均以Attribute来结尾在为对象标记特性时如果没有添加Attribute编译器会自动寻找带有Attribute的版本。 使用构造函数参数参数的顺序必须同构造函数声明时的顺序相同所有在特性中也叫位置参数(Positional Parameters)与此相应属性参数也叫做命名参数(Named Parameters)。在下面会详细说明。 2.自定义特性(Custom Attributes) 2.1范例介绍 如果不能自己定义一个特性并使用它我想你怎么也不能很好的理解特性我们现在就自己构建一个特性。假设我们有这样一个很常见的需求我们在创建或者更新一个类文件时需要说明这个类是什么时候、由谁创建的在以后的更新中还要说明在什么时候由谁更新的可以记录也可以不记录更新的内容以往你会怎么做呢是不是像这样在类的上面给类添加注释 [Record(更新, Leo, 2013-3-20, Memo 修改……)][Record(创建, Amy, 2013-3-10)][Record(更新, aehyok, 2013-3-18)]public class DemoClass{public override string ToString(){return This is a demo class;}}这样的的确确是可以记录下来但是如果有一天我们想将这些记录保存到数据库中作以备份呢你是不是要一个一个地去查看源文件找出这些注释再一条条插入数据库中呢 通过上面特性的定义我们知道特性可以用于给类型添加元数据这些元数据可以用于描述类型。那么在此处特性应该会派上用场。那么在本例中元数据应该是注释类型(“更新”或者“创建”)修改人日期备注信息(可有可无)。而特性的目标类型是DemoClass类。 按照对于附加到DemoClass类上的元数据的理解我们先创建一个封装了元数据的类RecordAttribute public class RecordAttribute{private string recordType; // 记录类型更新/创建private string author; // 作者private DateTime date; // 更新/创建 日期// 构造函数构造函数的参数在特性中也称为“位置参数”。public RecordAttribute(string recordType, string author, string date){this.recordType recordType;this.author author;this.date Convert.ToDateTime(date);}// 对于位置参数通常只提供get访问器public string RecordType { get { return recordType; } }public string Author { get { return author; } }public DateTime Date { get { return date; } }// public string Memo { get; set; }}这个类不光看上去实际上也和普通的类没有任何区别显然不能它因为名字后面跟了个Attribute就摇身一变成了特性。那么怎样才能让它称为特性并应用到一个类上面呢进行下一步之前我们看看.Net内置的特性Obsolete是如何定义的 // 摘要:// 标记不再使用的程序元素。无法继承此类。[Serializable][ComVisible(true)][AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited false)]public sealed class ObsoleteAttribute : Attribute{// 摘要:// 使用默认属性初始化 System.ObsoleteAttribute 类的新实例。public ObsoleteAttribute();//// 摘要:// 使用指定的变通方法消息初始化 System.ObsoleteAttribute 类的新实例。//// 参数:// message:// 描述可选的变通方法的文本字符串。public ObsoleteAttribute(string message);//// 摘要:// 使用变通方法消息和布尔值初始化 System.ObsoleteAttribute 类的新实例该布尔值指示是否将使用已过时的元素视为错误。//// 参数:// message:// 描述可选的变通方法的文本字符串。//// error:// 指示是否将使用已过时的元素视为错误的布尔值。public ObsoleteAttribute(string message, bool error);// 摘要:// 获取指示编译器是否将使用已过时的程序元素视为错误的布尔值。//// 返回结果:// 如果将使用已过时的元素视为错误则为 true否则为 false。默认为 false。public bool IsError { get; }//// 摘要:// 获取变通方法消息包括对可选程序元素的说明。//// 返回结果:// 变通方法文本字符串。public string Message { get; }}
}2.2添加特性的格式位置参数和命名参数 首先我们应该发现它继承自Attribute类这说明我们的RecordAttribute也应该继承自Attribute类。 其次我们发现在这个特性的定义上又用了三个特性去描述它。这三个特性分别是Serializable、AttributeUsage 和 ComVisible。Serializable特性应该主要是用来序列化用的ComVisible简单来说是“控制程序集中个别托管类型、成员或所有类型对 COM 的可访问性”(微软给的定义)。这里我们应该注意到特性本身就是用来描述数据的元数据而这三个特性又用来描述特性所以它们可以认为是“元数据的元数据”(元元数据meta-metadata)。 因为我们需要使用“元元数据”去描述我们定义的特性 RecordAttribute所以现在我们需要首先了解一下“元元数据”。这里应该记得“元元数据”也是一个特性大多数情况下我们只需要掌握 AttributeUsage就可以了所以现在就研究一下它。我们首先看上面AttributeUsage是如何加载到ObsoleteAttribute特性上面的。 [AttributeUsage(8192, Inherited false)]然后我们看一下AttributeUsage的定义 // 摘要:// 指定另一特性类的用法。无法继承此类。[Serializable][ComVisible(true)][AttributeUsage(AttributeTargets.Class, Inherited true)]public sealed class AttributeUsageAttribute : Attribute{// 摘要:// 用指定的 System.AttributeTargets、System.AttributeUsageAttribute.AllowMultiple// 值和 System.AttributeUsageAttribute.Inherited 值列表初始化 System.AttributeUsageAttribute// 类的新实例。//// 参数:// validOn:// 使用按位“或”运算符组合的一组值用于指示哪些程序元素是有效的。public AttributeUsageAttribute(AttributeTargets validOn);// 摘要:// 获取或设置一个布尔值该值指示能否为一个程序元素指定多个指示特性实例。//// 返回结果:// 如果允许指定多个实例则为 true否则为 false。默认为 false。public bool AllowMultiple { get; set; }//// 摘要:// 获取或设置一个布尔值该值指示指示的特性能否由派生类和重写成员继承。//// 返回结果:// 如果该特性可由派生类和重写成员继承则为 true否则为 false。默认为 true。public bool Inherited { get; set; }//// 摘要:// 获取一组值这组值标识指示的特性可应用到的程序元素。//// 返回结果:// 一个或多个 System.AttributeTargets 值。默认为 All。public AttributeTargets ValidOn { get; }}可以看到它有一个构造函数这个构造函数含有一个AttributeTargets类型的位置参数(Positional Parameter。注意ValidOn属性不是一个命名参数因为它不包含set访问器。 这里大家一定疑惑为什么会这样划分参数这和特性的使用是相关的。假如AttributeUsageAttribute 是一个普通的类我们一定是这样使用的 // 实例化一个 AttributeUsageAttribute 类
AttributeUsageAttribute usagenew AttributeUsageAttribute(AttributeTargets.Class)
;
usage.AllowMultiple true; // 设置AllowMutiple属性
usage.Inherited false;// 设置Inherited属性但是特性只写成一行代码然后紧靠其所应用的类型(目标类型)那么怎么办呢微软的软件工程师们就想到了这样的办法不管是构造函数的参数 还是 属性统统写到构造函数的圆括号中对于构造函数的参数必须按照构造函数参数的顺序和类型对于属性采用“属性值”这样的格式它们之间用逗号分隔。于是上面的代码就减缩成了这样 [AttributeUsage(AttributeTargets.Class, AllowMutipletrue, Inheritedfalse)]可以看出AttributeTargets.Class是构造函数参数(位置参数)而AllowMutiple 和 Inherited实际上是属性(命名参数)。命名参数是可选的。将来我们的RecordAttribute的使用方式于此相同。(为什么管他们叫参数我猜想是因为它们的使用方式看上去更像是方法的参数吧。) 假设现在我们的RecordAttribute已经OK了则它的使用应该是这样的 [Record(创建, Amy, 2013-3-10,创建Test)]public class DemoClass其中recordType, author 和 date 是位置参数Memo是命名参数。 2.3 AttributeTargets 位标记 从AttributeUsage特性的名称上就可以看出它用于描述特性的使用方式。具体来说首先应该是其所标记的特性可以应用于哪些类型或者对象。从上面的代码我们看到AttributeUsage特性的构造函数接受一个 AttributeTargets 类型的参数那么我们现在就来了解一下AttributeTargets。 AttributeTargets 是一个位标记它定义了特性可以应用的类型和对象。 // 摘要:// 指定可以对它们应用特性的应用程序元素。[Serializable][Flags][ComVisible(true)]public enum AttributeTargets{// 摘要:// 可以对程序集应用特性。Assembly 1,//// 摘要:// 可以对模块应用特性。Module 2,//// 摘要:// 可以对类应用特性。Class 4,//// 摘要:// 可以对结构应用特性即值类型。Struct 8,//// 摘要:// 可以对枚举应用特性。Enum 16,//// 摘要:// 可以对构造函数应用特性。Constructor 32,//// 摘要:// 可以对方法应用特性。Method 64,//// 摘要:// 可以对属性应用特性。Property 128,//// 摘要:// 可以对字段应用特性。Field 256,//// 摘要:// 可以对事件应用特性。Event 512,//// 摘要:// 可以对接口应用特性。Interface 1024,//// 摘要:// 可以对参数应用特性。Parameter 2048,//// 摘要:// 可以对委托应用特性。Delegate 4096,//// 摘要:// 可以对返回值应用特性。ReturnValue 8192,//// 摘要:// 可以对泛型参数应用特性。GenericParameter 16384,//// 摘要:// 可以对任何应用程序元素应用特性。All 32767,}现在应该不难理解为什么上面我范例中用的是 [AttributeUsage(AttributeTargets.Class, AllowMutipletrue, Inheritedfalse)]而ObsoleteAttribute特性上加载的 AttributeUsage是这样的 [AttributeUsage(8192, Inherited false)]因为AttributeUsage是一个位标记所以可以使用按位或“|”来进行组合。所以当我们这样写时 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)意味着既可以将特性应用到类上也可以应用到接口上。 注意这里存在着两个特例观察上面AttributeUsage的定义说明特性还可以加载到程序集Assembly和模块Module上而这两个属于我们的编译结果在程序中并不存在这样的类型我们该如何加载呢可以使用这样的语法[assembly:SomeAttribute(parameter list)]另外这条语句必须位于程序语句开始之前。 2.4 Inherited 和 AllowMutiple属性 AllowMutiple 属性用于设置该特性是不是可以重复地添加到一个类型上(默认为false)就好像这样 [Record(更新, Leo, 2013-3-20, Memo 修改……)][Record(更新, aehyok, 2013-3-18)][Record(创建, Amy, 2013-3-10)]public class DemoClass{所以我们必须显示的将AllowMutiple设置为True。 Inherited 就更复杂一些了假如有一个类继承自我们的DemoClass那么当我们将RecordAttribute添加到DemoClass上时DemoClass的子类也会获得该特性。而当特性应用于一个方法如果继承自该类的子类将这个方法覆盖那么Inherited则用于说明是否子类方法是否继承这个特性。 在我们的例子中将 Inherited 设为false。 2.5 实现 RecordAttribute 现在实现RecordAttribute应该是非常容易了对于类的主体不需要做任何的修改我们只需要让它继承自Attribute基类同时使用AttributeUsage特性标记一下它就可以了(假定我们希望可以对类和方法应用此特性) [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple true, Inherited false)]public class RecordAttribute:Attribute{private string recordType; // 记录类型更新/创建private string author; // 作者private DateTime date; // 更新/创建 日期// 构造函数构造函数的参数在特性中也称为“位置参数”。public RecordAttribute(string recordType, string author, string date){this.recordType recordType;this.author author;this.date Convert.ToDateTime(date);}// 对于位置参数通常只提供get访问器public string RecordType { get { return recordType; } }public string Author { get { return author; } }public DateTime Date { get { return date; } }// 构建一个属性在特性中也叫“命名参数”public string Memo { get; set; }}2.6 使用 RecordAttribute [Record(更新, Leo, 2013-3-20, Memo 修改……)][Record(更新, aehyok, 2013-3-18)][Record(创建, Amy, 2013-3-10)]public class DemoClass{public override string ToString(){return This is a demo class;}}class Program{static void Main(string[] args){DemoClass demo new DemoClass();Console.WriteLine(demo.ToString());这段程序简单地在屏幕上输出一个“This is a demo class”。我们的属性也好像使用“//”来注释一样对程序没有任何影响实际上我们添加的数据已经作为元数据添加到了程序集中。 3.使用反射查看自定义特性 利用反射来查看 自定义特性信息 与 查看其他信息 类似首先基于类型(本例中是DemoClass)获取一个Type对象然后调用Type对象的GetCustomAttributes()方法获取应用于该类型上的特性。当指定GetCustomAttributes(Type attributeType, bool inherit) 中的第一个参数attributeType时将只返回指定类型的特性否则将返回全部特性第二个参数指定是否搜索该成员的继承链以查找这些属性。 Type t typeof(DemoClass);Console.WriteLine(下面列出应用于 {0} 的RecordAttribute属性, t);// 获取所有的RecordAttributes特性object[] records t.GetCustomAttributes(typeof(RecordAttribute), false);foreach (RecordAttribute record in records){Console.WriteLine( {0}, record);Console.WriteLine( 类型{0}, record.RecordType);Console.WriteLine( 作者{0}, record.Author);Console.WriteLine( 日期{0}, record.Date.ToShortDateString());if (!String.IsNullOrEmpty(record.Memo)){Console.WriteLine( 备注{0}, record.Memo);}}下面来看一下最后的执行效果 这是实例代码下载连接示例代码 转载于:https://www.cnblogs.com/aehyok/archive/2013/03/27/2969010.html