Create a method call in .NET based on a string val

2020-02-10 23:56发布

问题:

Right now, I have code that looks something like this:

Private Sub ShowReport(ByVal reportName As String)
    Select Case reportName
        Case "Security"
            Me.ShowSecurityReport()
        Case "Configuration"
            Me.ShowConfigurationReport()
        Case "RoleUsers"
            Me.ShowRoleUsersReport()
        Case Else
            pnlMessage.Visible = True
            litMessage.Text = "The report name """ + reportName + """ is invalid."
    End Select
End Sub

Is there any way to create code that would use my method naming conventions to simplify things? Here's some pseudocode that describes what I'm looking for:

Private Sub ShowReport(ByVal reportName As String)
    Try
        Call("Show" + reportName + "Report")
    Catch ex As Exception
        'method not found
    End Try
End Sub

回答1:

Type type = GetType();
MethodInfo method = type.GetMethod("Show"+reportName+"Report");
if (method != null)
{
    method.Invoke(this, null);
}

This is C#, should be easy enough to turn it into VB. If you need to pass parameter into the method, they can be added in the 2nd argument to Invoke.



回答2:

You've got a deeper problem. Your strings are too important. Who is passing you strings? can you make them not do that?

Stick with the switch statement, as it decouples your internal implementation (method names) from your external view.

Suppose you localize this to German. You gonna rename all those methods?



回答3:

Reflection API allows you to get a MethodInfo from a method, then calling Invoke dynamically on it. But it is overkill in your case.

You should consider having a dictionary of delegates indexed by strings.



回答4:

You could use reflection to do this but to be honest I think it's overcomplicating things for your particular scenario i.e. code and switch() in the same class.

Now, if you had designed the app to have each report type in its own assembly (kinda like an add-in/plugin architecture) or bundled in a single external assembly then you could load the reporting assemblie(s) into an appdomain and then use reflection to do this kinda thing.



回答5:

Use reflection. In the System.Reflection namespace you need to get a MethodInfo object for the method you want, using GetMethod("methodName") on the type containing the method.

Once you have the MethodInfo object, you can call .Invoke() with the object instance and any parameters.

For Example:

System.Reflection.MethodInfo method = this.GetType().GetMethod("foo");
method.Invoke(this, null);


回答6:

You can use reflection. Though personally, I think you should just stick with the switch statement.

private void ShowReport(string methodName)
{
    Type type = this.GetType();
    MethodInfo method = type.GetMethod("Show"+methodName+"Report", BindingFlags.Public)
    method.Invoke(this, null);
}

Sorry, I'm doing C#. Just translate it to VB.NET.



回答7:

Python (and IronPython) can do this thing very easily. With .Net though, you need to use reflection.

In C#: http://www.dotnetspider.com/resources/4634-Invoke-me-ods-dynamically-using-reflection.aspx

My quick port to VB.Net:

Private Sub InvokeMethod(instance as object, methodName as string )
            'Getting the method information using the method info class
            Dim mi as MethodInfo = instance.GetType().GetMethod(methodName)

            'invoing the method
            'null- no parameter for the function [or] we can pass the array of parameters
            mi.Invoke(instance, Nothing)
End Sub


回答8:

If i understand the question correctly, you'll have to use Reflection to find the method "show" + reportName and then invoke it indirectly:

Half-baked example:

Case "financial" :
{
   Assembly asm = Assembly.GetExecutingAssembly ();

    MethodInfo mi = asm.GetType ("thisClassType").GetMethod ("showFinancialReport");

   if (mi != null)
      mi.Invoke (null, new object[] {});

}

Insert your own logic there to make up the name for the method to call.

See MSDN documentation of MethodInfo and Assembly for details.



回答9:

Using reflection:

Type t = this.GetType();
try 
{
    MethodInfo mi = t.GetMethod(methodName, ...);

    if (mi != null)
    {
        mi.Invoke(this, parameters);
    }
} 

But I agree with ago, better not change your original code ;-)



回答10:

The "Closest to your question" solution.

You could make delegates out of those reports, and call them by looking up the matching String in a Hashtable:

Public Sub New()
    '...
    ReportTable.Add("Security", New ReportDelegate(AddressOf ShowSecurityReport))
    ReportTable.Add("Config", New ReportDelegate(AddressOf ShowConfigReport))
    ReportTable.Add("RoleUsers", New ReportDelegate(AddressOf ShowRoleUsersReport))
    '...
End Sub

Private Sub ShowSecurityReport()
    '...
End Sub

Private Sub ShowConfigReport()
    '...
End Sub

Private Sub ShowRoleUsersReport()
    '...
End Sub

Private Delegate Sub ReportDelegate()

Private ReportTable As New Dictionary(Of String, ReportDelegate)

Private Sub ShowReport(ByVal reportName As String)
    Dim ReportToRun As ReportDelegate
    If ReportTable.TryGetValue(reportName, ReportToRun) Then
        ReportToRun()
    Else
        pnlMessage.Visible = True
        litMessage.Text = "The report name """ + reportName + """ is invalid."
    End If
End Sub

That way you can add as many reports as you like, and your ability to reflect them, and the perf hit of reflection, aren't an issue.



回答11:

You can, using System.Reflection. See this code project article for more information.

string ModuleName = "TestAssembly.dll";
string TypeName = "TestClass";
string MethodName = "TestMethod";

Assembly myAssembly = Assembly.LoadFrom(ModuleName);

BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public | 
  BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);

Module [] myModules = myAssembly.GetModules();
foreach (Module Mo in myModules) 
{
 if (Mo.Name == ModuleName) 
     {
     Type[] myTypes = Mo.GetTypes();
     foreach (Type Ty in myTypes)
         {
        if (Ty.Name == TypeName) 
            {
            MethodInfo[] myMethodInfo = Ty.GetMethods(flags);
            foreach(MethodInfo Mi in myMethodInfo)
                {
                if (Mi.Name == MethodName) 
                    {
                    Object obj = Activator.CreateInstance(Ty);
                    Object response = Mi.Invoke(obj, null);
                    }
                }
            }
        }
    }
}


回答12:

Worked for me in VB .Net MSVS 2015

Dim tip As Type = GetType(MODULENAME)'if sub() or function() in module
        Dim method As MethodInfo = tip.GetMethod("MaxV") 'name of function (gets 2 params double type)
        Dim res As Double = 0 'temporary variable
        If (Not Nothing = method) Then 'if found function "MaxV"

            res = method.Invoke(Me, New Object() {10, 20})
        End If
        MsgBox(res.ToString())