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

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

posted @ 2019-12-31 01:20  打工人小余  阅读(385)  评论(0编辑  收藏  举报