描述
下面的代码示例有效。有一种方法调用如下:
ProcessMethod(MethodA, 1);
(选项1(
问题
a( 有没有办法也这样称呼它?
ProcessMethod(MethodA(1));
(选项2(
换句话说,是否有一种方法可以调用一个接受Action<T>
类型委托的方法,方法是如选项2所示传递包含参数的委托,而不是如选项1所示单独传递参数?
b( 如果是,代码会是什么样子?
c( 每个选项的优缺点是什么(如果存在选项2(?
示例
using System;
namespace DelegateDemo
{
class Program
{
static void Main(string[] args)
{
new Demo().Run();
}
}
public class Demo
{
public void Run()
{
ProcessMethod(MethodA, 1); // Can this be invoked like this ProcessMethod(MethodA(1))?
}
private void ProcessMethod(Action<int> method, int i)
{
method(i);
}
private void MethodA(int i)
{
Console.WriteLine("MethodA: {0}", i);
}
}
}
您试图做的事情的术语是闭包——当您有一个对象,它包含调用方法所需的所有信息,包括调用站点和参数时。
要在C#中创建闭包,您需要匿名委托,这在今天意味着lambda表达式。给定一个名为的方法(如MethodA
(,您不能将该方法的"调用"(带参数(直接传递到另一个方法(如(中
public void Run(Action<int> method) { }
public void MethodA(int x ) { }
// Does *not* work
Run(MethodA(5));
因为MethodA(5)
不再是一个方法,它是一个类型为void
的表达式,这是错误的。但是,您可以将此方法调用封装为为类型Action<int>
:的闭包
Run(x => MethodA(5));
这正是您所要求的:您将Action<int>
传递给Run
方法,并在调用站点捕获参数。然而,这可能不是您真正想要的:如果您将Action<int>
传递到Run
方法中,该方法将假设它需要将参数传递到方法调用本身中,但您的lambda将忽略它。相反,您可以按照@SLaks的建议进行操作,并完全删除该参数:
public void Run(Action method) { }
public void MethodA(int x ) { }
Run(() => MethodA(5));
在这种情况下,lambda表达式中没有参数,因此生成的委托类型为Action
;您最终调用了一个带有参数的方法,这一事实隐藏在闭包中。
这是一种相当流行的技术,因此显然有一些好处:
- 按照您的要求执行:允许我们捕获在实际调用委托时超出范围的参数或值。请注意,您可以在闭包中使用变量,例如
var x = 6; Run(() => MethodA(x))
,并且即使在变量x
超出范围后,它仍将可用于闭包 - 它们比只传递命名方法更灵活,因为lambda可能很复杂,例如:
Run(() => { MethodA(5); MethodB(5) });
- lambdas鼓励的"函数式"编程风格如今非常流行,正如C#在过去几个版本中获得的函数元素数量所示
然而,这是一个权衡,主要是在你对lambda的功能有多少控制方面。例如,在您的案例中,您有一个Run
方法,该方法最初采用Action<int>
和int
,并用另一个调用其中一个。这可能意味着你的Run
方法假设,在某个时刻,你传递给它的方法会用一些整数值做一些事情(在数据库中查找它,用它计算一些结果,等等(
如果您转而切换到闭包,那么您现在允许调用者在传入的委托中执行他们想要的任何操作,例如:
Run(() => Console.WriteLine("hahaha! No int here!"));
在你的情况下,这可能很好;您可能并不真正关心代理内部发生了什么。但是,如果您的逻辑假设传入的方法有一些特殊之处,那么使用匿名委托可以很容易地规避这些期望。
您可以传递一个lambda表达式,该表达式捕获闭包中的参数:
public void Run()
{
ProcessMethod(() => MethodA(1));
}
private void ProcessMethod(Action method)
{
method();
}