元数据和反射
大多数程序都要处理数据,包括读、写、操作和显示数据。然而,对于某些程序来说,它们操作的数据不是数字、文本或图形,而是关于程序和程序类型的信息。
1、有关程序及其类型的数据被称为元数据,它们保存在程序的程序集中
2、程序在运行时,可以查看其他程序集或其本身的元数据。运行中的程序查看本身的元数据或其他程序的元数据的行为称为反射
注意:要使用反射,必须使用System.Reflection命名空间
Type类
由于Type是抽象类,因此它不能有实例。在运行时,CLR(运行时环境)创建从Type(RuntimeType)派生的类的实例,Type包含了类型信息。当访问这些实例时,CLR不会返回派生类的引用而是返回Type基类的引用。
关于Type的重要事项
1、对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象
2、不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例
System.Type类的部分成员
Name 属性 返回类型的名字
Namespace 属性 返回包含类型声明的命名空间
Assembly 属性 返回声明类型的程序集。如果类型是泛型的,返回定义这个类型的程序集
GetFields 方法 返回类型的字段类型
GetProperties 方法 返回类型的属性列表
GetMethods 方法 返回类型的方法列表
获取Type对象
还可以使用typeof运算符来获取Type对象。只需要提供类型名作为操作数,它就会返回Type对象的引用
class BaseClass
{
public int BaseField = 0;
}
class DerivedClass : BaseClass
{
public int DerivedField = 0;
}
class Program
{
static void Main(string[] args)
{
var bc = new BaseClass();
var dc = new DerivedClass();
BaseClass[] bca = new BaseClass[] { bc, dc };
foreach (var v in bca)
{
Type t = v.GetType();//获取类型
Console.WriteLine($"Object type :{t.Name}");
//通过FieldInfo类的实例,可以获取和操作类或结构体中的字段信息,包括但不限于字段名称、类型、是否可读写、获取和设置字段值等
//FieldInfo[] fi 是一个FieldInfo类型的数组,用来存储一组FieldInfo实例。当你使用Type.GetFields()方法时,会返回一个包含该类型所有字段的FieldInfo数组。
FieldInfo[] fi = t.GetFields();//获取字段信息
foreach (var f in fi)
{
Console.WriteLine($" Field:{f.Name}");
}
Console.WriteLine();
}
}
}
特性
特性(attribute)是一种允许我们向程序的程序集添加元数据的语言结构,它是用于保存程序结构信息的特殊类型的类。
1、将应用了特性的程序结构叫作目标(target)
2、设计用来获取和使用元数据的程序叫作特性的消费者
3、.NET预定了很多特性,我们也可以声明自定义特性
特性的特点
1、我们在源代码中将特性应用于程序结构
2、编译器获取源代码并且从特性产生元数据,然后把元数据放到程序集中
3、消费者程序可以获取特性的元数据以及程序中其他组件的元数据。
应用特性
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集,可以通过把特性应用到结构来实现
1、通过在结构前放置特性片段来应用特性
2、特性片段由方括号包围特性名和参数列表构成
[Serializable] //特性
public class MyClass
{
}
[MyAttribute("Simple class","Version 3.57")] //带有参数的特性
public class MyOtherClass
{
}
有关特性需要了解的重要事项
1、大多数特性只应用于直接跟随在一个或多个特性片段后的结构
2、应用了特性的结构称为被特性装饰(decorated 或 adorned)
预定义的保留特性
1、Obsolete特性
Obsolete特性将程序结构标注为“过时”,并且在代码编译时显示有用的警告信息
2、Conditional特性
Conditional特性允许我们包括或排斥特定方法的所有调用。要使用Conditional特性,将其应用于方法声明并把编译符作为参数
3、调用者信息特性
利用调用者信息特性可以访问文件路径、代码行数、调用成员的名称等源代码信息
3个特性:CallerFilePath 、CallerLineNumber 、CallerMemberName
DebuggerStepThrough特性
DebuggerStepThrough特性告诉调试器在执行目标代码时不要进入该方法调试
1、该特性位于System.Diagnostics命名控件;
2、该特性可用于类、结构、构造函数、方法或访问器
标志的定义
1、标识符(类名、方法名等)是程序员写给自己或其他程序员看的
2、程序无法通过标识符知道其自身的含义
3、但是我们可以使用标志来标记标识符的含义,程序可以读取标志从而得知一个标识符的含义
4、标志就像一本字典给每个字做的注解
class ABC
{
public ABC(string s)
{
}
}
[ABC("这是标志")] ==> new ABC("这是标志")
反射的定义
反射:在程序运行时可以指导一个类有哪些成员,每个成员方法上有哪些标志、几个参数、每个参数的类型、返回值类型
class CommentAttribute : Attribute
{
public string s;
public CommentAttribute(string s)
{
this.s = s;
}
}
class ABCAttribute : Attribute
{
public string s;
public ABCAttribute(string s)
{
this.s = s;
}
}
//2、可以给类或者方法等加上一个标志
[Comment("游戏对象的基类")]//=>new CommentAtteribute("游戏对象的基类")
class GameObject
{
public int i;
public float H { get; set; }
[Comment("这是一个方法")]
[ABC("这是一个ABC")]
public void f()
{
}
}
class Program
{
static void Main(string[] args)
{
//取得一个类的信息:typeof(类名),返回:类的成员信息类
TypeInfo ti = typeof(GameObject).GetTypeInfo();
//取得类名:
Console.WriteLine(ti.Name);
//取得类中的成员
MemberInfo[] miArray = ti.GetMembers();
for (int i = 0; i < miArray.Length; i++)
{
MemberInfo mi = miArray[i];//取得成员数组中的第i个成员
Console.WriteLine("Name={0},Type={1}",mi.Name,mi.MemberType);//取得该成员的名称
object[] attrs = mi.GetCustomAttributes(false);
//返回一个标志数组,虽然是object数组,但其中存放的内容实际上一定是从Attribute继承的对象
for (int j = 0; j < attrs.Length; j++)
{
object attr = attrs[j];//取得标志数组中的第i个标志
if (attr is CommentAttribute)
{
CommentAttribute ca = (CommentAttribute)attr;
Console.WriteLine(ca.s);
}
else if (attr is ABCAttribute)
{
ABCAttribute abc = (ABCAttribute)attr;
Console.WriteLine(abc.s);
}
}
}
Console.ReadLine();
}
}
GetCustomAttributes(false)
确定是否有自定义标志