Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A class to dynamically create delegates of functions at runtime

0.00/5 (No votes)
6 Oct 2006 2  
A class to dynamically create delegates of functions at runtime. Wraps code by Luyan, from his article: A General Fast Method Invoker.

Sample Image

Sample Image

Sample Image

Sample Image

Introduction

This article describes a .NET (2.0) class that uses Luyan's Project, "A General Fast Method Invoker", to dynamically create delegates and invoke them at runtime.

Background

The C# project by Luyan entitled, A General Fast Method Invoker, is extremely useful for dynamically creating delegates and invoking them. I needed this functionality when I wanted my users to access helpful functions in a Form class by simply clicking on a list of the functions, and filling in the appropriate function arguments.

Using System.Reflection, you can obtain a MethodInfo object for any (or all) of the functions that you have written. The MethodInfo object allows you to create a delegate at runtime, which you can then invoke to execute the function that the user selected.

Using the code

The code is in one zip file, but divided into two separate projects.

  1. Unzip the all the entire file into your the same folder that contains your Visual Studio 2005. For me, it was my MyDocuments folder.
  2. From Visual Studio 2005, open the project FastInvoker (it should be in the folder Visual Studio 2005\Projects\FastInvoker). Build the project. It is a C# Class Library.
  3. Run another instance of Visual Studio 2005 and open the project SimpleTestForm, which I stupidly put in the folder Visual Studio 2005\Projects\FastInvokeTestForm.
  4. You might see a broken reference to the FastInvoker. I believe that this broken reference will go away after you build the SimpleTestForm project for the first time. (Don't ask me why).
  5. Look at the code for each project.

The Visual Basic .NET project entitled "SimpleTestForm" instantiates a FastInvoker object after doing some other useful tricks which might amuse you.

The C# project entitled FastInvoker creates the FastInvoke class, which creates the dynamic delegate, stores the dynamic delegate and provides a public method to execute the dynamic delegate. I copied most of this code from Luyan. He deserves all of the real credit for the main functionality in this project. I simply copied Luyan's code into a new class, and created a useful constructor that saves the dynamic delegate that he creates, and also saves other useful information about the called function.

The following Visual Basic .NET (2.0) code is for the SimpleTestForm form that tests the FastInvoke object. (The C# FastInvoke class code is further below.)

' First -  we need to reference the FastInvoker Class

'  Make sure you add a reference to Visual Studio 2005\FastInvoker

Imports FastInvoke = FastInvoker.FastInvoke
'

' Next - Create the Form class, by using

' the Visual basic Windows template

Public Class Form1
    '

    ' Create 2 member variables

    '  The first variable is a list of FastInvoke

    ' objects (one for each function that

    '    you want to dynamically invoke.

    Dim FastInvokeObjectList As New List(Of FastInvoke)
    '

    '  The second variable is a list of names

    ' of the functions that you want to 

    '    dynamically invoke.

    Dim methodNames As New List(Of String)
    '

    ' Use the Form's constructor to search

    ' all of the methods of the of Form object

    '   in order to find the functions that

    '   you want to show to the user.

    Public Sub New()
        ' This call is required by the Windows

        ' Form Designer. (This is not my code)

        InitializeComponent()
        '

        Dim i As Integer = 0
        '

        ' create an array of MethodInfo objects

        ' which has all of Form1's methods

        '   THESE INCLUDE INHERITED METHODS !!!!!!!!!!!!!!!!!!

        Dim methodinfoArray() As _
            System.Reflection.MethodInfo = Me.GetType.GetMethods()
        ' Now, use the MethodInfo.DeclaringType

        ' property of each method in methodinfoArray

        '   in order to determine if the method

        '   is one that I created, or if it is a method

        '   that Form1 inherited from the System.Windows.Form class.

        For Each m As System.Reflection.MethodInfo In methodinfoArray
            If m.DeclaringType Is Me.GetType Then
                ' If the code comes to this spot,

                ' it has found a method within Form1 that I created.

                '  Now I use the FastInvoke class

                '  to create an object that will hold a

                '  dynamically created Delegate for this

                '  method (or function, if you like).

                FastInvokeObjectList.Add(New FastInvoke(Me, m.Name))
                methodNames.Add(m.Name)
                '  I also add the method to a listbox

                '  so that the user of the form

                '    can select it.

                ListBox1.Items.Add(m.Name)
                i = i + 1
            End If
        Next
    End Sub
    '

    '

    ' Now I implement the SelectedIndexChanged

    ' event of the listbox.  This event gets

    '   fired when the user selects

    '   a particular element in the ListBox. 

    '  I obtain the function name that I had

    '  previously saved in the constructor above,

    '    and then append to the parameter types

    '    of the function so that the user

    '    knows what kind of parameters he can enter

    '   (i.e. - int, double, string, etc)

    Private Sub ListBox1_SelectedIndexChanged(ByVal sender As _
            System.Object, ByVal e As System.EventArgs) _
            Handles ListBox1.SelectedIndexChanged
        Dim functionString As String
        '  This line of code gets the function name

        functionString = ListBox1.Items(ListBox1.SelectedIndex) & "("
        '  This For each loop gets the

        '  type name (e.g. "double") of each argument. 

        '     (an argument is a "parameter" in .Net Land).

        For Each p As System.Reflection.ParameterInfo In _
          FastInvokeObjectList(ListBox1.SelectedIndex).MyParameters
            functionString = functionString & p.ParameterType.Name & ","
        Next
        '  Finally, I have to replace the comma

        ' that I inserted at the end of the last

        '    argument type with a closed paren.

        functionString = Mid(functionString, 1, _
                             Len(functionString) - 1) & ")"
        '  Now I insert the entire string which

        '  contains the function specification

        '  into the TextBox that I located above

        '  the ListBox on the Form.

        TextBox1.Text = functionString
        '   I then select the TextBox so that the

        '   user can replace the argument type names

        '   with actual arguments (replace the word

        '   "double" with a number, like 2.5)

        TextBox1.Select()
    End Sub
    '


    '  Next I implement the Click event of the

    '  Button that I placed to the left of

    '  the text box.  The user gets the

    '  result of the function  that he selected

    '  by clicking this button.

    Private Sub Button1_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles Button1.Click
        '

        ' I'll use this TempDouble Variable soon enough

        Dim TempDouble As Double
        '  The Ptype variable is of System.Type.

        '  I use this variablee to dynamically 

        '  convert the arguments (that the user

        '  entered) into their proper types

        '  so that the FastInvoke object can

        '  use them in the dynamic delegate call.

        Dim Ptype As Type

        Dim functioncall As String = Me.TextBox1.Text
        '  I will use the following code to parse

        '  out the function name, and each

        '    function argument.

        Dim PositionOfLeftParen As Integer = InStr(functioncall, "(")
        '  This line gets the function name

        Dim func_name As String = Mid(functioncall, 1, _
                         PositionOfLeftParen - 1)
        Dim i As Integer = 0
        '  This loop makes sure that the function name

        '  is in the list of supported functions

        For Each name As String In methodNames
            If name = func_name Then
                Exit For
            Else
                i = i + 1
            End If
        Next
        If i < methodNames.Count Then
            ' if you get here, you have found

            ' the function, now parse the parameters 

            '  and do the fastinvoke delegate


            '  the params() object array holds

            '  the boxed user arguments that the user entered.

            '  The following crazy code parses

            '  out the commas and parenthesis, and

            '  the converts each parameter

            '  to the type that I specified when I

            '  created the member function of Form1

            Dim params(FastInvokeObjectList(i).NumberOfArguments_
                                                  - 1) As Object
            Dim PositionOfComma As Integer
            Dim k As Integer = 0
            Dim paramtoken As String
            For j As Integer = PositionOfLeftParen + 1 To Len(functioncall)
                PositionOfComma = InStr(Mid(functioncall, j), ",")

                '  If you get here, you have found an argument.

                If PositionOfComma > 0 Then
                    '  put the argument in the variable paramtoken

                    paramtoken = Mid(functioncall, j, PositionOfComma - 1)
                    '  put the arugment's type in the variable Ptype

                    Ptype = _
                      Me.FastInvokeObjectList(i).MyParameters(k).ParameterType

                    '  !!!!!!!! THIS CODE IS ONLY

                    '  SUPPORTING MEMBER FUNCTIONS OF

                    '  FORM1 THAT HAVE NUMBERS AND

                    '  STRINGS  AS PARAMETERS !!!!!!!

                    '

                    '  If the argument is a number,

                    '  convert it the proper number type.

                    '  This was not easy!!!!!  You cannot

                    '  simply use the statement

                    '  CType(paramtoken, Ptype), because

                    '  the CType statement compiles

                    '  "inline" and will not accept a variable

                    '  in it's second paramenter.

                    '  Instead you have to create a number

                    '  that is double, and then use

                    '  System.Convert.ChangeType(object,Type)

                    '  to convert the number to 

                    '  it's proper type. I embedded the

                    '  call to System.Convert.ChangeType

                    '  in the FastInvoke object, as a method of that object.

                    If IsNumeric(paramtoken) Then
                        TempDouble = CType(paramtoken, Double)
                        params(k) = _
                          Me.FastInvokeObjectList(i).TypeConvert(_
                                               TempDouble, Ptype)
                    Else
                        params(k) = paramtoken
                    End If
                    k = k + 1
                    j = j + PositionOfComma - 1
                Else
                    ' If you get here, you have found the last argument

                    paramtoken = Mid(functioncall, j, Len(functioncall) - j)
                    ' the next 8 lines are identical

                    ' to the lines above.  (not very elegant)

                    Ptype = _
                      Me.FastInvokeObjectList(i).MyParameters(k).ParameterType
                    If IsNumeric(paramtoken) Then
                        TempDouble = _
                          CType(paramtoken, Double)
                        params(k) = _
                          Me.FastInvokeObjectList(i).TypeConvert(TempDouble, _
                                                                 Ptype)
                    Else
                        params(k) = paramtoken
                    End If
                    Exit For
                End If
            Next
            '

            '  Now it's time to execute the function

            '  dynamcially.  I call the 

            '  ExecuteDelegate method of the FastInvoke

            '  object. (See the C# project)

            Try
                Dim o As Object = _
                    FastInvokeObjectList(i).ExecuteDelegate(params)
                MsgBox("result = " & o)
            Catch ex As Exception
                MsgBox(ex.Message)
            End Try
        Else
            MsgBox("bad function name  -  try again ")
        End If
        '

    End Sub
    '

    '

    ' Here are some sample functions.

    ' If you would like, add your own functions.  

    ' HOWEVER, the functions must use and return numbers and strings.  

    ' I hope that someone improves my code to create

    ' a more general dynamic type conversion

    ' routine that allows you to give the user functions

    ' that use any kind of object as

    ' a parameter or a return type.

    '

    Function getmethodtest(ByVal p1 As String, _
             ByVal p2 As Integer, ByVal p3 As Double) As String
        getmethodtest = p1 & p2 & p3

    End Function
    '

    Function AddTwoNumbers(ByVal x As Double, _
             ByVal y As Double) As Double
        AddTwoNumbers = x + y
    End Function
    '

    Function SaySomething(ByVal s As String) As String
        SaySomething = "I would like to say " & s
    End Function
    '

End Class

Code for the FastInvoke class is listed below:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;


namespace FastInvoker
{
    public delegate object FastInvokeHandler(object 
                    target, object[] paramters);
  
    
    public class Class1
    {
    }

    public class FastInvoke
    {
        FastInvokeHandler MyDelegate;
        public MethodInfo MyMethodInfo;
        public ParameterInfo [] MyParameters;
        Object HostObject;
        public int NumberOfArguments;

        public FastInvoke(Object MyObject, String MyName)
        {
            HostObject = MyObject;
            Type t2 = MyObject.GetType();
            MethodInfo m2 = t2.GetMethod(MyName);
            MyDelegate = GetMethodInvoker(m2);
            NumberOfArguments = m2.GetParameters().Length;
            MyMethodInfo = m2;
            MyParameters = m2.GetParameters();
        }

        public object ExecuteDelegate(object [] FunctionParameters)
        {
            try
            {
                return (MyDelegate(HostObject, FunctionParameters));
            }
            catch(Exception e)
            {
                Object o = new Object();
                o = e.Message;
                return (o);
              
            }

        }

        private FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, 
                          typeof(object), new Type[] { typeof(object), 
                          typeof(object[]) }, 
                          methodInfo.DeclaringType.Module);
            ILGenerator il = dynamicMethod.GetILGenerator();
            ParameterInfo[] ps = methodInfo.GetParameters();
            Type[] paramTypes = new Type[ps.Length];
            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                    paramTypes[i] = ps[i].ParameterType.GetElementType();
                else
                    paramTypes[i] = ps[i].ParameterType;
            }
            LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];

            for (int i = 0; i < paramTypes.Length; i++)
            {
                locals[i] = il.DeclareLocal(paramTypes[i], true);
            }
            for (int i = 0; i < paramTypes.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_1);
                EmitFastInt(il, i);
                il.Emit(OpCodes.Ldelem_Ref);
                EmitCastToReference(il, paramTypes[i]);
                il.Emit(OpCodes.Stloc, locals[i]);
            }
            if (!methodInfo.IsStatic)
            {
                il.Emit(OpCodes.Ldarg_0);
            }
            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                    il.Emit(OpCodes.Ldloca_S, locals[i]);
                else
                    il.Emit(OpCodes.Ldloc, locals[i]);
            }
            if (methodInfo.IsStatic)
                il.EmitCall(OpCodes.Call, methodInfo, null);
            else
                il.EmitCall(OpCodes.Callvirt, methodInfo, null);
            if (methodInfo.ReturnType == typeof(void))
                il.Emit(OpCodes.Ldnull);
            else
                EmitBoxIfNeeded(il, methodInfo.ReturnType);

            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                {
                    il.Emit(OpCodes.Ldarg_1);
                    EmitFastInt(il, i);
                    il.Emit(OpCodes.Ldloc, locals[i]);
                    if (locals[i].LocalType.IsValueType)
                        il.Emit(OpCodes.Box, locals[i].LocalType);
                    il.Emit(OpCodes.Stelem_Ref);
                }
            }

            il.Emit(OpCodes.Ret);
            FastInvokeHandler invoder = (FastInvokeHandler)
               dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
            return invoder;
        }

        private static void EmitCastToReference(ILGenerator il, 
                                                System.Type type)
        {
            if (type.IsValueType)
            {
                il.Emit(OpCodes.Unbox_Any, type);
            }
            else
            {
                il.Emit(OpCodes.Castclass, type);
            }
        }

        private static void EmitBoxIfNeeded(ILGenerator il, System.Type type)
        {
            if (type.IsValueType)
            {
                il.Emit(OpCodes.Box, type);
            }
        }

        private static void EmitFastInt(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    return;
            }

            if (value > -129 && value < 128)
            {
                il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
            }
            else
            {
                il.Emit(OpCodes.Ldc_I4, value);
            }
        }

        public object TypeConvert(object source, Type DestType)
        {
          
            object NewObject = System.Convert.ChangeType(source,DestType);
            
            return (NewObject);
        }

    }
}

History

I am currently writing a trading application that requires me to provide a recursive descent parser within my code, so that users can enter formulas for various purposes. I wanted to allow users of the application to enter functions, and I wanted to create an environment where other developers could easily add functions. I realized that the System.Reflection classes would be helpful, when I stumbled upon Luyan's brilliant code to create a fast method invoker. I decided to wrap his code, so it would be a little more usable. I wanted to send it to him directly, but I couldn't get his email address (he's in China) so I decided to post this code, and then send him a message from his project.

I tried to create code in which, upon instantiating the main form, the constructor code would "pick up" every member function of the form class that was not inherited - in other words, every function that I wrote. I also wanted to use the parameter type information that you can extract from the function/method's MethodInfo class, so that I could dynamically cast the user's input arguments into the function/method's respective parameter and return types - at runtime.

I was able to accomplish this functionality for all functions that have strings and numbers for their parameters and return type. However, I would have liked to be able to dynamically cast any kind of parameter or return type at runtime, but was unable to figure out a way to do this. I needed a function that accomplished what the Visual Basic statement CType(object, nativeType) accomplishes, but where the nativeType can be a System.Type object. I just could not quite figure this out.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here