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
LVL 1
MarjaRAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

ste5anSenior DeveloperCommented:
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
0
ambienceCommented:
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.
0
MarjaRAuthor Commented:
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?
0
Get your problem seen by more experts

Be seen. Boost your question’s priority for more expert views and faster solutions

ambienceCommented:
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.
0
MarjaRAuthor Commented:
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?
0
ambienceCommented:
No, Activator.CreateInstance is slow and whole exercise you did was to get rid of it.

Obviously, caching objects/delegates (you are in fact caching a whole object/set of objects when you cache a delegate) could have side-effects like increased memory utilization AND more importantly since all your  "AllowDeleteUser" classes are now singletons (only one instance ever created and then cached) you'd have to keep that in mind in the implementation.

I would suggest that you keep it at what you have achieved so far and come back to it when further improvements are needed.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.