用于在运行时动态创建函数委托的类






4.20/5 (7投票s)
2006年10月6日
3分钟阅读

83992

1631
一个在运行时动态创建函数委托的类。 封装了 Luyan 的代码,来自他的文章:通用快速方法调用器。
引言
本文介绍了一个 .NET (2.0) 类,它使用 Luyan 的项目“通用快速方法调用器”来动态创建委托并在运行时调用它们。
背景
Luyan 的 C# 项目,名为 通用快速方法调用器,对于动态创建委托和调用它们非常有用。 当我希望我的用户通过简单地点击函数列表并填写适当的函数参数来访问 Form
类中的有用函数时,我需要此功能。
使用 System.Reflection
,您可以获得任何(或所有)您编写的函数的 MethodInfo
对象。 MethodInfo
对象允许您在运行时创建一个委托,然后您可以调用该委托来执行用户选择的函数。
使用代码
代码位于一个 zip 文件中,但分为两个独立的项目。
- 将整个文件解压缩到包含您的 Visual Studio 2005 的同一文件夹中。 对我来说,它是我的MyDocuments文件夹。
- 从 Visual Studio 2005 中,打开项目 FastInvoker(它应该在文件夹Visual Studio 2005\Projects\FastInvoker中)。 构建项目。 这是一个 C# 类库。
- 运行 Visual Studio 2005 的另一个实例,并打开项目 SimpleTestForm,我愚蠢地将其放在文件夹Visual Studio 2005\Projects\FastInvokeTestForm中。
- 您可能会看到对
FastInvoker
的损坏引用。 我相信在您第一次构建 SimpleTestForm 项目后,这个损坏的引用就会消失。(不要问我为什么)。 - 查看每个项目的代码。
名为“SimpleTestForm”的 Visual Basic .NET 项目在进行一些其他有用的技巧后实例化一个 FastInvoker
对象,这些技巧可能会让你感到有趣。
名为 FastInvoker 的 C# 项目创建了 FastInvoke
类,该类创建动态委托,存储动态委托并提供公共方法来执行动态委托。 我从 Luyan 那里复制了大部分代码。 他应该为这个项目中的主要功能获得所有的真正功劳。 我只是将 Luyan 的代码复制到一个新类中,并创建了一个有用的构造函数,该构造函数保存他创建的动态委托,并保存有关被调用函数的其他有用信息。
以下 Visual Basic .NET (2.0) 代码用于测试 FastInvoke
对象的 SimpleTestForm 窗体。(C# FastInvoke
类代码在下面列出。)
' 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
FastInvoke
类的代码如下所示
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);
}
}
}
历史
我目前正在编写一个交易应用程序,该应用程序要求我在我的代码中提供一个递归下降解析器,以便用户可以输入各种用途的公式。 我想允许应用程序的用户输入函数,并且我想创建一个环境,让其他开发人员可以轻松地添加函数。 我意识到 System.Reflection
类会有所帮助,这时我偶然发现了 Luyan 的出色代码,用于创建一个快速的方法调用器。 我决定包装他的代码,使其更易于使用。 我想直接发送给他,但我无法获得他的电子邮件地址(他在中国),所以我决定发布此代码,然后从他的项目给他发送消息。
我试图创建代码,其中在实例化主窗体时,构造函数代码会“拾取”窗体类的每个成员函数,这些函数不是继承的 - 换句话说,我编写的每个函数。 我还想使用您可以从函数/方法的 MethodInfo
类中提取的参数类型信息,以便我可以在运行时将用户的输入参数动态转换为函数/方法各自的参数和返回类型。
我能够为所有参数和返回类型为字符串和数字的函数实现此功能。 但是,我希望能够在运行时动态转换任何类型的参数或返回类型,但无法找到一种方法来做到这一点。 我需要一个函数来完成 Visual Basic 语句 CType(object, nativeType)
所完成的操作,但其中 nativeType
可以是 System.Type
对象。 我只是无法完全弄清楚这一点。