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

这些应用可以空间换时间,第一次加载完直接存入缓存即可大大提高性能。

posted @ 2020-02-17 12:01  每天进步多一点  阅读(1228)  评论(0编辑  收藏  举报