程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#中辦法的直接挪用、反射挪用與Lambda表達式挪用比較

C#中辦法的直接挪用、反射挪用與Lambda表達式挪用比較

編輯:C#入門知識

C#中辦法的直接挪用、反射挪用與Lambda表達式挪用比較。本站提示廣大學習愛好者:(C#中辦法的直接挪用、反射挪用與Lambda表達式挪用比較)文章只能為提供參考,不一定能成為您想要的結果。以下是C#中辦法的直接挪用、反射挪用與Lambda表達式挪用比較正文


想挪用一個辦法很輕易,直接代碼挪用就行,此人人都邑。其次呢,還可使用反射。不外經由過程反射挪用的機能會遠遠低於直接挪用——至多從相對時光下去看切實其實是如許。固然這是個盡人皆知的景象,我們照樣來寫個法式來驗證一下。好比我們如今新建一個Console運用法式,編寫一個最簡略的Call辦法。

class Program
{
    static void Main(string[] args)
    {
       
    }

    public void Call(object o1, object o2, object o3) { }
}

Call辦法接收三個object參數卻沒有任何完成,如許我們便可以讓測試專注於辦法挪用,而並不是辦法完成自己。因而我們開端編寫測試代碼,比擬一下辦法的直接挪用與反射挪用的機能差距:

static void Main(string[] args)
{
    int times = 1000000;
    Program program = new Program();
    object[] parameters = new object[] { new object(), new object(), new object() };
    program.Call(null, null, null); // force JIT-compile

    Stopwatch watch1 = new Stopwatch();
    watch1.Start();
    for (int i = 0; i < times; i++)
    {
        program.Call(parameters[0], parameters[1], parameters[2]);
    }
    watch1.Stop();
    Console.WriteLine(watch1.Elapsed + " (Directly invoke)");

    MethodInfo methodInfo = typeof(Program).GetMethod("Call");
    Stopwatch watch2 = new Stopwatch();
    watch2.Start();
    for (int i = 0; i < times; i++)
    {
        methodInfo.Invoke(program, parameters);
    }
    watch2.Stop();
    Console.WriteLine(watch2.Elapsed + " (Reflection invoke)");

    Console.WriteLine("Press any key to continue...");
    Console.ReadKey();
}

履行成果以下:

00:00:00.0119041 (Directly invoke)
00:00:04.5527141 (Reflection invoke)
Press any key to continue...

經由過程各挪用一百萬次所花時光來看,二者在機能上具稀有量級的差距。是以,許多框架在必需應用到反射的場景中,都邑想法應用一些較高等的替換計劃來改良機能。例如,應用CodeDom生成代碼並靜態編譯,或許應用Emit來直接編寫IL。不外自從.NET 3.5宣布了Expression相干的新特征,我們在以上的情形下又有了更便利並直不雅的處理計劃。

懂得Expression相干特征的同伙能夠曉得,System.Linq.Expressions.Expression<TDelegate>類型的對象在挪用了它了Compile辦法以後將獲得一個TDelegate類型的拜托對象,而挪用一個拜托對象與直接挪用一個辦法的機能開支相差無幾。那末關於下面的情形,我們又該獲得甚麼樣的Delegate對象呢?為了使處理計劃足夠通用,我們必需將各類簽名的辦法同一至異樣的拜托類型中,以下:

public Func<object, object[], object> GetVoidDelegate()
{
    Expression<Action<object, object[]>> exp = (instance, parameters) =>
        ((Program)instance).Call(parameters[0], parameters[1], parameters[2]);

    Action<object, object[]> action = exp.Compile();
    return (instance, parameters) =>
    {
        action(instance, parameters);
        return null;
    };
}

如上,我們就獲得了一個Func<object, object[], object>類型的拜托,這意味它接收一個object類型與object[]類型的參數,和前往一個object類型的成果——等等,同伙們有無發明,這個簽名與MethodInfo類型的Invoke辦法完整分歧?不外可喜可賀的是,我們如今挪用這個拜托的機能遠高於經由過程反射來挪用了。那末關於有前往值的辦法呢?那結構一個拜托對象就更便利了:

public int Call(object o1, object o2) { return 0; }

public Func<object, object[], object> GetDelegate()
{
    Expression<Func<object, object[], object>> exp = (instance, parameters) =>
        ((Program)instance).Call(parameters[0], parameters[1]);

    return exp.Compile();
}

至此,我想同伙們也曾經可以或許輕松得出挪用靜態辦法的拜托結構方法了。可見,這個處理計劃的症結在於結構一個適合的Expression<TDelegate>,那末我們如今就來編寫一個DynamicExecuter類來作為一個較為完全的處理計劃:


public class DynamicMethodExecutor
{
    private Func<object, object[], object> m_execute;

    public DynamicMethodExecutor(MethodInfo methodInfo)
    {
        this.m_execute = this.GetExecuteDelegate(methodInfo);
    }

    public object Execute(object instance, object[] parameters)
    {
        return this.m_execute(instance, parameters);
    }

    private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo)
    {
        // parameters to execute
        ParameterExpression instanceParameter =
            Expression.Parameter(typeof(object), "instance");
        ParameterExpression parametersParameter =
            Expression.Parameter(typeof(object[]), "parameters");

        // build parameter list
        List<Expression> parameterExpressions = new List<Expression>();
        ParameterInfo[] paramInfos = methodInfo.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            // (Ti)parameters[i]
            BinaryExpression valueObj = Expression.ArrayIndex(
                parametersParameter, Expression.Constant(i));
            UnaryExpression valueCast = Expression.Convert(
                valueObj, paramInfos[i].ParameterType);

            parameterExpressions.Add(valueCast);
        }

        // non-instance for static method, or ((TInstance)instance)
        Expression instanceCast = methodInfo.IsStatic ? null :
            Expression.Convert(instanceParameter, methodInfo.ReflectedType);

        // static invoke or ((TInstance)instance).Method
        MethodCallExpression methodCall = Expression.Call(
            instanceCast, methodInfo, parameterExpressions);
       
        // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
        if (methodCall.Type == typeof(void))
        {
            Expression<Action<object, object[]>> lambda =
                Expression.Lambda<Action<object, object[]>>(
                    methodCall, instanceParameter, parametersParameter);

            Action<object, object[]> execute = lambda.Compile();
            return (instance, parameters) =>
            {
                execute(instance, parameters);
                return null;
            };
        }
        else
        {
            UnaryExpression castMethodCall = Expression.Convert(
                methodCall, typeof(object));
            Expression<Func<object, object[], object>> lambda =
                Expression.Lambda<Func<object, object[], object>>(
                    castMethodCall, instanceParameter, parametersParameter);

            return lambda.Compile();
        }
    }
}

DynamicMethodExecutor的症結就在於GetExecuteDelegate辦法中結構Expression Tree的邏輯。假如您關於一個Expression Tree的構造不太懂得的話,無妨測驗考試一下應用Expression Tree Visualizer 來對一個現成的Expression Tree停止不雅察和剖析。我們將一個MethodInfo對象傳入DynamicMethodExecutor的結構函數以後,就可以將各組分歧的實例對象和參數對象數組傳入Execute停止履行。這一切就像應用反射來停止挪用普通,不外它的機能就有了顯著的進步。例如我們添加更多的測試代碼:


DynamicMethodExecutor executor = new DynamicMethodExecutor(methodInfo);
Stopwatch watch3 = new Stopwatch();
watch3.Start();
for (int i = 0; i < times; i++)
{
    executor.Execute(program, parameters);
}
watch3.Stop();
Console.WriteLine(watch3.Elapsed + " (Dynamic executor)");

如今的履行成果則是:

00:00:00.0125539 (Directly invoke)
00:00:04.5349626 (Reflection invoke)
00:00:00.0322555 (Dynamic executor)
Press any key to continue...

現實上,Expression<TDelegate>類型的Compile辦法恰是應用Emit來生成拜托對象。不外如今我們曾經無需將眼光放在更低真個IL上,只需應用高真個API來停止Expression Tree的結構,這無疑是一種提高。不外這類辦法也有必定局限性,例如我們只能對私有辦法停止挪用,而且包括out/ref參數的辦法,或許除辦法外的其他類型成員,我們就沒法如上例般舒服地編寫代碼了。

彌補

木野狐兄在評論中援用了Code Project的文章《A General Fast Method Invoker》,個中經由過程Emit構建了FastInvokeHandler拜托對象(其簽名與Func<object, object[], object>完整雷同)的挪用效力仿佛較“辦法直接”挪用的機能更高(固然從原文示例看來並不是如斯)。現實上FastInvokeHandler其外部完成與DynamicMethodExecutor完整雷同,竟然有如斯使人弗成思議的表示其實讓人啧啧稱奇。我猜想,FastInvokeHandler與DynamicMethodExecutor的機能優勢能夠表現在以下幾個方面:

1.范型拜托類型的履行機能較非范型拜托類型略低(求證)。
2.多了一次Execute辦法挪用,喪失部門機能。
3.生成的IL代碼更加短小緊湊。
4.木野狐兄沒有應用Release形式編譯。:P

不曉得能否有對此感興致的同伙可以或許再做一個測試,不外請留意此類機能測試必定須要在Release編譯下停止(這點很輕易被疏忽),不然意義其實不年夜。

另外,我還想強調的就是,本篇文章停止是純技巧上的比擬,並不是在引誘年夜家尋求點滴機能上的優化。有時刻看到一些關於比擬for或foreach機能好壞的文章讓很多同伙都糾結與此,乃至弄得面紅耳赤,我總會認為有些迫不得已。其實從實際下去說,進步機能的方法有許很多多,記適合時在年夜學裡進修Introduction to Computer System這門課時得一個功課就是為一段C法式作機能優化,其時用到很多手腕,例如內聯辦法挪用以削減CPU指令挪用次數、調劑輪回嵌套次序以進步CPU緩存射中率,將一些代碼應用內嵌ASM調換等等,可謂“無所不消其極”,年夜家都在為幾個時鐘周期的機能進步而奮發圖強喝彩雀躍……

那是實際,是在進修。然則在現實應用中,我們還必需准確看待學到的實際常識。我常常說的一句話是:“任何運用法式都邑有其機能瓶頸,只要從機能瓶頸著手能力做到事半功倍的成果。”例如,通俗Web運用的機能瓶頸常常在內部IO(特別是數據庫讀寫),要真正進步機能必需從此動手(例如數據庫調優,更好的緩存設計)。正因如斯,開辟一個高機能的Web運用法式的症結不會在說話或說話運轉情況上,.NET、RoR、PHP、Java等等在這一范疇都表示優越。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved