-
#1. 自定义特性
- 1.1 编写自定义特性
-
#2. 反射
- 2.1 System.Type类
#1. 自定义特性
.NET Framework允许用户自定义自己的特性。显然,这些特性不会影响编译过程,因为编译器不能识别它们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据。
这些元数据在文档说明中非常有用。但是,使自定义特性非常强大的因素是使用反射,代码可以读取这些元数据,使用它们在运行期间作出决策,也就是说,自定义特性可以直接影响代码的运行方式。
1.1 编写自定义特性
为了理解编写自定义特性的方式,应了解一下在编译器遇到代码中的某个应用了自定义特性的元素时,该如何处理。以数据库为例,假定有一个C#属性声明,如下所示。
[FieldName("SocialSecurityNumber")]
public string SocialSecurityNumber {
get {
//etc.
}
}
当C#编译器发现这个属性(property)应用了一个FieldName特性时,首先会把字符串Attribute追加到这个名词的后面,形成一个组合名称FieldNameAttribute,然后在其搜索路径的所有名称空间(即在using语句中提及的名称空间)中搜索有指定名称的类。但要注意,如果用一个特性标记数据项,而该特性的名称以字符串Attribute结尾,编译器就不会把该字符串加到组合名称中,而是不修改该特性名。因此,上面的代码等价于:
[FieldNameAttribute("SocialSecurityNumber")]
public string SocialSecurityNumber {
get {
//etc.
}
}
编译器会找到含有该名称的类,且这个类直接或间接派生自System.Attribute。编译器还认为这个类包含控制特性用法的信息。特别是属性类需要指定:
- 特性可以应用到哪些类型的程序元素上(类、结构、属性和方法等)
- 它是否可以多次应用到用一个程序上
- 特性在应用到类或接口上时,是否由派生类和接口继承
- 这个特性有哪些必选和可选参数
如果编译器找不到对应的特性类,或者找到一个这样的特性类,但使用特性的方式与特性类中的信息不匹配,编译器就会产生一个编译错误。例如,如果特性类指定该特性只能应用于类,但我们把它应用到结构定义上,就会产生一个编译错误。
继续上面的示例,假定定义了一个FieldName特性:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FieldNameAttribute : Attribute
{
private string name;
public FieldNameAttribute(string name)
{
this.name = name;
}
}
下面讨论这个定义中的每个元素:
- AttributeUsage特性
要注意的第一个问题是特性(attribute)类本身用一个特性——System.AttributeUsage特性来标记。AttributeUsage主要用于标识自定义特性可以应用到哪些类型的程序元素上。这些信息是由它的第一个参数给出,该参数是必选的,其类型是枚举类型AttributeTargets。
AllowMultiple参数表示一个特性是否可以多次应用到同一项上,这里把它设置为false,表示如果编译器遇到下述代码,就会产生一个错误:
[FiledName("SocialSecurityNumber")]
[FieldName("NationalInsuranceNumber")]
public string SocialSecurityNumber {
//etc.
}
如果把Inerited参数设置为true,就表示应用到类或接口上的特性也可以自动应用到所有派生类或接口上。如果特性应用到方法或属性上,它就可以应用到该方法或属性等的重写版本上。
- 指定特定参数
下面介绍如何指定自定义特性接受的参数。在编译器遇到下述语句时:
[FieldName("SocialSecurityNumber")]
public string SocialSecurityNumber
{
//etc.
}
编译器会检查传递给特性的参数,并查找该特性中带这些参数的构造函数。如果编译器找到一个这样的构造函数,编译器就会把指定的元数据传递给程序集。如果编译器找不到,就生成一个编译错误。
- 指定特性的可选参数
在AttributeUsage特性中,可以使用另一种语法,把可选参数添加到特性中。这种语法指定可选参数的名称和值,它通过特性类中的公共属性或字段其作用。例如,假定修改SocialSecurityNumber属性的定义,如下所示:
[FieldName("SocialSecurityNumber",Comment="This is the primary key field")]
public string SocialSecuriyNumber
{
//etc.
}
在本例中,编译器识别第二个参数的语法<ParameterName>=<ParameterValue>,并且不会把这个参数传递给FieldNameAttribute类的构造函数,而是查找一个有该名称的公共属性或字段(最好不要使用公共字段,所以一般情况下要使用特性),编译器可以用属性设置第二个参数的值。如果希望上面的代码工作,就必须给FieldNameAttribute类添加一些代码:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FieldNameAttribute : Attribute
{
private string comment;
public string Comment
{
get
{
return comment;
}
set
{
comment = value;
}
}
}
#2. 反射
C#提供了System.Type类,通过这个类可以访问任何数据类型的信息。通过System.Reflection.Assembly类,它可以用于访问给定程序集的相关信息,或者把这个程序集加载到程序中。
2.1 System.Type类
这里使用Type类只为了存储类型的引用:
Type t = typeof(double);
通常,获取指向任何给定类型的Type引用有3种常用方式:
- 使用C#的typeof运算符,这个运算符的参数是类型的名称(但不放在引号中)。
- 使用GetType()方法,所有的类都会从System.Object继承这个方法。
Type是许多反射功能的入口。它实现许多方法和属性。注意,可用的属性都是只读的:可用使用Type确定数据的类型,但不能使用它修改该类型。
- Type的属性
由Type实现的属性可以分为下述3类:
- 许多属性都可以获取包含与类相关的各种名称的字符串。
属性 | 返回值 |
---|---|
Name | 数据类型名 |
FullName | 数据类型的完全限定名(包括命名空间) |
Namespace | 在其中定义数据类型的名称空间名 |
- 属性还可以进一步获取Type对象的引用,这些引用表示相关的类。
属性 | 返回对应的Type应用 |
---|---|
BaseType | 该Type的直接基本类型 |
UnderlyingSystemType | 该Type在.NET运行库中映射到的类型 |
- 许多布尔属性表示这种类型是一个类,还是一个枚举等。
Type intType = typeof(int);
Console.WriteLine(intType.IsAbstract);
Console.WriteLine(intType.IsClass);
Console.WriteLine(intType.IsEnum);
Console.WriteLine(intType.IsPrimitive);
Console.WriteLine(intType.IsValueType);
- 方法
System.Type的大多数方法都用于获取对应数据类型的成员信息:构造函数、属性、方法和事件等。
它有许多方法,但它们都有相同的模式。
返回的对象类型 | 方法(名称为复数形式的方法返回一个数组) |
---|---|
ConstrcutorInfo | GetConstructor()、GetConstructors() |
EventInfo | GetEvent()、GetEvents() |
FieldInfo | GetField()、GetFields() |
InterfaceInfo | GetInterface()、GetInterfaces() |
MemberInfo | GetMember()、GetMembers() |
MethodInfo | GetMethod()、GetMethods() |
PropertyInfo | GetProperty()、GetPropertys() |
网友评论