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 语 ...
随机推荐
- 洛谷 题解 P1336 【最佳课题选择】
详细解析解题过程 设计状态 dp[i][j]表示前i节课题写j篇论文花费的最少时间 初始数组 for(int i=0;i<=20;i++) for(int j=0;j<=200;j++)d ...
- 常用的API
API (application programming Interface)应用程序编程接口 java 是字典 Scanner类的功能可以实现从键盘输入到程序当中. 引用类型的一般使用步骤 ·1导包 ...
- [Agc030B]Tree Burning_贪心
Tree Burning 题目链接:https://atcoder.jp/contests/agc030/tasks/agc030_b 数据范围:略. 题解: 开始以为是左右左右这样,发现过不去样例. ...
- 添加 godoc 模块
获取godoc源码go get -d golang.org/x/tools/cmd/godoc 或 go get golang.org/x/tools/cmd/godoc 如果 下不到源码,就用43服 ...
- Python+requests维持会话
Python+requests维持会话 一.使用Python+requests发送请求,为什么要维持会话? 我们是通过http协议来访问web网页的,而http协议是无法维持会话之间的状态.比如说我们 ...
- 更改oracle RAC public ip,vip,scan ip和private ip
更改oracle RAC public ip,vip,scan ip和private ip oifcfg - Oracle 接口配置工具 用法: oifcfg iflist [-p [-n]] ...
- Mac安装postgresql和卸载PostgreSQL
1.homebrew安装 brew install postgresql 2.初始化 initdb /usr/local/var/postgres 3.创建数据库及查看数据库 (1)先创建db. cr ...
- 线段树维护动态连续子段HDU1540
题意:http://acm.hdu.edu.cn/showproblem.php?pid=1540 #define IOS ios_base::sync_with_stdio(0); cin.tie( ...
- 笔记-2:python基本数据类型
1.数字类型 1.1 整数类型 整数类型有4种进制表示:十进制,二进制,八进制,十六进制,默认情况下,整数采用十进制. 整数类型有4种进制:十进制. 二进制. 八进制和十六进制. 默认情况, 整数采用 ...
- Python3 中,一行可以书写多个语句,一个语句可以分成多行书写
Python3 中,一行可以书写多个语句 语句之间用分号隔开即可 print('I love you');print('very much!') Python3 中,一个语句可以分成多行书写 一行过长 ...