首先說一下反射的優點:動態!!!
首先了解一下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
這些應用可以空間換時間,第一次加載完直接存入緩存即可大大提高性能。