溺水动物的C#学习笔记: 反射和特性(Reflection & Attribute)
- 笔者是c#萌新, 如有错漏请指出.
- 会更新(因为我完全没写懂..)
反射(Reflection)
假使我们现在要实现这样一个函数: 函数参数是Object,函数的功能就是执行这个类内所有参数为空的公有成员方法。那么显然, 我们需要运行时获取某个类中的所有成员方法. 反射为我们提供了可以这样做的工具.
您可以使用反射动态的(也就是在程序运行时)创建一个类型的实例, 绑定类型至现有的对象, 或是从现有对象中获取类型.
以下是一个反射利用GetType()的获知某个类型的信息的例子. 因为该方法是继承自Object类所以不需要using System.Reflection.
using System;
//using System.Reflection;
class Application {
public static void Main(string[] args) {
int i = 42;
Type type = i.GetType();
Console.WriteLine(type);
}
}
运行结果如下.
System.Int32
以下是利用反射, 获取某个程序集(Assembly)的信息的例子:
using System;
using System.Reflection;
class Application {
public static void Main(string[] args) {
Assembly info = typeof(int).Assembly;
Console.WriteLine(info);
}
}
运行结果如下:
System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
特性(Attribute)
特性是一个强大的工具用以结合元数据, 或是声明信息.
所谓元数据(Metadata), 就是您在程序中定义的有关类型的信息. 所有基于.NET框架的程序都包含有一个元数据的集合, 用以描述定义在程序集里的类型和类型成员.
特性有如下的特质:
- 特性会在您的程序里添加元数据. 您可以使用特性去找到元数据里您想要的信息.
- 您可以为您的程序提供一个或者多个特性.
- 特性可以像方法 (method) 和属性 (property) 那样接受参数 (arguments).
- 您的程序可以访问自身的元数据, 也可以通过反射 (Reflection) 访问其他程序的元数据.
我觉得你可以简单的把这玩意理解成游戏里武器的附魔词缀, 比方说破旧的长剑什么的
在c#中, 您可以在中括号中写入特性的名称来声明特定的特性.
规定特性的语法如下:
[attribute(positional_parameters, name_parameter = value, ...)]
element
特性(Attribute)的名称和值是在方括号内规定的,放置在它所应用的元素之前。positional_parameters 规定必需的信息,name_parameter 规定可选的信息。
在本例中, 可串行化的特性将在类中加入特定的元素.
[Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}
.NET框架提供了三种预定义特性, 分别是AttributeUsage, AttributeUsage, AttributeUsage.
AttributeUsage
您可以使用AttributeUsage来定义您自己的特性. 具体的内容将会在稍后提及.
它的语法如下:
[AttributeUsage(
validon,
AllowMultiple=allowmultiple,
Inherited=inherited
)]
其中:
- 参数 validon 规定特性可被放置的语言元素。(语言元素就是诸如特性(property), 字段(field), 类(class)之类的). 它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
- 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
- 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)
请看如下的例子:
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
这个特性可以修饰类, 构造函数, 字段, 属性, 方法. 它是多用的.
Conditional
语法如下:
[Conditional(
conditionalSymbol
)]
这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。它会引起方法调用的条件编译.
Obsolete
如果您更新了您代码中的某些部分, 但出于某种原因, 您仍然想保留旧的部分, 呢么您可以将旧的代码标记为Obsolete (过时的)
它的语法规则如下:
[Obsolete(
message
)]
[Obsolete(
message,
iserror
)]
其中
- 参数 message,是一个字符串,您可以在此处提供这个方法已经过时的信息, 并且可以告诉人们应该怎么做.
- 参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。
创建自定义特性
您也可以创建自定义的特性, 用于储存声明性质的信息. 其可以在运行时被检索.
创建并使用自定义特性包含如下四个步骤:
- 声明自定义特性
- 构建自定义特性
- 在目标程序元素上应用自定义特性
- 通过反射访问特性
声明自定义特性
一个新的自定义特性应派生自 System.Attribute 类。
如下是一个创建自定义特性DeBugInfo的语法.
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DeBugInfo : System.Attribute
构建自定义特性
我们继续完成我们声明的DeBugInfo特性. 假设我们希望它储存如下的信息:
- bug 的代码编号
- 辨认该 bug 的开发人员名字
- 最后一次审查该代码的日期
- 一个存储了开发人员标记的字符串消息
显然, 我们希望该特性必须包含前三种信息, 但可以选择是否包含后一种信息. 我们将我们的 DeBugInfo 类将带有三个用于存储前三个信息的私有属性和一个用于存储消息的公有属性。所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,消息将是一个可选的命名(named)参数。
具体语法示例如下:
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DeBugInfo : System.Attribute
{
private int bugNo;
private string developer;
private string lastReview;
public string message;
public DeBugInfo(int bg, string dev, string d)
{
this.bugNo = bg;
this.developer = dev;
this.lastReview = d;
}
public int BugNo
{
get
{
return bugNo;
}
}
public string Developer
{
get
{
return developer;
}
}
public string LastReview
{
get
{
return lastReview;
}
}
public string Message
{
get
{
return message;
}
set
{
message = value;
}
}
}
应用自定义特性
您可以直接将您的自定义特性放在元素之前来应用他们. 如
[DeBugInfo(55, "Zara Ali", "19/10/2012",
Message = "Return type mismatch")]
public double GetArea()
{
return length * width;
}
您也可以利用反射来获得您自己定义的特性.