C#反射的特性

如果您现在对反射还不太了解的话,那么可以先看看这篇博文,来粗略的了解一下反射吧。什么是反射

  反射特性(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 usage=new AttributeUsageAttribute(AttributeTargets.Class)
;
usage.AllowMultiple = true;  // 设置AllowMutiple属性
usage.Inherited = false;// 设置Inherited属性

 但是,特性只写成一行代码,然后紧靠其所应用的类型(目标类型),那么怎么办呢?微软的软件工程师们就想到了这样的办法:不管是构造函数的参数 还是 属性,统统写到构造函数的圆括号中,对于构造函数的参数,必须按照构造函数参数的顺序和类型;对于属性,采用“属性=值”这样的格式,它们之间用逗号分隔。于是上面的代码就减缩成了这样:

[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]

 

可以看出,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, AllowMutiple=true, Inherited=false)]

 而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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/494201.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

拥抱人工智能报告:中国未来就业的挑战与应对

来源: 199IT互联网数据中心近日,中国发展研究基金会联合红杉资本中国基金,对外发布了一份名为《投资人力资本,拥抱人工智能:中国未来就业的挑战与应对》的研究报告。在这篇报告中,研究课题组对比中外、调研…

《Python 快速入门》C站最全Python标准库总结

点赞 ➕ 评论 ➕ 收藏 养成三连好习惯 🍅 联系作者: 不吃西红柿 🍅 作者简介:CSDN 博客专家丨全站 Top 10🏆、HDZ 核心组成员、信息技术智库公号号主 🍅 简历模板、PPT 模板、学习资料、技术互助。关注…

工业4.0进行时:未来协作方式的变革

来源:资本实验室协作是将人类智力发挥至极致的方式,也是推动人类社会进步的重要手段。随着各种新技术的发展与应用,人类之间的协作方式也在随着技术的进步而进步。从面对面交流,到电话与传真、电子邮件与OA系统,再到在…

java之代理设计模式

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托…

lisp中的*,**,***

在lisp中“*”除了乘法的作用外,还被用来保存REPL(read-eval-print-loop)中的返回值。其中 * -> 保存最后一次返回值。 ** -> *的上一次值。 *** -> **的上一次值。 例子如下: 而且  * (car /) ** …

为了帮粉丝完成毕业设计,我发现了一款私活神器

一、缘起 不日前,有粉丝找到我,让我帮着做个: 教师管理系统 由于种种借口,我当时把问题交给群友去解决了..... 思来想去,越想越内疚,于是就请教了经常做私活的小伙伴。 必须分享给更多的小伙伴~ 二、揭开面…

狗脸识别APP整合

本文主要包括以下内容 android studio中导入so文件 通过URI获得Bitmap android studio中导入so文件 在main文件夹下建立jniLibs目录,并将so文件拷贝进去即可。 注意 声明的native方法与so文件中定义的方法的包名必须相同 通过URI获得Bitmap private Bitmap …

解析丰田对自动驾驶汽车的愿景:打造更加安全的汽车

丰田高管约翰莱昂纳德在丰田研究所的麻省理工学院车库,在他身后是研究所改造的一辆雷克萨斯选自:Bloomberg来源: 网易科技参与:乐邦约翰莱昂纳德(John Leonard)漫步走到麻省理工学院(MIT)校园里一间单调乏味的一层车库&#xff0c…

C站最全Python库总结丨标准库+高级库

梦想还是要有的,万一别人问呢? 作者:不吃西红柿 简介:CSDN博客专家、蓝桥签约作者、大数据&Python领域优质创作者。 CSDN私信我,回复【资料】领取: 1、100套小编购买的简历模板; 2、1000套…

DeepMind-深度学习: AI革命及其前沿进展 (54页ppt报告)

来源:专知摘要:2018年9 月 9 日-14 日,DeepMind主办的Deep Learning Indaba 2018 大会在南非斯泰伦博斯举行。会上,牛津大学教授和其他15位专家做了《深度学习: AI革命及其前沿进展》的报告。Nando de FreitasNando de Freitas是一…

❤️ 6个Python办公黑科技,工作效率提升100倍!HR小姐姐都馋哭了(附代码)❤️

🍅 作者:不吃西红柿 🍅 简介:CSDN博客专家🏆、信息技术智库公号作者✌。简历模板、职场PPT模板、技术难题交流、面试套路尽管【关注】私聊我。 🍅 欢迎点赞 👍 收藏 ⭐留言 📝 如有…

苹果未来秘密在这里!从神秘组织到七大技术布局

来源:智东西随着人工智能的艰难发展,智能手机增长的放缓, 苹果公司能否第三次重塑自我?在很多方面,苹果仍然是一家以Steve Jobs个人形象制造的公司,专注于颠覆性产品。但今天,苹果走在了十字路口…

❤️ 爬虫分析CSDN大佬之间关系,堪比娱乐圈 ❤️

🍅 作者主页:不吃西红柿 🍅 简介:CSDN博客专家🏆、信息技术智库公号作者✌简历模板、PPT模板、技术资料尽管【关注】私聊我。历史文章目录:https://t.1yb.co/zHJo 🍅 欢迎点赞 👍 …

SSH框架整合

ssh框架整合步骤如下 提示:myeclipse环境、工程环境、tomcat环境的jdk保持一致 1、新建一个工程,把工程的编码为utf-8 2、把jsp的编码形式改成utf-8 3、把jar包放入到lib下 4、建立三个src folder src 存放源代码 config 存放配置文件 hibernate…

关于未来的10点核心思考

作者:尤瓦尔赫拉利 牛津大学历史学博士,全球瞩目的新锐历史学家来源:《今日简史》世界正在变得越来越复杂,我们正在陷入知识的错觉和群体的无知。同时,我们的生活被社交媒体所塑造,真相早已不存在&#xff…

❤️爆肝3万字,最硬核丨Mysql 知识体系、命令全集 【建议收藏 】❤️

🍅 作者主页:不吃西红柿 🍅 简介:CSDN博客专家🏆、信息技术智库公号作者✌ 简历模板、PPT模板、学习资料、面试题库、技术互助【关注我,都给你】 🍅 欢迎点赞 👍 收藏 ⭐留言 &am…

今天专攻POWERSHELL获取本机CPU,内存消耗

PS脚本如下: 1 $Server $env:computername2 #servers CPU Mem Hardinfor 3 $cpu gwmi –computername $Server win32_Processor 4 $men gwmi -ComputerName $Server win32_OperatingSystem 5 $Disks gwmi –Computer: $Server win32_logicaldisk -filter …

证明黎曼猜想的5页论文已发布!最简洁的解读在这里

来源:潇轩社著名数学家阿蒂亚(Michael Atiyah)公开了他为黎曼猜想做的“简洁证明”,论文长度总共5页。借助量子力学中的无量纲常数α(fine structure constant),阿提亚声称解决了复数域上的黎曼…

《Python顶级入门教程》一步一步,是魔鬼的步伐

目录 🍅 1、欲练此功,先知此人 ⚾ 2、Python 语言特性 ❤ 3、Python 特点 🍅 4、Python 行情如何? ✍ 5、Python 怎么学? 5.1 学理论——懂原理 5.2 做练习——会应用 5.3 团队学习——不懂就问 &#x1f34…

C#/C++/Fortran 在32位/64位下数学计算性能对比

测试平台 在我的上一篇博客中对比了VS2010中C#和C在运算密集型程序中的性能。上一篇博客的链接: http://www.cnblogs.com/ytyt2002ytyt/archive/2011/11/24/2261104.html 当时是在AMD 速龙9650 CPU(4核心)下的测试结果。 随着VS2012、Intel Parallel Studio XE 2013…