.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指令, 直接指向了方法的入口.这就节省了大部分的额外消耗时间.

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

posted @ 2009-01-11 15:41  Jeffrey Sun  阅读(753)  评论(3编辑  收藏  举报