C# 反射、与dynamic最佳组合

在 C# 中反射技术应用普遍,至于什么是反射.........你若是不了解的话,请看下段说明,不然请跳过下段。广告一下:喜欢我文章的朋友请关注一下个人blog,这也有助于提升本人写做的动力。html

反射:当你背对一个美女或帅哥却不能回头仔细观察研究时(纯属虚构,若有巧合、纯属雷同),一面小镜子就能知足你的需求。在 C# 编程过程当中也常常遇到相似的状况:有一个别人写的 dll 类库你想使用却没程序文档资料......此时经过 C# Runtime 提供的功能,你能够把该 dll 类库加载到你的程序中,并细细研究 dll 的每一部份内容,这就是 C# 中的反射。web

我的认为反射最突出的优势或存在的合理性:在不修改程序原码的状况下,实现程序功能的动态调整(Runtime动态对象建立数据库

示例:编程

    interface IRun {
        void Run();
    }
    class Person : IRun
    {
        public void Run()
        {
            Console.WriteLine("走,去LOL啊!");
        }
    }
    class Car : IRun
    {
        public void Run()
        {
            Console.WriteLine("呜...........");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IRun e = new Person();
            e.Run();
            Console.ReadLine();
        }
    }

若是将上面的Run功能并不必定是由Person来执行,有时须要是Car有时须要Person。常见的解决方案是添加 if 等判断结构,以下:设计模式

       static void Main(string[] args)
        {
            Console.WriteLine("请输入:Car或Person");
            string type = Console.ReadLine();
            IRun e = null;
            if ("Car" == type)
            {
                e = new Car();
            }else if("Person" == type)
            {
                e = new Person();
            }
            if(null != e)
                e.Run();

            Console.ReadLine();
        }

这种结构确是解决了如今的需求,但并不健壮。随着 IRun 接口实现、相关类的继承的增长,上面的判断结构也会飞速增加。面向对象编程、设计模式均遵循的一大原则就是封装变换,因此上面的程序没法很好的应对变化。在此咱们并不涉及 “设计模式的” 的知识,所以下面的示例代码只为简化上面的程序、并未刻意套用设计模式相关知识。以下:缓存

        static void Main(string[] args)
        {
            Console.WriteLine("请输入:Car或Person");
            string type = Console.ReadLine();
            string classPath = String.Format("namespace.{0}", type);
            IRun e = Activator.CreateInstance(null, classPath).Unwrap() as IRun;

            if(null != e)
                e.Run();

            Console.ReadLine();
        }

通过上面的修改,程序可自行根据用户的输入,经过Activator.CreateInstance建立 IRun 的实例,程序此处不会再随 IRun 的实现者增多这种问题的影响而发生变化。上面的这种优势就是经过反射获得的,也是我所认为的“反射存在的合理性”。oracle

Activator、Assembly 实现反射方式建立对象负载均衡

C#中反射方式建立对象能够经过 Activator.CreateInstance(静态)和 Assembly.CreateInstance(非静态)来实现,其中Assembly.CreateInstance 内部调用的还是Activator.CreateInstance。异步

根据要动态建立的类型对象是否处于当前程序集之中,可将反射建立对象分为:建立程序集内的类型对象与建立程序集外的类型对象。异步编程

建立程序集内的类型对象

        private static void ReflectionIRun1(string className)
        {
            string classPath = String.Format("namespace.{0}", className);
            //参数 null ,指出所要建立类型对象位于当前程序集 
            var handler = Activator.CreateInstance(null, classPath);
            IRun e = (IRun)handler.Unwrap();
            Console.WriteLine(e.Run());
        }
        private static void ReflectionIRun2(string className)
        {
            string classPath = String.Format("namespace.{0}", className);
            //typeof(IRun).Assembly 获取 IRun 类型所在的程序集
            object obj = typeof(IRun).Assembly.CreateInstance(null, classPath);
            IRun e = (IRun)obj;
            Console.WriteLine(e.Run());
        }

建立程序集外的类型对象

项目中增长一个 类库 (另外一个程序集),以下图:

添加一个 Boss 类,以下:

namespace Lib
{
    public class Boss
    {
        private string name = "老大";
        
        public string Name{
            get {return name;}
        }
        public string Talk()
        {
            return "大家都被开除了......";
        }
        //老板不会算帐,老是多付钱,因此颇有自知之明的将Payfor设为private,防止外部人员调用
        private int Payfor(int total)
        {
            return total + 10;
        }
    }
}    

获取 一个 Boss 对象前,首先添加对 Lib 的引用,获取示例以下:

        private static void ReflectionBoss1()
        {
            string classPath ="Lib.Boss";
            //"Lib" 参数指明要加载的程序集(即要建立的对象类型在哪一个程序集中定义)
            var handler = Activator.CreateInstance("Lib", classPath);
            Boss b = handler.Unwrap() as Boss;
            Console.WriteLine(b.Talk());
        }
        private static void ReflectionBoss2()
        {
            string classPath ="Lib.Boss";
            //Assembly.Load("Lib") 加载的程序集(即要建立的对象类型在哪一个程序集中定义)
            var assembly = Assembly.Load("Lib");
            Boss b = (Boss)assembly.CreateInstance(classPath);
            Console.WriteLine(b.Talk());
        }  

关于反射时CLR如何查找并定位要加载的程序集,请参考MSDN中关于反射相关的知识。

反射访问字段、调用方法(属性

反射除能够帮咱们动态建立对象外,还可帮咱们动态访问对象的方法(属性)或字段,因 C# 版本不一样具体方法会有变动或扩展,更深刻内容请参考MSDN。下面仅做简单示例(标准用法)。

给老板更名,示例: 

        private static void ReflectionBoss1()
        {
            string classPath = "Lib.Boss";
            //"Lib" 参数指明要加载的程序集(即要建立的对象类型在哪一个程序集中定义)
            var handler = Activator.CreateInstance("Lib", classPath);
            Boss b = handler.Unwrap() as Boss;
            //关键代码
            FieldInfo f = b.GetType().GetField("name", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
            f.SetValue(b, "小二");

            Console.WriteLine("{0}:{1}", b.Name, b.Talk());
        }

输出:

让老板付钱:

private static void ReflectionBoss1()
        {
            string classPath = "Lib.Boss";
            //"Lib" 参数指明要加载的程序集(即要建立的对象类型在哪一个程序集中定义)
            var handler = Activator.CreateInstance("Lib", classPath);
            Boss b = handler.Unwrap() as Boss;
            //关键代码
            MethodInfo method = b.GetType().GetMethod("Payfor", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance);
            object money = method.Invoke(b, new object[] { 10 });
Console.WriteLine(
"DW039:老大给我报销10元钱车费......"); Console.WriteLine("{0}:.....,算不清了,给你这些吧。",b.Name); Console.WriteLine("DW039:......"); Console.WriteLine("{0}:{1}", b.Name,money); Console.WriteLine("DW039:老大你真棒!"); }

输出:

dynamic 与 反射 双剑合璧

由于反射是运行时的类型操做,因此在编程时面临类型不肯定的问题。根据上一篇《C# 匿名对象(匿名类型)、var、动态类型 dynamic》讲得 dynamic 动态类型结合咱们编写的反射程序,能够大大优化程序逻辑(访问受保护级别限制的代码不在此范围内)。

上面代码的优化:

 private static void ReflectionBoss1()
        {
            string classPath ="Lib.Boss";
            var handler = Activator.CreateInstance("Lib", classPath);
            dynamic b = handler.Unwrap();
            Console.WriteLine(b.Talk());
        }
        private static void ReflectionBoss2()
        {
            string classPath ="Lib.Boss";
            var assembly = Assembly.Load("Lib");
            dynamic b = assembly.CreateInstance(classPath);
            Console.WriteLine(b.Talk());
        }  

经过 dynamic 动态类型对象 b 来调用反射获得对象的属性、方法可直接调用,从而省去了频繁的类型转换操做。

反射常见应用场景

应用场景我印象最深入的是 MS Petshop 示例,从SQL Server 数据库切换到 oracle 数据库时反射得到不一样的数据访问层。然我实际项目中从未遇到过中途切换数据库的状况,其余应用场景基本相似上面的示例。若是朋友你发现更多的应用场景,请给予补充,3ks。

反射的优缺点

优势:反射使程序更灵活

缺点:反射运行速度相对较慢

至于反射相比普通程序慢,我没有进行过测试也不打算进行。现实状况是:Ms提倡使用 dynamic、Mvc流行、Ms对CLR不断优化、机器性能的提高,因此你在开发中无需过多考虑反射的性能问题。若是你写的程序运行速度出现了瓶颈(应首先确保本身程序写的合理),研究一下数据库优化、数据缓存、web缓存、负载均衡等技术我认为更实际一些。

请放心大胆的使用反射技术吧,朋友!

随后我将写一系列关于C# 异步编程的文章,感兴趣的朋友请点下面的 “关注我” ,谢谢。

相关文章
相关标签/搜索