使用C#将方法传递为参数


694

我有几种方法都具有相同的签名(参数和返回值),但是不同的名称和方法的内部原理不同。我想将要运行的方法的名称传递给另一个方法,该方法将调用传入的方法。

public int Method1(string)
{
    ... do something
    return myInt;
}

public int Method2(string)
{
    ... do something different
    return myInt;
}

public bool RunTheMethod([Method Name passed in here] myMethodName)
{
    ... do stuff
    int i = myMethodName("My String");
    ... do more stuff
    return true;
}

public bool Test()
{
    return RunTheMethod(Method1);
}

这段代码不起作用,但这就是我想要做的。我不了解如何编写RunTheMethod代码,因为我需要定义参数。


12
为什么不传递委托而不是方法名称?
Mark Byers 2010年

Answers:


852

您可以将.net 3.5中的Func委托用作RunTheMethod方法中的参数。Func委托允许您指定一个方法,该方法采用许多特定类型的参数并返回特定类型的单个参数。这是一个应该起作用的示例:

public class Class1
{
    public int Method1(string input)
    {
        //... do something
        return 0;
    }

    public int Method2(string input)
    {
        //... do something different
        return 1;
    }

    public bool RunTheMethod(Func<string, int> myMethodName)
    {
        //... do stuff
        int i = myMethodName("My String");
        //... do more stuff
        return true;
    }

    public bool Test()
    {
        return RunTheMethod(Method1);
    }
}

51
如果该方法具有返回void且没有参数的签名,则Func调用将如何更改?我似乎无法使语法正常工作。
user31673'1

210
@unknown:在这种情况下,它将Action改为Func<string, int>
乔恩·斯基特

12
但是现在,如果您想将参数传递给方法呢?
约翰·克特吉克

40
@ user396483例如,Action<int,string>对应于采用2个参数(整数和字符串)并返回void的方法。
serdar 2014年

24
@NoelWidmer Using Func<double,string,int>对应于一个采用2个参数(doublestring)并返回的方法int。最后指定的类型是返回类型。您最多可以将此委托用于16个参数。如果您需要更多帮助,请将您的委托编写为public delegate TResult Func<in T1, in T2, (as many arguments as you want), in Tn, out TResult>(T1 arg1, T2 arg2, ..., Tn argn);。如果我误会了,请纠正我。
serdar 2014年

356

您需要使用一个委托。在这种情况下,您的所有方法都带有一个string参数并返回一个int-最简单地由Func<string, int>委托1表示。因此,只需进行如下更改即可使代码正确无误:

public bool RunTheMethod(Func<string, int> myMethodName)
{
    // ... do stuff
    int i = myMethodName("My String");
    // ... do more stuff
    return true;
}

诚然,代表们的权力比这大得多。例如,使用C#,您可以从lambda表达式创建委托,因此可以通过以下方式调用方法:

RunTheMethod(x => x.Length);

这将创建一个匿名函数,如下所示:

// The <> in the name make it "unspeakable" - you can't refer to this method directly
// in your own code.
private static int <>_HiddenMethod_<>(string x)
{
    return x.Length;
}

然后将该委托传递给该RunTheMethod方法。

您可以将委托用于事件订阅,异步执行,回调-各种各样的事情。值得一读,特别是如果您想使用LINQ。我有一个文章大多是关于委托和事件之间的差异,但你会发现它很有用呢。


1这只是基于Func<T, TResult>框架中的通用委托类型;您可以轻松声明自己的:

public delegate int MyDelegateType(string value)

然后将参数设为类型MyDelegateType


58
+1这真是一个令人惊叹的答案,只需两分钟即可响起来。
David Hall 2010年

3
虽然您可以使用委托来传递函数,但更传统的面向对象方法是使用策略模式。
Paolo 2010年

20
@Paolo:代表只是策略模式的一种非常方便的实现,其中所讨论的策略只需要一个方法。它不是像这会打击策略模式-但它是一个很多比实现使用接口模式更方便的挫折感。
乔恩·斯基特

5
“经典”委托(从.NET 1/2中获知)是否仍然有用,还是由于Func / Action而完全废弃了?另外,示例中是否没有缺少委托关键字public **delegate** int MyDelegateType(string value)
M4N 2010年

1
@马丁:Do!感谢您的修复。编辑。至于声明自己的委托人-给类型名称起一些含义可能很有用,但是自.NET 3.5开始,我很少创建自己的委托人类型。
乔恩·斯基特

112

从OP的示例:

 public static int Method1(string mystring)
 {
      return 1;
 }

 public static int Method2(string mystring)
 {
     return 2;
 }

您可以尝试动作代表!然后使用

 public bool RunTheMethod(Action myMethodName)
 {
      myMethodName();   // note: the return value got discarded
      return true;
 }

RunTheMethod(() => Method1("MyString1"));

要么

public static object InvokeMethod(Delegate method, params object[] args)
{
     return method.DynamicInvoke(args);
}

然后只需调用方法

Console.WriteLine(InvokeMethod(new Func<string,int>(Method1), "MyString1"));

Console.WriteLine(InvokeMethod(new Func<string, int>(Method2), "MyString2"));

4
谢谢,这使我到了我想去的地方,因为我想要一个更通用的“ RunTheMethod”方法,该方法可以使用多个参数。顺便说一句,您的第一个InvokeMethodlambda电话应该RunTheMethod改为
John

1
像约翰一样,这确实帮助我有了通用的移动方法。谢谢!
ean5533 '11

2
您过得很愉快;)与选择的答案IMO相比,它非常易于使用并且更加灵活。
Sidewinder94 2014年

有没有一种方法可以扩展RunTheMethod(()=> Method1(“ MyString1”)); 检索返回值?理想情况下是通用的吗?
周杰伦

如果要传递参数,请注意以下
几点

31
public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

用法:

var ReturnValue = Runner(() => GetUser(99));

6
非常用户化。通过这种方式,可以使用一个或多个参数。我猜,最新的答案是这样。
bafsar 2015年

我想为此实现添加一件事。如果要传递的方法的返回类型为void,则不能使用此解决方案。
伊曼特·沃尔科夫斯

@ImantsVolkovs我相信您可以修改它以使用Action而不是Func,并将签名更改为void。虽然不是100%肯定。
Shockwave

2
有什么方法可以将参数传递给被调用的函数吗?
吉米

16

为了共享尽可能完整的解决方案,我将以三种不同的操作方式结束,但是现在,我将从最基本的原则开始。


简介

在CLR(公共语言运行时)之上运行的所有语言(例如C#,F#和Visual Basic)都在VM下工作,该VM的运行代码要比C和C ++(直接编译为机器)的本地语言更高码)。因此,方法不是任何一种已编译的块,但它们只是CLR可以识别的结构化元素。因此,您不能考虑将方法作为参数传递,因为方法本身不产生任何值,因为它们不是表达式!相反,它们是在生成的CIL代码中定义的语句。因此,您将面对委托概念。


什么是代表?

委托代表方法的指针。正如我上面所说的,方法不是值,因此CLR语言中有一个特殊的类Delegate,它包装了所有方法。

看下面的例子:

static void MyMethod()
{
    Console.WriteLine("I was called by the Delegate special class!");
}

static void CallAnyMethod(Delegate yourMethod)
{
    yourMethod.DynamicInvoke(new object[] { /*Array of arguments to pass*/ });
}

static void Main()
{
    CallAnyMethod(MyMethod);
}

三种不同的方式,背后的概念相同:

  • 方法1如上例所示,直接
    使用Delegate特殊类。该解决方案的问题在于,当您动态传递参数时,将不检查您的代码,而不会将其限制为方法定义中的参数类型。

  • 方式2
    除了Delegate特殊类外,委托概念还扩展到自定义委托,这是由delegate关键字开头的方法的定义,其行为与常规方法相同。从而对它们进行了检查,因此您将获得完美无缺的安全代码。

    这是一个例子:

    delegate void PrintDelegate(string prompt);
    
    static void PrintSomewhere(PrintDelegate print, string prompt)
    {
        print(prompt);
    }
    
    static void PrintOnConsole(string prompt)
    {
        Console.WriteLine(prompt);
    }
    
    static void PrintOnScreen(string prompt)
    {
        MessageBox.Show(prompt);
    }
    
    static void Main()
    {
        PrintSomewhere(PrintOnConsole, "Press a key to get a message");
        Console.Read();
        PrintSomewhere(PrintOnScreen, "Hello world");
    }
  • 方法3
    或者,您可以使用.NET标准中已经定义的委托:

    • Action包装不void带参数的a。
    • Action<T1>void用一个参数包装一个。
    • Action<T1, T2>包装了void两个参数。
    • 等等...
    • Func<TR>包装具有TR返回类型且不带参数的函数。
    • Func<T1, TR>TR返回类型和一个参数包装一个函数。
    • Func<T1, T2, TR>TR返回类型和两个参数包装一个函数。
    • 等等...

(后一种解决方案是大多数人发布的解决方案。)


1
Func <T>的返回类型不应该是最后一个吗? Func<T1,T2,TR>
sanmcp

13

您应该使用一个Func<string, int>代表,该代表代表一个函数,该函数接受stringas作为参数并返回一个int

public bool RunTheMethod(Func<string, int> myMethod) {
    // do stuff
    myMethod.Invoke("My String");
    // do stuff
    return true;
}

然后使用它:

public bool Test() {
    return RunTheMethod(Method1);
}

3
这不会编译。该Test方法应该是return RunTheMethod(Method1);
PSWG


2

虽然接受的答案是绝对正确的,但我想提供一种其他方法。

在我自己寻找类似问题的解决方案之后,我在这里结束了。我正在构建一个插件驱动的框架,并且作为框架的一部分,我希望人们能够将应用程序菜单的菜单项添加到通用列表,而无需暴露实际Menu对象,因为该框架可能会部署在没有MenuUI的其他平台上对象。添加有关菜单的一般信息非常容易,但是事实证明,允许插件开发人员有足够的自由来创建单击菜单时的回调。直到我意识到我一直在尝试重新发明方向盘和普通菜单调用并触发事件回调!

因此,解决方案(听起来很简单)一到您就意识到了。

只需为每个当前方法创建单独的类(如果需要,可以从基类继承),然后向每个方法添加事件处理程序。


1

这是一个示例,可以帮助您更好地理解如何将函数作为参数传递。

假设您具有“ 父”页面,并且想要打开一个子弹出窗口。在父页面中,应基于子弹出文本框填充一个文本框。

在这里您需要创建一个委托。

Parent.cs //代表声明公共代表void FillName(String FirstName);

现在创建一个将填充您的文本框的函数,并且该函数应映射委托

//parameters
public void Getname(String ThisName)
{
     txtname.Text=ThisName;
}

现在单击按钮,您需要打开一个子弹出窗口。

  private void button1_Click(object sender, RoutedEventArgs e)
  {
        ChildPopUp p = new ChildPopUp (Getname) //pass function name in its constructor

         p.Show();

    }

在ChildPopUp构造函数中,您需要创建父级//页面的“代理类型”参数

ChildPopUp.cs

    public  Parent.FillName obj;
    public PopUp(Parent.FillName objTMP)//parameter as deligate type
    {
        obj = objTMP;
        InitializeComponent();
    }



   private void OKButton_Click(object sender, RoutedEventArgs e)
    {


        obj(txtFirstName.Text); 
        // Getname() function will call automatically here
        this.DialogResult = true;
    }

编辑但此答案的质量仍然可以提高。
SteakOverflow

1

如果要将方法作为参数传递,请使用:

using System;

public void Method1()
{
    CallingMethod(CalledMethod);
}

public void CallingMethod(Action method)
{
    method();   // This will call the method that has been passed as parameter
}

public void CalledMethod()
{
    Console.WriteLine("This method is called by passing parameter");
}

0

这是一个不带参数的示例:http : //en.csharp-online.net/CSharp_FAQ_How_call_a_method_using_a_name_string

带有参数: http //www.daniweb.com/forums/thread98148.html#

您基本上会传入对象数组以及方法名称。然后,您可以同时使用Invoke方法。

params Object []参数


请注意,方法的名称不在字符串中-实际上是作为方法组。代表们是最好的答案,而不是反思。
乔恩·斯基特

@Lette:在方法调用中,用作参数的表达式是方法组;它是在编译时已知的方法的名称,编译器可以将其转换为委托。这与仅在执行时知道名称的情况有很大不同。
乔恩·斯基特

0
class PersonDB
{
  string[] list = { "John", "Sam", "Dave" };
  public void Process(ProcessPersonDelegate f)
  {
    foreach(string s in list) f(s);
  }
}

第二类是客户端,它将使用存储类。它具有Main方法,该方法创建PersonDB的实例,并使用Client类中定义的方法调用该对象的Process方法。

class Client
{
  static void Main()
  {
    PersonDB p = new PersonDB();
    p.Process(PrintName);
  }
  static void PrintName(string name)
  {
    System.Console.WriteLine(name);
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.