大家好,本节我们来详解反射,那么关于本篇的话,我们直接来通过代码来演示反射的常用方法,以及java相应的反射实现。
关于反射的原理、反射的使用场景以及具体的示例我计划放到下篇,因为我觉得光理论是很枯燥的,而且也只有知道了怎么用,知道了自己以往是否用过,才能更好的理解其使用场景。
那么,现在我们就开始本节内容吧。
反射Reflection,是.Net Framework提供的一个帮助类库,可以读取并使用metadata,位于如下命名空间
System.Reflection
反射使用过程图解
这里先说一下我们的项目架构,有一个IDBHelper的接口类,定义了2个方法
public interface IDBHelper { void Query(); void Insert(); }
然后,分别定义了MySQL、SQLServer、Orace3个对应的类库并实现了IDBHelper接口,以MySQLHelper为例,其他类似
public class MySqlHelper : IDBHelper { public MySqlHelper() { Console.WriteLine("{0}构造函数", this.GetType().Name); } public void Query() { Console.WriteLine("{0}.Query",this.GetType().Name); } public void Insert() { Console.WriteLine("{0}.Insert", this.GetType().Name); } }
下面开始讲解上图步骤吧。
程序集加载
程序集常用加载有3种方式,分别如下所示
{ //Load:通过程序集命名空间 Assembly assembly = Assembly.Load("DB.MySql"); //LoadFile:通过程序集对应的完整物理路径 Assembly assembly1 = Assembly.LoadFile(@"E:\StydyDemos\Samples\MyReflection\MyReflection\DB.MySql\bin\Debug\DB.MySql.dll"); //LoadFrom:通过程序集的文件名或路径加载 //1.文件名 Assembly assembly2 = Assembly.LoadFrom("DB.Mysql.dll"); //2.完整物理路径 Assembly assembly3 = Assembly.LoadFrom(@"E:\StydyDemos\Samples\MyReflection\MyReflection\DB.MySql\bin\Debug\DB.MySql.dll"); }
获取类型
通过getType()、getTypes()方法可以用来获取对应的类型
{ Assembly assembly = Assembly.Load("DB.MySql"); //获取程序集下全部类型,并打印其名称 Type[] types=assembly.GetTypes(); types.ToList().ForEach(o => Console.WriteLine(o.Name)); //获取assembly的Type Type type1 = assembly.GetType(); //获取指定类名称的类型 Type type2 = assembly.GetType("DB.MySql.MySqlHelper"); }
结果如下,注意assembly的Type是RuntimeAssembly。
创建对象
类型获取到之后,我们应该来创建对象了,创建对象可通过assembly.CreateInstance()方法,如下,方法如要传入typeName参数,而且返回的是一个object对象。
因此,我们在拿到对象之后,需要对object进行类型转换,转换为我们需要的对象。
类型转换
{ Assembly assembly = Assembly.Load("DB.MySql"); Type type = assembly.GetType("DB.MySql.MySqlHelper"); //创建对象 object o = assembly.CreateInstance("DB.MySql.MySqlHelper"); //类型转换 MySqlHelper mySqlHelper = (MySqlHelper)o; }
到这里的话,我们已经获取对了相应的对象了,此时的对象,和我们new出来的实例是没有区别的,此时可以通过.来调用相应的方法。那反射既然可以根据类名获取到对应的类型,那如果我们知道要调用的方法的名称,是否也能通过反射来获取到方法信息,然后通过反射的方式来调用呢?答案是可以的,下面我们来看看通过反射的方式调用方法。
方法调用
首先,我们可以通过type.GetMethods()或type.GetMethod方法来获取,分别返回MethodInfo[]和MethodInfo
{ Assembly assembly = Assembly.Load("DB.MySql"); Type type = assembly.GetType("DB.MySql.MySqlHelper"); object o = assembly.CreateInstance("DB.MySql.MySqlHelper"); //获取方法集合 MethodInfo[] methodInfos= type.GetMethods(); //获取特定方法名的方法 MethodInfo methodInfo = type.GetMethod("Query"); methodInfo.Invoke(o,null); }
这里我们演示调用了一个public,参数为空的方法,实际上,可能有public、private、static甚至是有多个重载的方法,那么,这些调用方式分别是如何的呢?
首先,我们在这里添加几个测试类到MySqlHelper中
#region Method /// <summary> /// 无参方法 /// </summary> public void Show1() { Console.WriteLine("这里是{0}的Show1", this.GetType()); } /// <summary> /// 有参数方法 /// </summary> /// <param name="id"></param> public void Show2(int id) { Console.WriteLine("这里是{0}的Show2", this.GetType()); } /// <summary> /// 重载方法之一 /// </summary> /// <param name="id"></param> /// <param name="name"></param> public void Show3(int id, string name) { Console.WriteLine("这里是{0}的Show3", this.GetType()); } /// <summary> /// 重载方法之二 /// </summary> /// <param name="name"></param> /// <param name="id"></param> public void Show3(string name, int id) { Console.WriteLine("这里是{0}的Show3_2", this.GetType()); } /// <summary> /// 重载方法之三 /// </summary> /// <param name="id"></param> public void Show3(int id) { Console.WriteLine("这里是{0}的Show3_3", this.GetType()); } /// <summary> /// 重载方法之四 /// </summary> /// <param name="name"></param> public void Show3(string name) { Console.WriteLine("这里是{0}的Show3_4", this.GetType()); } /// <summary> /// 重载方法之五 /// </summary> public void Show3() { Console.WriteLine("这里是{0}的Show3_1", this.GetType()); } /// <summary> /// 私有方法 /// </summary> /// <param name="name"></param> private void Show4(string name) { Console.WriteLine("这里是{0}的Show4", this.GetType()); } /// <summary> /// 静态方法 /// </summary> /// <param name="name"></param> public static void Show5(string name) { Console.WriteLine("这里是{0}的Show5", typeof(MySqlHelper)); } #endregion
那这里,我们就分别看一下不同的方法如何调用吧。由于创建对象的代码都相同,以下代码之包含方法创建及调用部分。
MethodInfo methodInfo = type.GetMethod("Show1"); //指定参数类型 //MethodInfo methodInfo = type.GetMethod("Show1",new Type[] { }); methodInfo.Invoke(o,null);
带参数的方法
MethodInfo methodInfo = type.GetMethod("Show2"); //MethodInfo methodInfo = type.GetMethod("Show2",new Type[] { typeof(int) }; methodInfo.Invoke(o, new object[] { 123 });
多个重载方法的方法
如上,我们可以在获取MethodInfo的时候,传入指定的参数,由此来确认对应的重载方法,以上2个方法,之所以可以不传参数类型,是因为方法没有重载,当方法具有重载时,即使没有参数也必须传入参数,否则会抛System.Reflection.AmbiguousMatchException异常。
{//无参 MethodInfo method = type.GetMethod("Show3", new Type[] { }); method.Invoke(o, null); } {//一个参数 MethodInfo method = type.GetMethod("Show3", new Type[] { typeof(int) }); method.Invoke(o, new object[] { 123 }); } {//一个参数 MethodInfo method = type.GetMethod("Show3", new Type[] { typeof(string) }); method.Invoke(o, new object[] { "画鸡蛋的不止达芬奇" }); } {//2个参数 MethodInfo method = type.GetMethod("Show3", new Type[] { typeof(int), typeof(string) }); method.Invoke(o, new object[] { 123, "画鸡蛋的不止达芬奇" }); } {//2个参数 MethodInfo method = type.GetMethod("Show3", new Type[] { typeof(string), typeof(int) }); method.Invoke(o, new object[] { "画鸡蛋的不止达芬奇", 123 }); }
静态方法的调用
其实,静态方法的调用比较简单,其与非静态方法的区别就是,在调用时,不是必须传入实例对象即可调用
{ MethodInfo methodInfo = type.GetMethod("Show5"); methodInfo.Invoke(null, new object[]{ "画鸡蛋的不止达芬奇" }); methodInfo.Invoke(o, new object[]{ "画鸡蛋的不止达芬奇" }); }
私有方法的调用
其实,GetMethod方法是个重载方法,它除了传入方法名称,还有一个可指定搜索方式的参数,如下
那么,私有方法的获取,可以指定该参数为BindingFlags.Instance | BindingFlags.NonPublic(搜索公共及非公共成员)
{ MethodInfo methodInfo = type.GetMethod("Show4",BindingFlags.Instance|BindingFlags.NonPublic); methodInfo.Invoke(o, new object[] { "画鸡蛋的不止达芬奇" }); }
讲到这里的话,反射的方法调用也就讲完了,那么接下来,我们再来说一下,反射类的字段和属性
反射类的属性和字段
我们这里通过创建person类来演示
public class Person { public Person() { Console.WriteLine("{0}被创建", this.GetType().FullName); } public int Id { get; set; } public string Name { get; set; } public string Description; }
和获取方法一样,获取属性和字段都属于Type上的方法
获取属性可通过GetProperties()、GetProperty()方法
获取字段可通过GetFields()、GetFiled()方法
{ Type type1 = typeof(Person); object oPerson = Activator.CreateInstance(type); PropertyInfo info=type1.GetProperty("Name"); foreach (var prop in type1.GetProperties()) { Console.WriteLine($"{type.Name}.{prop.Name}={prop.GetValue(oPerson)}"); if (prop.Name.Equals("Id")) { prop.SetValue(oPerson, 123); } else if (prop.Name.Equals("Name")) { prop.SetValue(oPerson, "画鸡蛋的不止达芬奇"); } Console.WriteLine($"{type.Name}.{prop.Name}={prop.GetValue(oPerson)}"); } FieldInfo fieldInfo= type1.GetField("Description"); foreach (var field in type1.GetFields()) { Console.WriteLine($"{type.Name}.{field.Name}={field.GetValue(oPerson)}"); if (field.Name.Equals("Description")) { field.SetValue(oPerson, "画鸡蛋的不止达芬奇"); } Console.WriteLine($"{type.Name}.{field.Name}={field.GetValue(oPerson)}"); } }
本节之前计划将这部分的基础操作和java中的反射技术做个对比,因为这两者很相似,这样不仅能加深印象,而且能扩宽一下知识面,因此今天在这里做个补充。java的操作操作类型如下,我就主要给出代码,如果你理解了C#,那么看懂下面的代码也不是什么难事了。
java反射概述
- Class.forName()来获取Class
- getConstructor获取构造函数
- getMethod()获取方法
- getField()获取字段
- private属性,需要使用带Declared的对应方法
- static类型的,再调用时,直接用null,无需传入实例对象即可。
反射类的构造函数
//反射构造函数 @Test public void ConstructorTest() throws Exception { Class aClass = Class.forName("Reflactor.Person"); Constructor[] constructors= aClass.getConstructors(); System.out.println("公有构造函数个数:"+constructors.length); /*for (Constructor constructor:constructors){ System.out.println(constructor.getParameterCount()); }*/ Constructor[] constructors1= aClass.getDeclaredConstructors(); System.out.println("全部构造函数个数:"+constructors1.length); //public 无参构造函数 Constructor constructor=aClass.getConstructor(); Person person=(Person) constructor.newInstance(); System.out.println(person.getName()); //public 有参构造函数,个数必须对应 Constructor constructor1=aClass.getConstructor(String.class,int.class); Person person1=(Person)constructor1.newInstance("Jack",18); System.out.println(person1.getName()); System.out.println(person1.getAge()); //private 有参构造函数 //私有构造函数,必须通过setAccessible(true)来获得访问权限 Constructor constructor2=aClass.getDeclaredConstructor(String.class); constructor2.setAccessible(true); Person person2=(Person)constructor2.newInstance("Jack"); System.out.println(person2.getName()); }
反射类的方法
//反射类的方法 @Test public void test1() throws Exception{ Person p=new Person(); Class clazz=Class.forName("Reflactor.Person"); //public Method method= clazz.getMethod("getAb"); method.invoke(p); //private Method method1=clazz.getDeclaredMethod("getab"); method1.setAccessible(true); method1.invoke(p); //private static Method method2=clazz.getDeclaredMethod("getAB"); method2.setAccessible(true); method2.invoke(null);//static方法可传null调用 }
反射类的main方法
//获取main方法 //注意传入参数的时候要对传入的参数进行封装。 @Test public void test2() throws Exception{ Class clazz=Class.forName("Reflactor.Person"); /*//使用获取private的方式获取 // wrong number of arguments Method method=clazz.getDeclaredMethod("main", String[].class);*/ Method method=clazz.getDeclaredMethod("main", String[].class); Person person=new Person(); //这里需要传入一个String[].class,但是invoke方法会把传入的new String[]{"1","2","3"}封装成一个参数,因此,需要在外层在封装一层。 // method.invoke(person,new Object[]{new String[]{"1","2","3"}}); method.invoke(null,new Object[]{new String[]{"1","2","3"}});//static方法调用时可直接传null。 }
反射类的字段
//反射类的字段 @Test public void test3() throws Exception{ Class clazz=Class.forName("Reflactor.Person"); System.out.println(clazz.getFields()); Person person=new Person(); //public 有默认值 Field field=clazz.getField("birthday"); String birthday=(String)field.get(person); System.out.println(birthday); //public 无默认值 Field field2= clazz.getField("name"); String name=(String)field2.get(person); System.out.println(name); //private 有默认值 Field field1= clazz.getDeclaredField("weight"); field1.setAccessible(true); int weight=(int)field1.get(person); System.out.println(weight); //private static Field field3=clazz.getDeclaredField("FirstName"); field3.setAccessible(true); String firstName=(String)field3.get(null); System.out.println(firstName); }