C# 反射 (15)
本章要点
- 自定义特性
- 反射
自定义特性运行把自定义元数据与程序元素关联起来。这些元数据时再编译过程中创建的并嵌入到程序集中。
反射是计算机术语,它描述在运行过程中检查和处理程序元素的功能。
反射允许完成以下任务:
- 枚举类型的成员
- 实例化新对象
- 执行对象的成员
- 查找类型的信息
- 查找程序集的信息
- 检查应用于某些类型的自定义特性
- 创建和编译新程序集
程序的各个数据项上定义特性。这些特性都是Microsoft定义好的,作为.NET Framework类库的一部分,许多特性都得到了C#编译器的支持。对于这些特殊的特性,编译器可以以特殊的方式定制编译过程,例如,可以根据StructLayout特性中的信息在内存中布置结构。
.NET FrameWork 也允许用户定义自己的特性。显然,这些特性不会影响编译过程,因为这些编译器不能识别它们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据。
使用自定义特性非常强大的因素是使用反射,代码可以读取这些元数据,使用它们在运行期间作出决策。也就是说,自定义特性可以直接影响代码运行的方式。例如,自定义特性可以用于支持对自定义许可类进行声明性的代码访问安全检查,把信息与程序元素关联起来,程序元素由测试工具使用,或者在开发可扩展的架构时,允许加载插件或模块。
编写自定义特性
[FieldName("SocialSecurityNumber")]
public string SocialSecurityNumber
{
get { return ""; }
} [FieldNameAttribute("SocialSecurityNumber")]
当C#编译器发现这个属性(property)应用了 FieldName 特性时,首先会把字符串 Attribute 追加到这个名称的后面,形成一个组合名称 FieldNameAttribute ,然后在其搜索路径的所有名称空间(即在using语句中提及的名称空间)中搜索指定名称的类。但要注意,如果用一个特性标记数据项,而该特性的名称以字符串 Attribute 结尾,编译器就不会把该字符串加到组合名称中,而是不修改该特性名。
编译器会找到含有该名称的类,且这个类直接或间接派生自 System.Attribute。编译器还认为这个类包含控制特性用法的信息。特别是属性类需要指定:
- 特性可以应用到那些类型的程序元素上(类、结构、属性和方法等)
- 它是否可以多次应用到同一个程序元素上
- 特性在应用到类或接口上时,是否由派生类和接口继承
- 这个特性有哪些必选和可选参数
如果编译器找不到对应的特性类,或者找到一个这样的特性类,但使用特性的方式与特性类中的信息不匹配,编译器就会产生编译错误。例如,如果特类指定该特性只能应用于类,但我们把它应用到结构定义上,就会产生一个编译错误。
定义一个 FieldName 特性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FieldNameAttribute : Attribute
{
private string name; public FieldNameAttribute(string name)
{
this.name = name;
}
}
1.AttributeUsage特性
AttributeUsage主要用于标识自定义特性可以应用到那些类型的程序元素上。它这些信息由它第一个参数给出,其类型是枚举类型 AttributeTargets。AttributeTargets成员如下:
成员名称 | 说明 | |
---|---|---|
All |
可以对任何应用程序元素应用属性。 |
|
Assembly |
可以对程序集应用属性。 |
|
Class |
可以对类应用属性。 |
|
Constructor |
可以对构造函数应用属性。 |
|
Delegate |
可以对委托应用属性。 |
|
Enum |
可以对枚举应用属性。 |
|
Event |
可以对事件应用属性。 |
|
Field |
可以对字段应用属性。 |
|
GenericParameter |
可以对泛型参数应用属性。(仅在 .net 2.0 以上提供。) |
|
Interface |
可以对接口应用属性。 |
|
Method |
可以对方法应用属性。 |
|
Module |
可以对模块应用属性。 |
|
Parameter |
可以对参数应用属性。 |
|
Property |
可以对属性 (Property) 应用属性 (Attribute)。 |
|
ReturnValue |
可以对返回值应用属性。 |
|
Struct |
可以对结构应用属性,即值类型。 |
上述,两个成员不对应于任何程序元素 Assembly 和 Moudle。它们分别应用到 整个程序集 或 模块 中。这种特性可以放在源代码的任何地方,但需要用 Assembly 或 Moudle 作为前缀:
[assembly: SomeAssemblyAttribute(Parameters)]
[moudle: SomeAssemblyAttribute(Parameters)]
在指定自定义特性的有效目标元素时,可以使用按位 or 运算符,把这些值组合起来。
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
当让也可以用 AttributeTargets.All 应用到所有类型的程序元素上。
AttributeUsage 特性还包含另外两个参数 AllowMultiple 和 Inherited 。
AllowMultiple 参数表示一个特性是否可以多次应用到同一项。如果把它设置为报错,下面编译器就会报错。
[FieldName("SocialSecurityNumber")]
[FieldName("NationalInsuranceNumber")]
public string SocialSecurityNumber
{
如果把 Inherited 参数设置为 true,就表示应用到类或接口上的特性也可以自动应用到所有派生的类或接口上。如果特性应用到方法或属性上,它就可以自动应用到该方法或属性等的重写版本上。
2.指定特性参数
指定自定义特性接受的参数,比如下述语句:
[FieldName("SocialSecurityNumber")]
public string SocialSecurityNumber
{
get { return ""; }
}
编译器会检查传递给特性的参数,并查抄该特性中带着些参数的构造函数。如果编译器找到一个这样的构造函数,编译器就会把指定的元数据传递给程序集。如果编译器找不到,就生成一个编译错误。如后面所述,反射会从程序集中读取元数据(特性),并实例化它们表示的特性类。因此,编译器需要确保存在这样的构造函数,才能在运行期间实例化指定的特性。
在本例中,仅为 FieldNameAttribute 类提供了一个构造函数,而这个构造函数有一个字符串参数。因此,在把 FieldName 特性应用到一个属性上时,必须为它提供一个字符串参数,如上面的代码所示。
如果可以选择特性提供的参数类型,就可以提供构造函数的不同重载方法,尽管一般是仅提供一个构造函数,使用属性来定义任何其他可选参数,下面将介绍可选参数。
3.指定特性的可选参数
在 ArrributeUsage 特性中,可以使用另一个中语法,把可选参数添加到特性中。这种语法指定可选参数的名称和值,它通过特性类中的公共属相或字段起作用。例如假定修改 SocialSecurityNumber属性的定义。如下所示:
[FieldName("SocialSecurityNumber", CodeComment = "This is the primary key field")]
public string SocialSecurityNumber
{
编译器识别第二个参数的语法<ParameterName>=<ParameterValue>,并且不会把这个参数传递给 FieldNameAttribute 类的构造函数,而是查找一个有该名称的公共属性或字段(最好不要使用公共字段,所以一般情况下要使用特性),编译器可以用这个属性设置第二个参数的值。所以必须给 FieldNameAttribute 修改如下
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FieldNameAttribute : Attribute
{
private string name; private string comment; public FieldNameAttribute(string name)
{
this.name = name;
} public string Comment
{
get { return comment; }
set { comment = value; }
}
}
C# 反射 (15)的更多相关文章
- 【.Net】net 反射15分钟速成
概述 什么是反射 Reflection,中文翻译为反射. 这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’.‘模块(Module)’. ...
- <NET CLR via c# 第4版>笔记 第15章 枚举类型和位标志
15.1 枚举类型 枚举定义的符号是常量值. C#编译器编译时,会用数值替换符号,不再引用定义了符号的枚举类型.可能会出现一些版本问题. Enum.IsDefined(Type enumType, o ...
- C#高级编程笔记(11至16章)异步/托管/反射/异常
11.1.2LINQ语句 LINQ查询表达式以from子句开始,以select或者group子句结束.在这两个子句之间可以跟零个或者多个from.let.where.join或者orderby子句. ...
- Python3学习之路
python基础知识点 1.python基础知识点汇总 2.python常用数据类型 3.python之列表 4.python之字符串 5.python常用数据运算符 6.python之字典 7.py ...
- C#高级编程(第8版)
http://spu.jd.com/11328513.html 第1章 .NET体系结构1.1 C#与.NET的关系1.2 公共语言运行库1.2.1 平台无关性1.2.2 提高性能1.2.3 语言的互 ...
- c#高级语言编程(第一部分)
1.一步一步学c#(一):.NET体系结构 2.一步一步学c#(二):核心c# 3.一步一步学c#(三):对象和类型 4.一步一步学c#(四):继承 5.一步一步学c#(五):泛型 6.一步一步学c# ...
- C#高级编程第9版 阅读笔记(一)
一.前言 C# 简洁.类型安全的面向对象的语言. .NET是一种在windows平台上编程的架构——一种API. C#是一种从头开始设计的用于.NET的语言,他可以利用.NET Framework及其 ...
- C#高级编程(第九版) 知识点梳理
---恢复内容开始--- 第二章 核心C# 2.7 命名空间可以使用别名,但是这样做有什么好处? 2.12 C#预处理器指令 #define DEBUG #if DEBUG Console.Write ...
- C#语言
封面 书名 版权 前言 目录 第Ⅰ部分 C#语言 第1章 NET体系结构 1.1 C#与.NET的关系 1.2 公共语言运行库 1.2.1 平台无关性 1.2.2 提高性能 1.2.3 语 ...
随机推荐
- 最新 顺网科技java校招面经 (含整理过的面试题大全)
从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.顺网科技得等10家互联网公司的校招Offer,因为某些自身原因最终选择了顺网科技.6.7月主要是做系统复习.项目复盘.Lee ...
- [mysql] C++操作mysql方法
下载:http://mirrors.sohu.com/mysql/MySQL-5.5/ From: http://www.cnblogs.com/magicsoar/p/3817518.html C+ ...
- 洛谷 题解 P4955 【[USACO14JAN]Cross Country Skiing 越野滑雪】
二分+DFS 看到这么多大佬写了并查集,BFS的,还没有人写DFS版的,那么肯定是要来水水积分的啦毕竟这可是道伪紫题呢! 做法楼上楼下也讲得很清楚了吧,详见代码的注释 #include<bits ...
- Web前端基础(一)--HTML简介
HTML简介 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言. HTML文档的后缀名:.html和.htm,这两种后缀名没有 ...
- Mybatis传递List集合
完整错误如下: org.apache.ibatis.binding.BindingException: Parameter ‘customerIdList’ not found. Available ...
- lua数据类型的的操作(三)
上一章我们学习了lua的数据类型,以及语法的定义,今天我们学习lua的数据类型操作,其实就是lua库一些api的操作,遇到对数据类型处理时,可以根据lua库提供的操作来实现. 一.字符串操作 1.字符 ...
- 浅谈 JDBC 中 CreateStatement 和 PrepareStatement 的区别与优劣
先说下这俩到底是干啥的吧.其实这俩干的活儿都一样,就是创建了一个对象然后去通过对象调用executeQuery方法来执行sql语句.说是CreateStatement和PrepareStatement ...
- centos7.4安装图形界面及报错处理
笔者实验环境:centos 7.4.1708,安装时默认最小化安装. 安装命令: yum -y update #如果不想升级内核版本可以参考笔者另一篇文章,关于update跟upgrade的区别yum ...
- 新拉的项目在idea中启动时报如下错误:org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start:
今天真的是很苦恼,之前启动项目没有任何问题,今天突然启动时给我报了如下一个错误. 详细报错信息: org.apache.catalina.core.ContainerBase.addChildInte ...
- vue去掉链接中的#
在router.js中修改, const router = new VueRouter({ mode: 'history', routes: [...] })