可以像“ProcessMethod(MethodA(1))”一样调用“ProcessMethod(MethodA,1)”吗?

时间:2014-05-11 12:48:17

标签: c# delegates

描述

以下代码示例有效。有一个方法调用,如下所示:

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

2 个答案:

答案 0 :(得分:2)

当您拥有一个包含调用方法所需的所有信息的单个对象(包括调用站点和参数)时,您尝试执行的操作的术语是闭包

要在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方法假设,在某些时候,你传递给它的方法将使用某个整数值(在数据库中查找,用它计算一些结果等)。< / p>

如果您切换到闭包,现在允许调用者在传入的委托中按字面意义执行任何操作,例如:

Run(() => Console.WriteLine("hahaha! No int here!"));

在你的情况下,这可能完全没问题;你可能不太关心代表内部发生了什么。但是,如果你的逻辑假设传递的方法有特殊之处,那么使用匿名代理很容易绕过这些期望。

答案 1 :(得分:1)

您可以传递一个lambda表达式来捕获闭包中的参数:

public void Run()
{
    ProcessMethod(() => MethodA(1));
}

private void ProcessMethod(Action method)
{
    method();
}