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

這些應用可以空間換時間,第一次加載完直接存入緩存即可大大提高性能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM