目录
一、元数据和反射
- 元数据:保存在程序集中有关程序及其类型的数据。
- 反射:程序在运行时,查看其他程序集或本身的元数据的行为叫做反射。(使用反射必须使用System.Reflection)
二、Type类
BCL声明了一个叫做Type的抽象类,被设计用来包含类型的特性,使用这个类的对象能让我们获取程序使用的类型的信息。
- 对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象。
- 程序中用到的每一个类型,都会关联到独立的Type类的对象。
- 不管创建的同一个类型有多少个实例,只有一个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. 什么是特性
特性是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。
- 将应用了特性的程序结构叫做目标。
- 设计用来获取和使用元数据的程序叫做特性的消费者。
- 有预定义特性,也可以自定义特性。
- 特性使用Pascal命名法并以Attribute后缀结尾。当应用特性时可以不使用特性。
2.应用特性
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。
- 在结构前放置特性片段来应用特性。
- 特性片段格式:[特性名和特性的参数列表]
- 大多数特性只针对直接跟随在一个多个特性片段后结构。
- 应用了特性的结构称为被特性装饰(decorated或adorned)
下面的代码演示了两个类的开始部分。最初的几行代码演示了把一个叫做Serializable的特性应用到MyClass。注意,Serializable没有参数列表。第二个类的声明有一个叫做MyAttribute的特性,它有一个带有两个string参数的参数列表。
3.预定义特性
1. Obsolete特性
使用Obsolete可以将程序结构标为过时的,但仍可继续使用。
使用Obsolete的重载特性,将程序结构标记为错误的,不能再使用。
2. Conditional特性
Conditional允许我们包括或排斥特定方法的所有调用(搭配预处理指令编译符号使用)。
3.调用者信息特性
- 三个特性名称为CallerFilePath、CallerLineNumber和ICallerMemberName。
- 这些特性只能用于方法中的可选参数。
4.DebuggerStepThrough特性
控制debug时不进入某些方法。可作用在类、结构、构造函数、方法或访问器。
5.其他预定义特性
6.多个特性
我们可以为单个结构应用多个特性。
- 方式1:独立的特性片段相互叠在一起。
- 方式2:单个特性片段,特性之间使用逗号分隔。
7.将特性应用到其他类型的目标
除了类,我们还可以将特性应用到诸如字段和属性等其他程序结构。例如:
我们还可以显式地标注特性,从而将它应用到特殊的目标结构。要使用显式目标,在特性片段的开始处放置目标类型,后面跟冒号。例如,如下的代码用特性装饰方法,并且还把特性应用到返回值上。
特性覆盖的目标:
8.全局特性
我们还可以通过使用assembly和module目标名称来使用显式目标说明符把特性设置在程序集或模块级别。一些有关程序集级别的特性的要点如下:
- 程序级级别的特性必须放置在任何命名空间之外,并且通常放置在AssemblyInfo.cs文件中
- AssemblyInfo.cs文件通常包含有关公司、产品以及版权信息的元数据。
4.自定义特性
特性只是某个特殊类型的类。
- 用户自定义的特性类叫做自定义特性
- 所有特性类都派生自System.Attribute
1.声明自定义特性
- 声明一个派生自System.Attribute的类
- 类名称以Attribute结尾
- 特性类的公共成员只能是字段、属性、构造函数
2.使用特性的构造函数
- 和其他类一样,如果不声明构造函数,编译器隐式提供公共无参的构造函数。
- 特性的构造函数也可重载
- 声明构造函数时必须使用类的全名称,包括后缀Attribute
3.指定构造函数
列在特性应用中的参数就是传给特性构造函数的参数。
- 应用特性时,构造函数的实参必须是编译期能确定值的常量表达式。
- 应用特性时,没有参数可以省略圆括号。
4.使用构造函数
特性也可以使用位置参数和命名参数,应用特性是一条声明语句,它不会决定什么时候构造特性类的对象。 (构造函数需要的任何位置参数都必须放在命名参数之前)
5.限制特性的使用
可以使用预定义类AttributeUsage特性,限制某个特性使用在某个目标类型上:
AttributeUsage特性重要的三个公共属性:
我们可以按位或来组合使用类型,但只能接受AttributeTarget枚举成员,下例被装饰的特性只能用在方法和构造函数上:
AttributeTarget枚举成员:
6.自定义特性的最佳实现准则
- 特性类应该表示目标结构的一些状态。
- 如果特性需要某些字段,可以通过包含具有位置参数的构造函数来收集数据,可选字段可以采用命名参数按需初始化。
- 除了属性之外,不要实现公共方法或其他函数成员。
- 为了更安全,把特性类声明为sealed。
- 在特性声明中使用AttributeUsage来显式指定特性目标组。
5.访问特性
我们可以使用反射,获取特性类型信息。
- Type中的IsDefined方法:某个特性是否引用到了某个类上。
- Type中的GetCustomAttributes方法:返回应用到结构的特性的数组。
(注:内容学习总结自《C#图解教程》)