c#之反射(Reflection)
首先说一下反射的优点:动态!!!
首先了解一下C#编译运行过程,大致如下所示:
首先被编译器编译成dll/exe,一般我们发布的都是这个东西,然后在运行的时候会被CLR/JIT编译成机器码。
为什么不直接通过编译器编译成机器码呢?答案就是:通过CLR/JIT可以根据不同的平台编译成不同的机器码,用以一次编译多平台运行。
微软提供的反射工具主要是 System.Reflection
加载dll的具体用法大致如下
1 Assembly assembly1 = Assembly.LoadFile(@"D:\戎光科技\Util_YCH.Console\RefTest\bin\Debug\netstandard2.0\RefTest.dll");//完整路径 2 Assembly assembly2 = Assembly.Load(@"RefTest");//程序集名称,不带后缀 3//既可以是完整路径也可以是程序集完整名称 4 Assembly assembly3 = Assembly.LoadFrom(@"D:\戎光科技\Util_YCH.Console\RefTest\bin\Debug\netstandard2.0\RefTest.dll"); 5 Assembly assembly = Assembly.LoadFrom(@"RefTest.dll");
反射的具体用法
新建一个项目:AnimalRefTest 新建接口IAnimal
1 using System; 2 3 namespace IRefTest 4 { 5 public interface IAnimal 6 { 7 string CallName(); 8 } 9 }
新建项目:DogRefTest 新建类 Dog
1 using IRefTest; 2 using System; 3 4 namespace DogRefTest 5 { 6 public class Dog: IAnimal 7 { 8 public Dog() 9 { 10 this.name = "无名小狗"; 11 } 12 13 public string name { set; get; } 14 public int Age { set; get; } 15 16 public string food; 17 18 private int foot; 19 20 public string CallName() 21 { 22 Console.WriteLine($"狗叫:{this.name}"); 23 return this.name; 24 } 25 } 26 }
新建项目:CatRefTest 新建类 Cat
1 using IRefTest; 2 using System; 3 4 namespace CatRefTest 5 { 6 public sealed class Cat : IAnimal 7 { 8 public Cat() 9 { 10 this.name = "无名小猫"; 11 } 12 public Cat(string name) 13 { 14 this.name = name ?? throw new ArgumentNullException(nameof(name)); 15 } 16 17 public string name { set; get; } 18 /// <summary> 19 /// 公开无参方法 20 /// </summary> 21 /// <returns></returns> 22 public string CallName() 23 { 24 Console.WriteLine($"猫叫:{this.name}"); 25 return this.name; 26 } 27 /// <summary> 28 /// 公开单参数方法 29 /// </summary> 30 /// <param name="what"></param> 31 public void CallWhatPublic(string what) 32 { 33 Console.WriteLine($"公开单参数方法:{what}"); 34 } 35 /// <summary> 36 /// 私有单参数方法 37 /// </summary> 38 /// <param name="what"></param> 39 private void CallWhatPrivate(string what) 40 { 41 Console.WriteLine($"私有单参数方法:{what}"); 42 } 43 44 } 45 }
新建一个项目RefTest,新建配置文件,添加内容
<add key="IAnimalConfig" value="CatRefTest,CatRefTest.Cat"/>
新建类AnimalFactory
1 using IRefTest; 2 using System; 3 using System.Configuration; 4 using System.Reflection; 5 6 namespace Util_YCH.Build.Reflection 7 { 8 public class AnimalFactory 9 { 10 private static string IAniamlConfig = ConfigurationManager.AppSettings["IAnimalConfig"]; 11 private static string DLLName = IAniamlConfig.Split(',')[0]; 12 private static string TypeName = IAniamlConfig.Split(',')[1]; 13 14 public static IAnimal GetAnimal() { 15 Assembly assembly = Assembly.LoadFrom(DLLName); 16 Type type = assembly.GetType(TypeName);//完全限定名 17 var obj = Activator.CreateInstance(type); 18 IAnimal animal = (IAnimal)obj; 19 return animal; 20 } 21 } 22 }
main方法中输入代码并运行
1 using Util_YCH.Build.Reflection; 2 3 namespace Util_YCH.Build 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 var animal = AnimalFactory.GetAnimal(); 10 animal.CallName();//输出: 11 } 12 } 13 }
输出
如果修改 配置文件的内容为
<!--<add key="IAnimalConfig" value="CatRefTest,CatRefTest.Cat"/>--> <add key="IAnimalConfig" value="DogRefTest,DogRefTest.Dog"/>
运行,输出
感觉和IOC有点像啊,应该是用了类似的方法实现的。
这样的话,就意味着,如果我们软件设计之初只支持Cat类,但是后来需求变更,需要支持Dog,那么我们只需要修改配置文件就可以在不修改源代码的情况下,只需要在根目录添加DogRefTest.dll,并更新配置文件即可支持,实现热更新。
如何通过反射调用方法?
添加一个 泛型类 Generic_Ref
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace CatRefTest 6 { 7 public class Generic_Ref<T> 8 { 9 /// <summary> 10 /// 泛型方法 11 /// </summary> 12 /// <typeparam name="T"></typeparam> 13 /// <param name="t"></param> 14 public void CallOne(T t) 15 { 16 Console.WriteLine($"泛型方法反射了:{t.GetType().FullName}"); 17 } 18 /// <summary> 19 /// 泛型方法 20 /// </summary> 21 /// <typeparam name="T"></typeparam> 22 /// <param name="t"></param> 23 public void Call<K,V>(K k,V v) 24 { 25 Console.WriteLine($"泛型方法反射了,K:{k.GetType().FullName},V:{v.GetType().FullName}"); 26 } 27 } 28 }
在AnimalFactory的GetAnimal()中添加如下代码
1 #region 反射方法 2 #region 无参函数调用 3 { 4 MethodInfo method = type.GetMethod("CallName"); 5 method.Invoke(obj, null); 6 } 7 #endregion 8 9 #region 有参函数反射调用 10 { 11 MethodInfo method = type.GetMethod("CallWhatPublic"); 12 method.Invoke(obj, new object[] { "反射运行了?" }); 13 } 14 #endregion 15 16 #region 私有参函数反射调用 17 { 18 MethodInfo method = type.GetMethod("CallWhatPrivate", BindingFlags.Instance | BindingFlags.NonPublic); 19 method.Invoke(obj, new object[] { "反射运行了?" }); 20 } 21 #endregion 22 23 #region 泛型方法反射 24 { 25 Type typeT = assembly.GetType("CatRefTest.Generic_Ref`1");//完全限定名 26 Type typeG = typeT.MakeGenericType(new Type[] { typeof(int) }); 27 var objG = Activator.CreateInstance(typeG); 28 MethodInfo method = typeG.GetMethod("CallOne"); 29 method.Invoke(objG, new object[] { 100 }); 30 } 31 #endregion 32 33 34 #region 泛型方法反射 35 { 36 Type typeT = assembly.GetType("CatRefTest.Generic_Ref`1");//完全限定名 37 Type typeG = typeT.MakeGenericType(new Type[] { typeof(int) }); 38 var objG = Activator.CreateInstance(typeG); 39 MethodInfo method = typeG.GetMethod("Call"); 40 MethodInfo methodNew = method.MakeGenericMethod(new Type[] { typeof(string), typeof(bool) }); 41 methodNew.Invoke(objG, new object[] { "hah0", false }); 42 } 43 #endregion 44 #endregion 45 46 #region 反射属性 47 { 48 Type typeDog = typeof(DogRefTest.Dog); 49 var theDog = Activator.CreateInstance(typeDog); 50 Console.WriteLine("属性"); 51 foreach (var pop in typeDog.GetProperties()) 52 { 53 Console.WriteLine(pop.Name); 54 if (pop.Name.Equals("name")) 55 { 56 pop.GetValue(theDog); 57 pop.SetValue(theDog,"反射的狗"); 58 } 59 else if (pop.Name.Equals("Age")) 60 { 61 pop.GetValue(theDog); 62 pop.SetValue(theDog, 5); 63 } 64 } 65 Console.WriteLine("字段"); 66 foreach (var fieId in typeDog.GetFields(BindingFlags.Instance|BindingFlags.Public| BindingFlags.NonPublic)) 67 { 68 Console.WriteLine(fieId.Name); 69 if (fieId.Name.Equals("food")) 70 { 71 fieId.GetValue(theDog); 72 fieId.SetValue(theDog, "大骨头"); 73 } 74 else if (fieId.Name.Equals("foot")) 75 { 76 fieId.GetValue(theDog); 77 fieId.SetValue(theDog, 4); 78 } 79 } 80 81 var theDogDto = new Mapper<DogRefTest.DogDto>().MapTo((DogRefTest.Dog)theDog); 82 } 83 #endregion
即可实现反射调用方法以及设置属性字段。
顺便手写了一个初级的映射
1 public interface IMapper<T> { 2 T MapTo<V>(V v) where V : class; 3 } 4 public class Mapper<T>:IMapper<T> where T : class 5 { 6 #region 利用反射进行自动映射 7 public T MapTo<V>(V v) 8 where V : class 9 { 10 Type typeIn = typeof(V); 11 Type typeOut = typeof(T); 12 var typeOutObj = Activator.CreateInstance(typeOut); 13 14 foreach (var pop in typeOut.GetProperties()) 15 { 16 Console.WriteLine(pop.Name); 17 var popIn = typeIn.GetProperty(pop.Name); 18 if (popIn is null) 19 throw new Exception($"{pop.Name} 无法进行映射"); 20 var value = popIn.GetValue(v); 21 Console.WriteLine($"对象v中的对应值是{pop}"); 22 pop.SetValue(typeOutObj, value); 23 } 24 25 foreach (var field in typeOut.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) 26 { 27 Console.WriteLine(field.Name); 28 var popIn = typeIn.GetField(field.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 29 if (popIn is null) 30 throw new Exception($"{field.Name} 无法进行映射"); 31 var value = popIn.GetValue(v); 32 Console.WriteLine($"对象v中的对应值是{field}"); 33 field.SetValue(typeOutObj, value); 34 } 35 return (T)typeOutObj; 36 } 37 #endregion 38 }
最后总结一下反射的缺点:
- 写起来复杂
- 逃脱了编译器的检查,出错概率高
- 性能问题,与直接调用之间性能差距可能百倍之多,但是大部分情况下不会影响程序的性能
反射的实际应用:MVC的路由,EF
这些应用可以空间换时间,第一次加载完直接存入缓存即可大大提高性能。