2016-09-09 44 views
3

我需要調用泛型類的實例方法。簽名看起來像這樣:使用表達式的C#加速方法調用泛型類

public class HandlerFactory 
{ 
    public static IHandler<T> Create<T>(); 
} 

public interface IHandler<T> 
{ 
    T Read(Stream s); 

    void Write(Stream s, T v); 
} 

我設法通過使用表達式和DynamicInvoke得到它的工作。可悲的是DynamicInvoke的表現並不好。我不能將代理強制轉換爲Action<MemoryStream, T>,因爲我不知道編譯時的類型。

public class Test 
{ 
    public static void Write(MemoryStream s, object value) 
    { 
     var del = GetWriteDelegateForType(value.GetType()); 

     // TODO: How to make this faster? 
     del.DynamicInvoke(s, value); 
    } 

    private static object GetHandlerForType(Type type) 
    { 
     var expr = Expression.Call(typeof(HandlerFactory), "Create", new[] { type }); 
     var createInstanceLambda = Expression.Lambda<Func<object>>(expr).Compile(); 
     return createInstanceLambda(); 
    } 

    private static Delegate GetWriteDelegateForType(Type type) 
    { 
     var handlerObj = GetHandlerForType(type); 
     var methodInfo = handlerObj.GetType().GetMethod("Write", new[] { typeof(MemoryStream), type }); 

     var arg1 = Expression.Parameter(typeof(MemoryStream), "s"); 
     var arg2 = Expression.Parameter(type, "v"); 

     var handlerObjConstant = Expression.Constant(handlerObj); 
     var methodCall = Expression.Call(handlerObjConstant, methodInfo, arg1, arg2); 

     var lambda = Expression.Lambda(methodCall, arg1, arg2); 

     return lambda.Compile(); 
    } 
} 

請注意,我沒有基準lambda代,只是調用DynamicInvoke。

有什麼方法可以用更快的東西來代替DynamicInvoke嗎?

更新:我評估了3個答案,其中包含代碼示例,並選擇與Lasse V. Karlsen答案一起歸因於簡單性。 (上Grax的代碼注:儘管緩存MakeGenericMethod調用它似乎比在委託包裝調用方法要慢)

   Method |  Median |  StdDev | 
------------------- |-------------- |----------- | 
      MyLambda | 1,133.2459 ns | 25.1972 ns | 
     ExplicitCall |  0.6450 ns | 0.0256 ns | 
Test2DelegateLasse | 10.6032 ns | 0.2141 ns | 
     LambdaGroo | 10.7274 ns | 0.1099 ns | 
     InvokeGrax | 349.9428 ns | 14.6841 ns | 
+1

你可以創建一個人們可以修補的[mcve]嗎?一個代表一組特定的現有代碼,以便我可以在我的計算機上運行它以獲得基線,然後修改它以查看是否可以更快地實現它? –

+0

擁有插件工廠的通用接口並不合理,恕我直言。 – Groo

+0

這是一個第三方庫,所以我不能決定它是否有意義。以下是演示性能的完整示例:http://pastebin.com/K3q4dgMk。我的電腦上的820ms到2ms比較直接方法調用和DynamicInvoke。 – coalmee

回答

4

要做到這一點的方法是通過適當的泛型方法,包裝從object加強到T,並跳過整個動態調用。

從引擎收錄你的代碼,這裏是你的測試類的新版本:

public class Test2 
{ 
    private static readonly Action<MemoryStream, object> del; 

    static Test2() 
    { 
     var genericCreateMethod = typeof(Test2).GetMethod("CreateWriteDelegate", BindingFlags.Static | BindingFlags.NonPublic); 
     var specificCreateMethod = genericCreateMethod.MakeGenericMethod(typeof(Model)); 
     del = (Action<MemoryStream, object>)specificCreateMethod.Invoke(null, null); 
    } 

    public static void Write(MemoryStream s, object value) 
    { 
     del(s, value); 
    } 

    private static Action<MemoryStream, object> CreateWriteDelegate<T>() 
    { 
     var handler = HandlerFactory.Create<T>(); 
     return delegate (MemoryStream s, object value) 
     { 
      handler.Write(s, (T)value); 
     }; 
    } 
} 

在我的機器代碼,上述以及執行爲:

您的測試: 1285ms
我的測試:20ms的
明確:4ms的

+0

您的解決方案是最簡單的解決方案,它甚至比我的解決方案在我的機器上運行速度快5%。但是您可能想將初始化從靜態構造函數移動到Write方法中,因爲現在您已對硬編碼的通用參數進行了硬編碼。此外,委託語法是如此2006. :) – Groo

+0

委託語法可能會老,但我傾向於在發佈代碼時謹慎一邊,以避免人們因錯誤原因而解僱代碼:)代碼爲no它應該被包裝在一個類中,每類緩存創建的委託並按需創建它們。我只是簡單地鏡像OP的pastebin代碼,以便於比較。 –

+0

我的觀點是,你爲'typeof(Model)'替換了'value.GetType()',這至少對我來說比較難一些。就我所見,OP的代碼中沒有'Model'。 – Groo

0

你可以只讓一個Action<MemoryStream, object>和簡單地使用Expression.Convert()改變v型從objecttype

爲了提高性能,您可以將這些Action s存儲在某些Type -keyed字典(併發?)中,但表達式需要對Create內部的處理程序進行一些更改。

+0

我已經緩存表達式,問題只是對DynamicInvoke的調用很慢。我無法更改HandlerFactory或IHandler的代碼,因爲它是第三方庫。 – coalmee

+0

我的解決方案不涉及庫的任何更改。您只需更改'GetWriteDelegateForType',使其接受'object'類型的參數'v',然後對其執行轉換。然後可以將此表達式樹編譯爲非泛型「Action」,並調用而不會造成任何性能損失。然後,可以緩存「操作」,其性能應與常規方法相似。如果有任何幫助,我可以爲所產生的「Action」添加僞代碼,稍後您可以嘗試將其轉換爲表達式樹。 – kiziu

3

編寫一個通用方法,並使用帶有Invoke的MakeGenericMethod來調用它。

將您想調用的方法存儲在一個靜態變量中,這樣GetMethod調用只需要發生一次。

然後在該MethodInfo上調用MakeGenericMethod並調用結果。

private static MethodInfo GenericWriteMethod = 
    typeof(Test).GetMethod("GenericWrite", BindingFlags.NonPublic | BindingFlags.Static); 

public static void Write(MemoryStream s, object value) 
{ 
    GenericWriteMethod 
     .MakeGenericMethod(value.GetType()) 
     .Invoke(null, new object[] { s, value }); 
} 

private static void GenericWrite<T>(MemoryStream s, T value) 
{ 
    HandlerFactory.Create<T>().Write(s, value); 
} 

在我的測試中,這使它快了100多倍。

+0

這個解決方案也可以被改進以避免'MakeingGenericMethod'。將這些與表達式結合在一起可以使最終的「Action」成爲最快的解決方案。 – kiziu

+0

我通過重寫粘貼在pastebin上的代碼進行測試,以完成您在此處發佈的相同內容,並將其用於明確寫入的約5倍時間。這絕對是要走的路。 –

1

您應該簡單地創建一個Action<Stream, object>代替:

static Action<Stream, object> GetWriteDelegateForType(Type type) 
{ 
    // get the actual generic method 
    var handlerObj = GetHandlerForType(type); 
    var methodInfo = handlerObj 
       .GetType() 
       .GetMethod("Write", new[] { typeof(MemoryStream), type }); 

    // but use (Stream, object) parameters instead 
    var streamArg = Expression.Parameter(typeof(Stream), "s"); 
    var objectArg = Expression.Parameter(typeof(object), "v"); 

    // this will cast object to T 
    var tCast = Expression.Convert(objectArg, type); 

    var handlerObjConstant = Expression.Constant(handlerObj); 
    var body = Expression.Call(handlerObjConstant, methodInfo, streamArg, tCast); 
    var lambda = Expression.Lambda<Action<Stream, object>>(body, streamArg, objectArg); 

    // and compile to an actual Action<Stream, object> 
    return lambda.Compile(); 
} 

然後你只需要調用它像一個普通的委託:

static void Write(MemoryStream s, object value) 
{ 
    var action = GetWriteDelegateForType(value.GetType()); 
    action(s, value); 
} 

這也將是不錯的主意緩存委託:

static readonly ConcurrentDictionary<Type, Action<Stream, object>> _cache = 
    new ConcurrentDictionary<Type, Action<Stream, object>>(); 

static void Write(MemoryStream s, object value) 
{ 
    var type = value.GetType(); 
    var action = _cache.GetOrAdd(type, GetWriteDelegateForType); 
    action(s, value); 
} 
+0

非常感謝您加快我的表達代碼。但我會堅持Lasse V. Karlsen的回答,因爲我認爲它比表情更容易理解。儘管如此,你的代碼很快! – coalmee

0

添加一個其他答案,因爲這是一個比我的第一個明顯不同。

TLDR:添加非通用接口,創建一個字典來緩存類型的處理程序,使用非通用寫方法調用處理程序。


向ExplicitHandler中添加一個非通用接口,以便於在非泛型代碼中進行交互。

public interface IHandler 
{ 
    void Write(Stream s, object v); 
} 

實施ExplicitHandler非通用Write方法轉換爲T和調用通用寫

void IHandler.Write(Stream s, object v) 
    { 
     Write(s, (T)v); 
    } 

緩存在字典中重寫源

public class Test 
{ 
    static Dictionary<Type, IHandler> delegates = new Dictionary<Type, IHandler>(); 

    public static void Write(MemoryStream s, object value) 
    { 
     IHandler handler; 

     var type = value.GetType(); 
     if (!delegates.TryGetValue(type, out handler)) 
     { 
      handler = (IHandler)typeof(HandlerFactory).GetMethod(nameof(HandlerFactory.Create)).MakeGenericMethod(type).Invoke(null, null); 
      delegates[type] = handler; 
     } 

     handler.Write(s, value); 
    } 
} 

這裏的處理程序:http://pastebin.com/hmfj2Gv2

+0

感謝您的回覆,但正如評論中所述,我無法修改IHandler界面,因爲它來自第三方庫。 – coalmee

+0

非泛型IHandler與泛型IHandler接口完全不同。你甚至可以稱之爲別的東西。真正的問題是你可以修改顯式處理程序來實現你的非通用寫入方法。如果不是,則可以始終編寫一個包裝類,其中包含非通用Write方法和對通用處理程序的引用。 – Grax

相關問題