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