说到C#中的反射机制,我们就必须重点关注type和Assembly两个抽象类的使用。其中Assembly是程序集级别的反射使用,type是类级别的反射使用。下面我们具体从语法用法和实际项目中的应用两个方面来介绍。
一、Type
Type是一个我们可以借此获取类型的一个类,我们通过vs的 go definition可以发现,这个Type是一个抽象类,而且有两千八百多成员方法和三百多成员属性。
type对于一般的应用可能很少涉及,一般人看来无非就是一个获取类型的关键字而已。
那么Type类可以为我们干些什么事呢?
1、typeof函数
最简单的用法就是typeof,这个方法是获取类(参数是类名)信息的方法。用它可以实例化Type类。
Type ty1=typeof(int);
Type ty2 = typeof(Author);
Console.WriteLine(ty1.FullName);
Console.WriteLine(ty2.FullName);
运行结果:
当然,我们也可以将自己编写的类放入typeof中,让它算一算“是哪一路神仙”。
2、GetType函数
我们使用Type中提供的 GetMethods()方法可以清楚的看到,每个对象都有一个GetType函数(这是object基类的固有方法,所以每个类都会有这个方法),无论是系统给的还是自己定义的类,包涵常用的数据类型,如int double bool 等等。
它不同于typeof,GetType是类的实例对象获取类型Type的方法,所以我们也可以用它来实例化Type类。我们一般这么调用;
Author ar = new Author("Tom");
Type ty = (ar).GetType();
Console.WriteLine(ty.FullName);
当然,我们也可以直接通过type类的静态方法GetType()来获取Type的实例对象
Type ty = Type.GetType("System.string", false, true);
3、获取类成员信息
类的成员有很多,我们常用的成员类型有方法、属性、字段、事件,甚至参数都是可以的。这里列举了一些常用成员的获取方法。
Author ar = new Author("Tom");
Type ty = (ar).GetType();
MethodInfo[] methodInfos = ty.GetMethods();
MemberInfo[] memberInfos = ty.GetMembers();
PropertyInfo[] propertyInfos = ty.GetProperties();
FieldInfo[] fieldInfos = ty.GetFields();
Console.WriteLine("FieldInfo information ---------------------------");
foreach (FieldInfo info in fieldInfos)
Console.WriteLine(info);
Console.WriteLine("MethodInfo information ---------------------------");
foreach (MethodInfo info in methodInfos)
Console.WriteLine(info);
Console.WriteLine("MemberInfo information ---------------------------");
foreach (MemberInfo info in memberInfos)
Console.WriteLine(info);
Console.WriteLine("PropertyInfo information ---------------------------");
foreach (PropertyInfo info in propertyInfos)
Console.WriteLine(info);
假定我们设计的Author类是个成员比较丰富的类(这个类的信息可以参考本篇博客最末尾),我们得到的如下的结果:
获取到这些信息,自然是可以为我们后面的程序提供
二、Assembly
Assembly是我们可以借此获取程序集的一个类,我们通过vs的 go definition可以发现,这个Assembly是一个抽象类,而且有一千多个方法,所以我们要想穷举这些方法掌握这些方法基本不可能。我们只能选择一些对于我们来说常用的方法。
我们最想通过她来获取到一个类的实例,因为有了类的实例我们就可以调用类中的方法属性等成员了。
1、获程序集实例
invoke对比代理中的invoke的使用
BeginInvoke
Activate
我们通过一个类的实例对象的GeTType可以获取程序集Assembly信息,这个和通过Assembly的load直接加载程序集是一样的效果。这里以Author类所在的程序集"ConsoleApp1为例,分两种方式加载
Author ar = new Author("Tom");
Type ty = (ar).GetType();
Assembly asm= ty.Assembly;
Console.WriteLine(asm.FullName);
上面的代码显得有点曲折,但是这是通过Type获得Assembly的必须步骤,也就等同于下面一句:
Assembly asm = Assembly.Load("ConsoleApp1");
Console.WriteLine(asm.FullName);
他们的输出都是:
ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
2、实例化程序集中的类
获取类,我们使用的是Type
那么,我们来看看一个程序集中的所有的类
Assembly asm = Assembly.Load("ConsoleApp1");
Type[] types = asm.GetTypes();
foreach(Type ty in types)
{
Console.WriteLine(ty.FullName);
}
我们可以得到下面的输出结果,说明我们的类中有这么多的类
那么单独的类怎么获取以及实例化呢?其中最重要的是构造函数参数信息的获取,否则实例化就会失败。
Type提供了ConstructorInfo来了解构造函数的名称、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors()或GetConstructor()方法来调用特定的构造函数。
ConstructorInfo[] cis = ar.GetType().GetConstructors();
ParameterInfo[] pis;
foreach (ConstructorInfo ci in cis)
{
pis = ci.GetParameters();
foreach (ParameterInfo pi in pis)
Console.WriteLine("parameter detail:\n"+
"name is:"+pi.Name.ToString()+
" type is:"+pi.ParameterType.ToString());
}
输出的结果如下:
我们可以很容易得到ar对象的类构造函数的参数和类型,所以我们就可以用Activator.CreateInstance展开实例化了。
//实例化一个由Gettype获取的类型
Type ty = ar.GetType();
Author obja = (Author)Activator.CreateInstance(ty,"Jack");
obja.mustDo();
3、执行程序集中类的成员函数
这可能是我们使用程序集的终极目标吧!其实上面的代码obja.mustDo()就已经实现了,这里不在赘述了。当然,如果我们的方法是有参数的,那就必须和获取构造函数的参数一样来获取其参数,否则,我们无法调用函数。
MethodInfo[] methodInfos = ty.GetMethods();
ParameterInfo[] pinfs;
foreach (MethodInfo info in methodInfos)
{
pinfs = info.GetParameters();
Console.WriteLine(info.Name.ToString()+ Environment.NewLine);
foreach (ParameterInfo pi in pinfs)
Console.WriteLine("para detail:"+pi.Name+"----"+pi.ParameterType.ToString());
}
这样,我就可以根据我们获取的参数类型来调用函数了。但这里显示出来的方法包括属性中的get 和set的方法。
4、访问私有成员
这里以访问私有字段为例,其他的如属性,方法等都一样。
FieldInfo field = typeof(Human).GetField("name", BindingFlags.NonPublic | BindingFlags.Instance);
string value = field.GetValue(ar).ToString();
//设置私有字段值
field.SetValue(ar, "new Name");
value = field.GetValue(ar).ToString();
Console.WriteLine(value);
三、反射的应用场合
如果单纯的从功能上说反射的应用场合,我们可以教科书式的归纳为以下几点:
1、使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。
2、使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
3、使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors()或GetConstructor()方法来调用特定的构造函数。
4、使用MethodInfo了解方法的名称,返回类型、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods()或GetMethod()方法来调用特定的方法。
5、使用FiledInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。
6、使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。
7、使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。
8、使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。
但这里我更想说的是具体工程和项目中的应用场合。
1、与特性配合对输入进行校验
2、生成查询语句
各类查询语句,如lamda ,linq,sql等语句,甚至正则表达式。
四、本文所引用类的源代码
本文采用的Author类源代码:
interface Creature
{
void Eat();
void Walk(int distance);
string Vocalize();
}
interface Mammal : Creature
{
void BirthBaby();
}
public abstract class Human
{
private string name;
public int age;
public abstract string Name { get; set; }
public Human(string name)
{
this.name = name;
this.age = 0;
}
public void showBaseInfo() { }
public void showFullInfo() { }
public void doSomething()
{
Console.WriteLine(this.name + ": let me do something!");
}
public abstract void mustDo();
public virtual void OtherFun()
{ }
}
public class Author : Human, Mammal
{
private string name;
public string area;
public string mobile;
public Author(string name) : base(name)
{
this.name = name;
}
public void BirthBaby() { }
public void Eat() { }
public void Walk(int distanc) { }
public string Vocalize()
{
return "hoo~~~";
}
public override string Name
{
get { return this.name; }
set { this.name = Name; }
}
public override void mustDo()
{
Console.WriteLine(Name + ": must do!");
}
}
public class Reader : Human
{
private string _name;
public Reader(string name) : base(name) {
this._name = name;
}
public override string Name
{
get { return this._name; }
set { this._name = Name; }
}
public override void mustDo()
{
Console.WriteLine(Name + ": must do!");
//throw new NotImplementedException();
}
}