MarjaR
asked on
Reducing reflection cost: replace MethodInfo.Invoke with delegates?
HI,
We're writing a modular webapplication where we often need to dynamically trigger methods in DLL's that aren't always present.
For example, prior to deleting a User record, we ask all available plugins (separate DLL's) if there is a reason not to delete the User record.
To be able to do that, each relevant plugin would have some generic class with a method named "AllowDeleteUser" which needs to be dynamically triggered/questioned before deleting the User record.
Currently we do this using Reflection and the MethodInfo.Invoke method, like this:
But.... we understand that doing it this way is costly in terms of performance, and we would be better off replacing MethodInfo.Invoke with a delegates mechanism and by caching those delegates.
Can anyone point me in the right direction as to how to replace our current InvokeMethod() implementation with a better one using delegates?
Any pointers would be highly appreciated!
Best regards, Marja
We're writing a modular webapplication where we often need to dynamically trigger methods in DLL's that aren't always present.
For example, prior to deleting a User record, we ask all available plugins (separate DLL's) if there is a reason not to delete the User record.
To be able to do that, each relevant plugin would have some generic class with a method named "AllowDeleteUser" which needs to be dynamically triggered/questioned before deleting the User record.
Currently we do this using Reflection and the MethodInfo.Invoke method, like this:
''' <summary>
''' Dynamically invoke the given method, passing the given arguments.
''' </summary>
''' <param name="p_sType">The type that implements the method</param>
''' <param name="p_sMethodName">The name of the method to invoke.</param>
''' <param name="p_aArgs">The arguments.</param>
''' <returns></returns>
Protected Function InvokeMethod(ByVal p_sType As String, ByVal p_sMethodName As String, ByRef p_aArgs As wsArrayList) As Object
Dim l_oObject As Object = Nothing
' Get the Type for the class
Dim l_oType As Type = TryCast(Type.GetType(p_sType), Type)
If l_oType IsNot Nothing Then
Dim l_oMethod As MethodInfo = TryCast(l_oType.GetMethod(p_sMethodName), MethodInfo)
If l_oMethod IsNot Nothing Then
' found one, see if it allows any arguments
Try
Dim l_oInstance As Object = Activator.CreateInstance(l_oType)
If l_oInstance IsNot Nothing Then
Dim l_aParms As Array = l_oMethod.GetParameters
If l_aParms.Length = 0 Then
' no arguments allowed, just pass Nothing on invocation
l_oObject = l_oMethod.Invoke(l_oInstance, Nothing)
Else
' pass the arguments to the methods on invocation
l_oObject = l_oMethod.Invoke(l_oInstance, p_aArgs.ToArray)
End If
Return l_oObject
End If
Catch ex As Exception
Dim l_oErrorHandler As New wsErrorHandler()
l_oErrorHandler.LogError(ex, "wsDynEvaluator.InvokeMethod(p_sType,p_sMethodName) - " & p_sType & " -> " & p_sMethodName)
' let caller decide what to do now
Throw ex
End Try
End If
End If
' Return the object that was returned by the called method.
Return l_oObject
End Function
But.... we understand that doing it this way is costly in terms of performance, and we would be better off replacing MethodInfo.Invoke with a delegates mechanism and by caching those delegates.
Can anyone point me in the right direction as to how to replace our current InvokeMethod() implementation with a better one using delegates?
Any pointers would be highly appreciated!
Best regards, Marja
You cannot hold on to a delegate if you are creating objects per every method call. Few things:
1 - Why not use an IOC container? I have a similar application where the UI Shell uses NInject and with a custom Binder I setup the Kernel such that all classes with MEF EXport attribute are found by the Kernel in plugin assemblies. An even better approach might be to use a custom Attribute to allow more flexibility in Binding. Anywhere I need a component I would do
_kernel.GetAll<SomeNeededT ypeOrInter face>();
This is the service locator pattern and arguably an anti-pattern, but if used sparingly and intelligently it can serve as a good pattern.
An IOC is also a better choice because it can also factor in the Instancing mode, i.e. an object that has to be a Singleton. Your code is creating new instance per every invoke but maybe there is a scenario where the interceptor makes more sense to be a state-ful object? An IOC can easily handle this, since most frameworks provide a way to specify instancing needs.
2 - At bootstrap build a registry/cache of all interceptor classes and generate a DynamicMethod with IL to mimic the following code
var obj = new SomeType();
type.SomeMethod(...);
This dynamic method can be cached and re-used over an over again. The performance is close to that of a delegate and it avoids altogether the horrible calls to Activator.CreateInstance. Have a look at this framework and take a look at the DynamicSurrogate implementation http://www.codeproject.com/Articles/13166/A-Fast-Compact-Serialization-Framework.
Sorry for all C# stuff, I'm really behind on VB.NET but you can easily adapt all code to VB.
1 - Why not use an IOC container? I have a similar application where the UI Shell uses NInject and with a custom Binder I setup the Kernel such that all classes with MEF EXport attribute are found by the Kernel in plugin assemblies. An even better approach might be to use a custom Attribute to allow more flexibility in Binding. Anywhere I need a component I would do
_kernel.GetAll<SomeNeededT
This is the service locator pattern and arguably an anti-pattern, but if used sparingly and intelligently it can serve as a good pattern.
An IOC is also a better choice because it can also factor in the Instancing mode, i.e. an object that has to be a Singleton. Your code is creating new instance per every invoke but maybe there is a scenario where the interceptor makes more sense to be a state-ful object? An IOC can easily handle this, since most frameworks provide a way to specify instancing needs.
2 - At bootstrap build a registry/cache of all interceptor classes and generate a DynamicMethod with IL to mimic the following code
var obj = new SomeType();
type.SomeMethod(...);
This dynamic method can be cached and re-used over an over again. The performance is close to that of a delegate and it avoids altogether the horrible calls to Activator.CreateInstance. Have a look at this framework and take a look at the DynamicSurrogate implementation http://www.codeproject.com/Articles/13166/A-Fast-Compact-Serialization-Framework.
Sorry for all C# stuff, I'm really behind on VB.NET but you can easily adapt all code to VB.
ASKER
Thank you both for your replies. But... regretfully, they are way passed my knowledge/experience level.
I'm not in a position where I have the time or the possibitliy to rewrite our whole system structure, so currently I'm merely looking for an easy replacement for my original dynamic method invocation.
Anyway, in the meantime I've managed to rewrite my original method to use delegates now, like this:
I've did some benchmarking, and this solutions turns out to be somewhat faster than the original method.Invoke() solution.
Do you see anything here that might further enhance this method?
I'm not in a position where I have the time or the possibitliy to rewrite our whole system structure, so currently I'm merely looking for an easy replacement for my original dynamic method invocation.
Anyway, in the meantime I've managed to rewrite my original method to use delegates now, like this:
''' <summary>
''' Dynamically invoke the given method, passing the given arguments.
''' </summary>
''' <param name="p_sType">The type that implements the method</param>
''' <param name="p_sMethodName">The name of the method to invoke.</param>
''' <param name="p_aArgs">The arguments.</param>
''' <returns></returns>
Protected Function InvokeMethod(ByVal p_sType As String, ByVal p_sMethodName As String, ByRef p_aArgs As wsArrayList) As Object
Dim l_oObject As Object = Nothing, l_oDelegate As System.Delegate = Nothing, l_sDelegateCacheKey As String = p_sType & "_" & p_sMethodName
Dim l_hDelegateCache As Hashtable = TryCast(wsCacheManager.GetFromCache("wsDynamicMethodDelegates"), Hashtable)
If l_hDelegateCache Is Nothing Then
l_hDelegateCache = New Hashtable
End If
' first see if we've already looked up this dynamic function before
If l_hDelegateCache.Count > 0 AndAlso l_hDelegateCache.ContainsKey(l_sDelegateCacheKey) Then
l_oDelegate = TryCast(l_hDelegateCache(l_sDelegateCacheKey), System.Delegate)
End If
If l_oDelegate Is Nothing Then
' get the Type for the class
Dim l_oType As Type = TryCast(Type.GetType(p_sType), Type)
If l_oType IsNot Nothing Then
Dim l_oMethod As MethodInfo = TryCast(l_oType.GetMethod(p_sMethodName), MethodInfo)
If l_oMethod IsNot Nothing Then
Try
Dim l_oInstance As Object = Activator.CreateInstance(l_oType)
If l_oInstance IsNot Nothing Then
' create and cache delegate
l_oDelegate = l_oMethod.CreateDelegate(l_oInstance)
' remember delegate for 24 hours
l_hDelegateCache.Add(l_sDelegateCacheKey, l_oDelegate)
wsCacheManager.AddToSlidingCache("wsDynamicMethodDelegates", l_hDelegateCache, 1440)
End If
Catch ex As Exception
' log error
Dim l_oErrorHandler As New wsErrorHandler()
l_oErrorHandler.LogError(ex, "wsDynEvaluator.InvokeMethod(p_sType,p_sMethodName) - e1 - " & p_sType & " -> " & p_sMethodName)
' let caller decide what to do now
Throw ex
End Try
End If
End If
End If
If l_oDelegate IsNot Nothing Then
' now invoke the requested method
Try
Dim l_aParms As Array = l_oDelegate.GetMethodInfo.GetParameters()
If l_aParms.Length = 0 Then
' no arguments allowed, just pass Nothing on invocation
l_oObject = l_oDelegate.DynamicInvoke(Nothing)
Else
' pass the arguments to the methods on invocation
l_oObject = l_oDelegate.DynamicInvoke(p_aArgs.ToArray)
End If
Catch ex As Exception
' log error
Dim l_oErrorHandler As New wsErrorHandler()
l_oErrorHandler.LogError(ex, "wsDynEvaluator.InvokeMethod(p_sType,p_sMethodName) - e2 - " & p_sType & " -> " & p_sMethodName)
' let caller decide what to do now
Throw ex
End Try
End If
' Return the object that was returned by the called method.
Return l_oObject
End Function
I've did some benchmarking, and this solutions turns out to be somewhat faster than the original method.Invoke() solution.
Do you see anything here that might further enhance this method?
Well, if it works then its good. You have however introduced a side-effect of caching objects (holding onto objects) whereas the first version was creating a new object per every call.
ASKER
Caching the delegates objects is a choice I made, to reduce the cost of calling Activator.CreateInstance(l _oType) every time we need the dynamic method.
Would just calling Activator.CreateInstance() everytime be a better way?
Would just calling Activator.CreateInstance()
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Here it looks like chain of responsibility, cause you can abort the evaluation when one plugin denies the deletion request.
btw, when I think of an plugin architecture, then reflection is only necessary when loading the plugins. Here I would encapsulate all my plugins in a single class derived from IMyPlugin for the common methods and IManageUser for plugins which must be queried while your user deletion process.
These mechanisms are already encsuplated in IoC (Inversion of Control) container like Unity or MEF.
See also
* Application Extensibility: MEF vs. IoC
* Unity vs. MEF: Picking the Right Dependency Injection Manager