Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# speedup method call of a generic class using expressions

I need to call an instance method of a generic class. The signature looks like this:

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

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

    void Write(Stream s, T v);
}

I managed to get it working by using expressions and DynamicInvoke. Sadly the performance of DynamicInvoke isn't that great. I can't cast the delegate to an Action<MemoryStream, T> because I don't know the type at compile time.

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();
    }
}

Please note, I didn't benchmark the lambda generation, just the call to DynamicInvoke.

Is there any way to replace DynamicInvoke with something faster?

Update: I evaluated the 3 answers which contained code samples and choose to go with Lasse V. Karlsen answer because of the simplicity. (Note on Grax's code: despite caching the MakeGenericMethod call it seems to be way slower than wrapping Invoke in a delegate)

             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 |
like image 412
coalmee Avatar asked Aug 30 '25 17:08

coalmee


1 Answers

The way to do this is to go through a proper generic method, wrapping up a cast from object to the T, and skipping the entire dynamic invoke.

From your code in the pastebin, here's a new version of your Test class:

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);
        };
    }
}

On my machine your code, with the above as well executes as:

Your test: 1285ms
My test: 20ms
Explicit: 4ms

like image 155
Lasse V. Karlsen Avatar answered Sep 02 '25 07:09

Lasse V. Karlsen