> 文章列表 > C#中的反射和特性

C#中的反射和特性

C#中的反射和特性

目录

一、元数据和反射

二、Type类

三、获取Type类对象

1. 使用GetType方法获取Type对象

2.使用typeof运算符获取Type对象

四、特性

1. 什么是特性

2.应用特性

 3.预定义特性

1. Obsolete特性

2. Conditional特性

 3.调用者信息特性

4.DebuggerStepThrough特性

 5.其他预定义特性

6.多个特性

7.将特性应用到其他类型的目标

 8.全局特性

4.自定义特性 

1.声明自定义特性

2.使用特性的构造函数

3.指定构造函数

4.使用构造函数

 5.限制特性的使用

6.自定义特性的最佳实现准则

5.访问特性


一、元数据和反射

  1. 元数据:保存在程序集中有关程序及其类型的数据。
  2. 反射:程序在运行时,查看其他程序集或本身的元数据的行为叫做反射。(使用反射必须使用System.Reflection)

二、Type类

BCL声明了一个叫做Type的抽象类,被设计用来包含类型的特性,使用这个类的对象能让我们获取程序使用的类型的信息。

  1. 对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象。
  2. 程序中用到的每一个类型,都会关联到独立的Type类的对象。
  3. 不管创建的同一个类型有多少个实例,只有一个Type对象会关联到这些所有实例。

三、获取Type类对象

1. 使用GetType方法获取Type对象

因为所有类派生自object,所以任何类型对象可以调用GetType()方法获取它的Type对象。

格式:

 代码:

    class BaseClass {public int BaseField = 0;}class DerivedClass : BaseClass {public int DerivedField = 0;}internal class Program{static void Main(string[] args){var bc = new BaseClass();var dc = new DerivedClass();BaseClass[] bca = new BaseClass[] { bc, dc };foreach (var a in bca) {Type t = a.GetType();//获取类型Console.WriteLine("Type对象:{0}",t.Name);FieldInfo[] fieldInfos = t.GetFields();//获取类型中所有字段信息foreach (FieldInfo fi in fieldInfos)Console.WriteLine("     Field : {0}",fi.Name);//获取字段名Console.WriteLine();}}}

结果:

图解:

 

2.使用typeof运算符获取Type对象

格式:

代码:

    class BaseClass {private int privateBaseField;//私有字段public int BaseField;}class DerivedClass : BaseClass {private int privateDerivedField;//私有字段public int DerivedField;}internal class Program{static void Main(string[] args){Type t = typeof(DerivedClass);//获取类型Console.WriteLine("类型对象:{0}",t.Name);FieldInfo[] fieldInfos = t.GetFields();//获取类型对象所有非私有字段foreach (FieldInfo f in fieldInfos) Console.WriteLine("     字段:{0}",f.Name);}}

 结果:

四、特性

1. 什么是特性

特性是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。

  1. 将应用了特性的程序结构叫做目标。
  2. 设计用来获取和使用元数据的程序叫做特性的消费者。
  3. 有预定义特性,也可以自定义特性。
  4. 特性使用Pascal命名法并以Attribute后缀结尾。当应用特性时可以不使用特性。

2.应用特性

特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。

  1. 在结构前放置特性片段来应用特性。
  2. 特性片段格式:[特性名和特性的参数列表] 
  3. 大多数特性只针对直接跟随在一个多个特性片段后结构。
  4. 应用了特性的结构称为被特性装饰(decorated或adorned)

下面的代码演示了两个类的开始部分。最初的几行代码演示了把一个叫做Serializable的特性应用到MyClass。注意,Serializable没有参数列表。第二个类的声明有一个叫做MyAttribute的特性,它有一个带有两个string参数的参数列表。

 3.预定义特性

1. Obsolete特性

使用Obsolete可以将程序结构标为过时的,但仍可继续使用。

 使用Obsolete的重载特性,将程序结构标记为错误的,不能再使用。

2. Conditional特性

Conditional允许我们包括或排斥特定方法的所有调用(搭配预处理指令编译符号使用)。

 

 3.调用者信息特性

  1. 三个特性名称为CallerFilePath、CallerLineNumber和ICallerMemberName。
  2. 这些特性只能用于方法中的可选参数。

4.DebuggerStepThrough特性

控制debug时不进入某些方法。可作用在类、结构、构造函数、方法或访问器。

 5.其他预定义特性

6.多个特性

我们可以为单个结构应用多个特性。

  1. 方式1:独立的特性片段相互叠在一起。
  2. 方式2:单个特性片段,特性之间使用逗号分隔。 

 

7.将特性应用到其他类型的目标

除了类,我们还可以将特性应用到诸如字段和属性等其他程序结构。例如:

我们还可以显式地标注特性,从而将它应用到特殊的目标结构。要使用显式目标,在特性片段的开始处放置目标类型,后面跟冒号。例如,如下的代码用特性装饰方法,并且还把特性应用到返回值上。

 

特性覆盖的目标:

 

 8.全局特性

我们还可以通过使用assembly和module目标名称来使用显式目标说明符把特性设置在程序集或模块级别。一些有关程序集级别的特性的要点如下:

  1. 程序级级别的特性必须放置在任何命名空间之外,并且通常放置在AssemblyInfo.cs文件中
  2. AssemblyInfo.cs文件通常包含有关公司、产品以及版权信息的元数据。

4.自定义特性 

特性只是某个特殊类型的类。

  1. 用户自定义的特性类叫做自定义特性
  2. 所有特性类都派生自System.Attribute

1.声明自定义特性

  1. 声明一个派生自System.Attribute的类
  2. 类名称以Attribute结尾
  3. 特性类的公共成员只能是字段、属性、构造函数

2.使用特性的构造函数

  1. 和其他类一样,如果不声明构造函数,编译器隐式提供公共无参的构造函数。
  2. 特性的构造函数也可重载
  3. 声明构造函数时必须使用类的全名称,包括后缀Attribute

3.指定构造函数

列在特性应用中的参数就是传给特性构造函数的参数。

  1. 应用特性时,构造函数的实参必须是编译期能确定值的常量表达式。
  2. 应用特性时,没有参数可以省略圆括号。 

 

4.使用构造函数

特性也可以使用位置参数和命名参数,应用特性是一条声明语句,它不会决定什么时候构造特性类的对象。 (构造函数需要的任何位置参数都必须放在命名参数之前)

 5.限制特性的使用

可以使用预定义类AttributeUsage特性,限制某个特性使用在某个目标类型上:

AttributeUsage特性重要的三个公共属性:

 

我们可以按位或来组合使用类型,但只能接受AttributeTarget枚举成员,下例被装饰的特性只能用在方法和构造函数上:

AttributeTarget枚举成员:

 

6.自定义特性的最佳实现准则

  1. 特性类应该表示目标结构的一些状态。
  2. 如果特性需要某些字段,可以通过包含具有位置参数的构造函数来收集数据,可选字段可以采用命名参数按需初始化。
  3. 除了属性之外,不要实现公共方法或其他函数成员。
  4. 为了更安全,把特性类声明为sealed。
  5. 在特性声明中使用AttributeUsage来显式指定特性目标组。

5.访问特性

我们可以使用反射,获取特性类型信息。

  1. Type中的IsDefined方法:某个特性是否引用到了某个类上。
  2. Type中的GetCustomAttributes方法:返回应用到结构的特性的数组。

(注:内容学习总结自《C#图解教程》)