Link to home
Start Free TrialLog in
Avatar of MarjaR
MarjaRFlag for Netherlands

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:

    ''' <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

Open in new window


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
Avatar of ste5an
ste5an
Flag of Germany image

Well, use an appropriate pattern.

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

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.
Avatar of MarjaR

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:

    ''' <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

Open in new window


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.
Avatar of MarjaR

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?
ASKER CERTIFIED SOLUTION
Avatar of ambience
ambience
Flag of Pakistan image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial