zoukankan      html  css  js  c++  java
  • .NET:使用接口进行动态绑定[草稿]

    其实这个话题已经有很多文章了. 看了博友一篇抽象工厂的文章, 利用.NET的反射特性, 避免使用if else判断, 从而为对象的创建解耦. 考虑到性能问题, 不禁自己想写一篇, 只是为了有机会的时候,把这篇文章深入一下.

    .NET提供的反射机制是一面双刃剑. 它既提供了方便动态调用的机制, 又有不小的性能损失.

    我们以一个简单加法来做一下对比.

    • 直接调用

    直接调用的代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    
    namespace DynamicBinding
    {
        class Program
        {
            static void Main(string[] args)
            {
    
                Stopwatch sw = new Stopwatch();
                sw.Reset();
                sw.Start();
                for (int i = 0; i < 10000000; i++)
                {
                    Add(100,100);
                }
                sw.Stop();
    
                Console.WriteLine(sw.ElapsedMilliseconds);
                Console.ReadKey();
            }
    
            public static int Add(int a, int b)
            {
                return a + b;
            }
        }
    }

    在E8300, 2G RAM环境中, 程序的运行结果是:

    167

    • 后期动态绑定

    被后期绑定的程序集代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace MyCaculator
    {
        public class MyCaculator
        {
            public int Add(int a, int b)
            {
                return a + b;
            }
        }
    }

    在我们的应用程序中可以这样进行绑定:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    using System.Diagnostics;
    
    namespace DynamicBinding
    {
        class Program
        {
            static void Main(string[] args)
            {
                Stopwatch sw = new Stopwatch();
                sw.Reset();
                object caculator = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
                                          "MyCaculator.dll", "MyCaculator.MyCaculator");
                MethodInfo adder = caculator.GetType().GetMethod("Add");
                object[] parameters = new object[2] { 100, 100 };
                sw.Start();
                for (int i = 0; i < 10000000; i++)
                {
                    adder.Invoke(caculator, parameters);
                }
                sw.Stop();
    
                Console.WriteLine(sw.ElapsedMilliseconds);
                Console.ReadKey();
            }
        }
    }

    在E8300, 2G RAM环境中, 程序的运行结果是:

    17413

    • 使用接口进行后期动态绑定

    面临如此大的性能损失, 是我们在使用.NET的反射特性的时候充满了犹豫. 那么有没有更好的办法, 既能利用到反射特性的边界, 又能使我们尽量地减少由此而蒙受的性能损失呢? 现阶段的实践下, 这种方法是有的, 那就是事先约定接口.

    我们来看同样的例子:

    接口代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace InterfaceCaculator
    {
        public interface ICaculator
        {
            int Add(int a, int b);
        }
    }

    被调用代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using InterfaceCaculator;
    
    namespace MyCaculator
    {
        public class MyCaculator: ICaculator
        {
            public int Add(int a, int b)
            {
                return a + b;
            }
        }
    }

    我们的应用程序代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    using InterfaceCaculator;
    using System.Diagnostics;
    
    namespace DynamicBinding
    {
        class Program
        {
            static void Main(string[] args)
            {
                Stopwatch sw = new Stopwatch();
                sw.Reset();
                ICaculator caculator = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(
                                          "MyCaculator", "MyCaculator.MyCaculator") as ICaculator;
                sw.Start();
                for (int i = 0; i < 10000000; i++)
                {
                    caculator.Add(100, 100);
                }
                sw.Stop();
    
                Console.WriteLine(sw.ElapsedMilliseconds);
                Console.ReadKey();
            }
        }
    }

    你预计这个程序运行的结果是多少?

    178!!!

    • 结论

    如上看来, 使用接口进行动态绑定大大的缩减了由于引入反射所带来的性能损失, 让这种性能损失达到了我们无所察觉或者可以忍受的地步. 实际上, 在给宿主程序提供了接口信息以后, 宿主程序利用反射特性调用客户代码时, 不再需要每次都重新遍历检索客户程序集文件头部的元数据段从而定位合适的方法, 而是通过接口和CallVirt指令, 直接指向了方法的入口.这就节省了大部分的额外消耗时间.

    我们在需要使用反射特性时, 应该尽量考虑使用接口来进行后期动态绑定.

    作者:Jeffrey Sun
    出处:http://sun.cnblogs.com/
    本文以“现状”提供且没有任何担保,同时也没有授予任何权利。本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    Java实现 LeetCode 382 链表随机节点
    Java实现 LeetCode 382 链表随机节点
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 381 O(1) 时间插入、删除和获取随机元素
    Java实现 LeetCode 380 常数时间插入、删除和获取随机元素
    Java实现 LeetCode 380 常数时间插入、删除和获取随机元素
    Linux下的iwpriv(iwlist、iwconfig)的简单应用
    OCX控件的注册卸载,以及判断是否注册
    .OCX、.dll文件注册命令Regsvr32的使用
  • 原文地址:https://www.cnblogs.com/sun/p/1373675.html
Copyright © 2011-2022 走看看