Why is casting to a generic type slower than an explicit cast in C#?

417 views Asked by At

I'm building a message dispatch map in C# and mostly just playing around with some different approaches. I am curious about a performance difference I am measuring, but it's not obvious why from looking at the IL.

The message map:

delegate void MessageHandler(Message message);
AddHandler(Type t, MessageHandler handler) 
{ 
    /* add 'handler' to messageMap invocation list */ 
}

delegate void GenericMessageHandler<T>(T message);
AddHandler<T>(GenericMessageHandler<T> handler) where T: Message
{
    AddHandler(typeof(T), e => { handler((T)e); });
}

Dictionary<Type, MessageHandler> messageMap;

I then have a class hierarchy of Messages, similar to EventArgs in WPF, for example:

public class Message {}
public class VelocityUpdateMessage : Message

and observer classes with handler functions:

void HandleVelocityUpdate(VelocityUpdateMessage message) { ... }

I am measuring 2 ways of adding & invoking handlers. I am wrapping the delegate call so I can get a bit of conceptual type safety and therein lies the perf difference.

Approach 1: listener calls

AddHandler(typeof(VelocityUpdateMessage), 
           e => { HandleVelocityUpdate((VelocityUpdateMessage)e); });

Approach 2: listener calls

AddHandler<VelocityUpdateMessage>(HandleVelocityUpdate);

Both approaches build a MessageHandler delegate that makes a cast and the same method call, but calling the delegates built using approach #2 is a wee bit slower even though the generated IL looks identical. Is it extra runtime overhead in casting to a generic type? Is it the type constraint? I would expect the JITted delegates to be the same once the generic type is resolved.

Thanks for any info.

2

There are 2 answers

0
Chronic Game Programmer On BEST ANSWER

ok, I had to look at MethodBody.GetILAsByteArray() IL rather than the ILSpy results for the delegates to get to the bottom of this. Using a generic delegate to wrap my message handler and cast the message type generates:

0000 : ldarg.0
0001 : ldfld
0006 : ldarg.1
0007 : unbox.any
000C : callvirt void MessageTest.Message+tMessageHandler`1[MessageTest.VelocityUpdateMessage].Invoke(‌​MessageTest.VelocityUpdateMessage) 
0011 : ret

where the wrapper delegate with the explicit cast generates:

0000 : ldarg.0
0001 : ldarg.1
0002 : castclass
0007 : call void Message.Component.HandleVelocityUpdate(MessageTest.VelocityUpdateMessage)
000C : ret

So yes, there is minimal overhead from using generics in this way.

4
Jonathan Allen On

The below line creates a new instance of an anonymous type each time it is called. Could that the cause of your performance difference?

AddHandler(typeof(T), e => { handler((T)e); });