"ProcessMethod(MethodA, 1)"也可以像这样调用"ProcessMethod(MethodA(1))"吗?



描述

下面的代码示例有效。有一种方法调用如下:

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();
}

最新更新