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 |
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With