概述反射
• 通过反射可以提供类型信息,从而使得我们开发人员在运行时能够利用这些信息构造和使用对象。
• 反射机制允许程序在执行过程中动态地添加各种功能。
运行时类型标识
•运行时类型标识(RTTI),可以在程序执行期间判定对象类型。例如使用它能够确切地知道基类引用指向了什么类型对象。
•运行时类型标识,能预先测试某个强制类型转换操作,能否成功,从而避免无效的强制类型转换异常。
•在c#中有三个支持RTTI的关键字:is 、 as 、typeof。 下面依次介绍他们
is运算符:
通过is运算符,能够判断对象类型是否为特顶类型,如果两种类型是相同类型,或者两者之间存在引用,装箱拆箱转换,则表明两种类型是兼容的。
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new B();
if (a is A)
{
Console.WriteLine("a is an A"); //这个打印,因为a 是 A 类型的对象
}
if (b is A)
{
//这个打印,因为b是B类型的对象,而B类型派生于A类型,由于b对象可以转换为A类型,因此b对象与A类型是兼容的,但是反过来就不成立,例如下面不打印
Console.WriteLine("b is an A because it is derived from");
}
if (a is B)
{
//这个不打印
Console.WriteLine("This won't display , because a not derived from B");
}
if (a is object)
{
//这个打印
Console.WriteLine("a is an object");
}
Console.ReadKey();
}
}
class A { }
class B : A { }
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new B();
if (a is B)
{
b = (B)a; //由于a变量不是B类型,因此这里将a变量转换为B类型是无效的。
}
else
{
b = null;
}
if (b ==null)
{
//这个打印
Console.WriteLine("The cast in b=(B)a is not allowed");
}
//上面使用as运算符,能够把两部合二为一。
b = a as B; //as类型先检查强制类型转换的有效性,如果有效,则执行强类型转换过程。这些都在这一句完成。
if (b == null)
{
//这个打印
Console.WriteLine("The cast in b=(B)a is not allowed");
}
Console.ReadKey();
}
}
class A { }
class B : A { }
static void Main(string[] args)
{
Type t=typeof(StringBuilder);
Console.WriteLine(t.FullName); //FullName属性返回类型的全称
if (t.IsClass)
{
Console.WriteLine("is a class"); //打印
}
if (t.IsSealed) //是否为密封类
{
Console.WriteLine("is Sealed"); //打印
}
Console.ReadKey();
}
MemberInfo类中的只读属性 | |
属性 | 描述 |
Type DeclaringType | 获取声明该成员的类或接口的类型 |
MemberTypes MemberType | 获取成员的类型,这个值用于指示该成员是字段、方法、属性、事件、或构造函数 |
Int MetadataToken | 获取与特定元数据相关的值 |
Module Module | 获取一个代表反射类型所在模块(可执行文件)的Module对象 |
String Name | 成员的名称 |
Type ReflectedType | 反射的对象类型 |
•MemberType属性的返回类型为MemberTypes,这是一个枚举,它定义了用于表示不同成员的类型值。这些值包括:MemberTypes.Constructor, MemberTypes.Method, MemberTypes.Field, MemberTypes.Event, MemberTypes.Property。因此可以通过检查MemberType属性来确定成员的类型,例如,在MemberType属性的值为MemberTypes.Method时,该成员为方法
•MemberInfo类还包含两个与特性相关的抽象方法:
1.GetCustomAttributes() :获得与主调对象关联的自定义特性列表。
2.IsDefined(): 确定是否为主调对象定义了相应的特性。
3.GetCustomAttributesData():返回有关自定义特性的信息(特性稍后便会提到)
当然除了MemberInfo类定义的方法和属性外,Type类自己也添加了许多属性和方法:如下表(只列出一些常用的,太多了,自己可以转定义Type类看下)
Type类定义的方法 | |
方法 | 功能 |
ConstructorInfo[] GetConstructors() | 获取指定类型的构造函数列表 |
EventInfo[] GetEvents(); | 获取指定类型的时间列 |
FieldInfo[] GetFields(); | 获取指定类型的字段列 |
Type[] GetGenericArguments(); | 获取与已构造的泛型类型绑定的类型参数列表,如果指定类型的泛型类型定义,则获得类型形参。对于正早构造的类型,该列表就可能同时包含类型实参和类型形参 |
MemberInfo[] GetMembers(); | 获取指定类型的成员列表 |
MethodInfo[] GetMethods(); | 获取指定类型的方法列表 |
PropertyInfo[] GetProperties(); | 获取指定类型的属性列表 |
Type类定义的属性 | |
属性 | 功能 |
Assembly Assembly | 获取指定类型的程序集 |
TypeAttributes Attributes | 获取制定类型的特性 |
Type BaseType | 获取指定类型的直接基类型 |
String FullName | 获取指定类型的全名 |
bool IsAbstract | 如果指定类型是抽象类型,返回true |
bool IsClass | 如果指定类型是类,返回true |
string Namespace | 获取指定类型的命名空间 |
使用反射
上面的列术都是为了,这里的使用。
通过使用Type类定义的方法和属性,我们能够在运行时获得类型的各种具体信息。这是一个非常强大的功能。我们一旦得到类型信息,就可以调用其构造函数,方法,和属性。可见,反射是允许使用编译时不可用的代码的。
由于Reflection API非常多,这里不可能完整的介绍他们(这里如果完整的介绍,据说要一本书,厚书)。但是,Reflection API是按照一定逻辑设计的。因此,只要知道部分接口的使用方法,就可以举一反三的使用剩余的接口。
这里我列出四种关键的反射技术:
1.获取方法的信息
2.调用方法
3.构造对象
4.从程序集中加载类型
获取方法的相关信息
一旦有了Type对象就可以使用GetMethodInfo()方法获取此类型支持的方法列表。该方法返回一个MethodInfo 对象数组,MethodInfo对象描述了主调类型所支持的方法,他位于System.Reflection命名空间中
MethodInfo类派生于MethodBase抽象类,而MethodBase类继承了MemberInfo类。因此我们能够使用这三个类定义的属性和方法。例如,使用Name属性得到方法名称。这里有两个重要的成员:
1. ReturnType属性 :为Type类型的对象,能够提供方法的返回类型信息
2. GetParameters()方法 :返回参数列表,参数信息以数组形式保存在PatameterInfo对象中。PatameterInfo类定义了大量描述参数信息的属性和方法。这里也列出两个常用的属性 :Name(包含参数名称信息的字符串),ParameterType(参数类型的信息)。
下面代码,我将使用反射获得类中所支持的方法,还有方法的信息。
class MyClass
{
int x;
int y;
public MyClass(int i, int j)
{
x = i;
y = j;
}
public int sum()
{
return x + y;
}
public bool IsBetween(int i)
{
if (x < i && i < y) return true;
else return false;
}
public void Set(int a, int b)
{
x = a;
y = b;
}
public void Set(double a, double b)
{
x = (int)a;
y = (int)b;
}
public void Show()
{
Console.WriteLine("x:{0},y:{1}",x,y);
}
}
class ReflectDemo
{
static void Main(string[] args)
{
Type t=typeof(MyClass); //获取描述MyClass类型的Type对象
Console.WriteLine("Analyzing methods in "+t.Name); //t.Name="MyClass"
MethodInfo[] mi = t.GetMethods(); //MethodInfo对象在System.Reflection命名空间下。
foreach (MethodInfo m in mi) //遍历mi对象数组
{
Console.Write(m.ReturnType.Name); //返回方法的返回类型
Console.Write(" " + m.Name + "("); //返回方法的名称
ParameterInfo[] pi = m.GetParameters(); //获取方法参数列表并保存在ParameterInfo对象数组中
for (int i = 0; i < pi.Length; i++)
{
Console.Write(pi[i].ParameterType.Name); //方法的参数类型名称
Console.Write(" "+pi[i].Name); // 方法的参数名
if (i + 1 < pi.Length)
{
Console.Write(", ");
}
}
Console.Write(")");
Console.WriteLine(); //换行
}
Console.ReadKey();
}
}
bool Equals(object obj) int GetHashCode() Type GetType() string ToString()
注意:这里输出的除了MyClass类定义的所有方法外,也会显示object类定义的共有非静态方法。这是因为c#中的所有类型都继承于object类。另外,这些信息实在程序运行时动态获得的,并不需要预先知道MyClass类的定义
GetMethods()方法的另一种形式
这种形式可以制定各种标记,已筛选想要获取的方法。他的通用形式为:MethodInfo[] GetMethods(BindingFlags bindingAttr)
BindingFlags是一个枚举,枚举值有(很多只列出5个吧):
1.DeclareOnly:仅获取指定类定义的方法,而不获取所继承的方法;
2.Instance:获取实例方法
3.NonPublic: 获取非公有方法
4.Public: 获取共有方法
5.Static:获取静态方法
GetMethods(BindingFlags bindingAttr)这个方法,参数可以使用or把两个或更多标记连接在一起,实际上至少要有Instance(或Static)与Public(或NonPublic)标记。否则将不会获取任何方法。
class MyClass
{
int x;
int y;
public MyClass(int i, int j)
{
x = i;
y = j;
}
private int sum()
{
return x + y;
}
public bool IsBetween(int i)
{
if (x < i && i < y) return true;
else return false;
}
public void Set(int a, int b)
{
x = a;
y = b;
}
public void Set(double a, double b)
{
x = (int)a;
y = (int)b;
}
public void Show()
{
Console.WriteLine("x:{0},y:{1}",x,y);
}
}
class ReflectDemo
{
static void Main(string[] args)
{
Type t=typeof(MyClass); //获取描述MyClass类型的Type对象
Console.WriteLine("Analyzing methods in "+t.Name); //t.Name="MyClass"
MethodInfo[] mi = t.GetMethods(BindingFlags.DeclaredOnly|BindingFlags.Instance|BindingFlags.Public); //不获取继承方法,为实例方法,为公开的
foreach (MethodInfo m in mi) //遍历mi对象数组
{
Console.Write(m.ReturnType.Name); //返回方法的返回类型
Console.Write(" " + m.Name + "("); //返回方法的名称
ParameterInfo[] pi = m.GetParameters(); //获取方法参数列表并保存在ParameterInfo对象数组中
for (int i = 0; i < pi.Length; i++)
{
Console.Write(pi[i].ParameterType.Name); //方法的参数类型名称
Console.Write(" "+pi[i].Name); // 方法的参数名
if (i + 1 < pi.Length)
{
Console.Write(", ");
}
}
Console.Write(")");
Console.WriteLine(); //换行
}
Console.ReadKey();
}
}
使用反射调用方法
上面我们通过反射获取到了类中的所有信息,下面我们就再使用反射调用通过反射获取到的方法。
要调用反射获取到的方法,则需要在MethodInfo实例上调用Invoke() 方法。Invoke()的使用,在下面例子中演示,说明。
下面例子是:先通过反射获取到要调用的方法,然后使用Invoke()方法,调用获取到的指定方法;
class MyClass
{
int x;
int y;
public MyClass(int i, int j)
{
x = i;
y = j;
}
private int sum()
{
return x + y;
}
public bool IsBetween(int i)
{
if (x < i && i < y) return true;
else return false;
}
public void Set(int a, int b)
{
Console.Write("Inside set(int,int).");
x = a;
y = b;
Show();
}
public void Set(double a, double b)
{
Console.Write("Inside set(double,double).");
x = (int)a;
y = (int)b;
Show();
}
public void Show()
{
Console.WriteLine("x:{0},y:{1}", x, y);
}
}
class InvokeMethDemo
{
static void Main()
{
Type t=typeof(MyClass);
MyClass reflectOb = new MyClass(10, 20);
reflectOb.Show(); //输出为: x:10, y:20
MethodInfo[] mi = t.GetMethods();
foreach (MethodInfo m in mi)
{
ParameterInfo[] pi = m.GetParameters();
if (m.Name.Equals("Set", StringComparison.Ordinal) && pi[0].ParameterType == typeof(int))
{
object[] args = new object[2];
args[0] = 9;
args[1] = 10;
//参数reflectOb,为一个对象引用,将调用他所指向的对象上的方法,如果为静态方法这个参数必须设置为null
//参数args,为调用方法的参数数组,如果不需要参数为null
m.Invoke(reflectOb, args); //调用MyClass类中的参数类型为int的Set方法,输出为Inside set(int,int).x:9, y:10
}
}
Console.ReadKey();
}
}
class MyClass
{
int x;
int y;
public MyClass(int i)
{
x = y + i;
}
public MyClass(int i, int j)
{
x = i;
y = j;
}
public int sum()
{
return x + y;
}
}
class InvokeConsDemo
{
static void Main()
{
Type t = typeof(MyClass);
int val;
ConstructorInfo[] ci = t.GetConstructors(); //使用这个方法获取构造函数列表
int x;
for (x = 0; x < ci.Length; x++)
{
ParameterInfo[] pi = ci[x].GetParameters(); //获取当前构造函数的参数列表
if (pi.Length == 2) break; //如果当前构造函数有2个参数,则跳出循环
}
if (x == ci.Length)
{
return;
}
object[] consargs = new object[2];
consargs[0] = 10;
consargs[1] = 20;
object reflectOb = ci[x].Invoke(consargs); //实例化一个这个构造函数有两个参数的类型对象,如果参数为空,则为null
//实例化后,调用MyClass中的方法
MethodInfo[] mi = t.GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
foreach (MethodInfo m in mi)
{
if (m.Name.Equals("sum", StringComparison.Ordinal))
{
val = (int)m.Invoke(reflectOb, null); //由于实例化类型对象的时候是用的两个参数的构造函数,所以这里返回的结构为30
Console.WriteLine(" sum is " + val); //输出 sum is 30
}
}
Console.ReadKey();
}
}
借助Reflection API,可以加载程序集,获取它的相关信息并创建其公共可用类型的实例。通过这种机制,程序能够搜索其环境,利用潜在功能,而无需在编译期间显式的定义他们。这是一个非常有效,且令人兴奋的概念。
为了说明如何获取程序集中的类型,我创建两个文件。第一个文件定义一组类,第二个文件则反射各个类的信息。 代码效果如下。
1.这下面代码是要编译生成MyClass.exe文件的
class MyClass
{
int x;
int y;
public MyClass(int i)
{
x = y + i;
}
public MyClass(int i, int j)
{
x = i;
y = j;
}
public int sum()
{
return x + y;
}
}
class Demo
{
static void Main()
{
Console.WriteLine("hello word !");
Console.ReadKey();
}
}
class Class3
{
static void Main() {
Assembly asm = Assembly.LoadFrom(@"C:/Users/lenovo/Documents/visual studio 2010/Projects/Reflection_test/ConsoleApplication1/bin/Debug/MyClass.exe"); //加载指定的程序集
Type[] alltype = asm.GetTypes(); //获取程序集中的所有类型列表
foreach (Type temp in alltype)
{
Console.WriteLine(temp.Name); //打印出MyClass程序集中的所有类型名称 MyClass , Demo
}
Console.ReadKey();
}
}
新闻热点
疑难解答