Can "ProcessMethod(MethodA, 1)" also be invoked like this "ProcessMethod(MethodA(1))"?

StackOverflow https://stackoverflow.com/questions/23592814

  •  20-07-2023
  •  | 
  •  

Domanda

Description

The below code example works. There is a method invokation which looks like this:

ProcessMethod(MethodA, 1); (Option 1)

Question

a) Is there a way to also call it like this?

ProcessMethod(MethodA(1)); (Option 2)

In other words, is there a way to call a method which takes a delegate of type Action<T> by passing the delegate including the argument as shown in option 2 instead of passing the argument separately as shown in option 1?

b) If so, what will the code look like?

c) What are the pros and cons of each option (provided option 2 exists)?

Example

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);
        }
    }
}
È stato utile?

Soluzione

The term for what you are trying to do is a closure -- when you have a single object that contains all of the information needed to call a method, including the call site and the parameters.

To make closures in C#, you need anonymous delegates, which these days means lambda expressions. Given a named method like MethodA, you cannot pass an "invocation" of that method, with parameter, directly into another method like this:

public void Run(Action<int> method) { }
public void MethodA(int x ) { }

// Does *not* work
Run(MethodA(5));

Because MethodA(5) is no longer a method, it's an expression whose type is void, and that's wrong. However, you can wrap up this method call into a closure that is of type Action<int>:

Run(x => MethodA(5));

This gives you exactly what you asked for: you passed in an Action<int> to the Run method and trapped the parameter at the call site. However, this is probably not what you actually want: if you pass an Action<int> into the Run method, that method is going to assume it needs to pass a parameter into the method call itself, but your lambda is going to ignore it. Instead, you can do as @SLaks suggests, and remove the parameter completely:

public void Run(Action method) { }
public void MethodA(int x ) { }

Run(() => MethodA(5));

In this case, there is no parameter going in to the lambda expression, so the resulting delegate will be of type Action; the fact that you ultimately called a method that took a parameter is hidden inside the closure.

This is a fairly popular technique, so obviously there are some benefits:

  • The do exactly what you ask: allow us to capture parameters or values that will be out of scope by the time the delegate is actually invoked. Note that you can use variables in your closure, e.g. var x = 6; Run(() => MethodA(x)), and the variable x will remain available to your closure even after it goes out of scope.
  • They are more flexible than passing just a named method, because your lambda can be complex, e.g.: Run(() => { MethodA(5); MethodB(5) });
  • The 'functional' style of programming that is encourage by lambdas is very popular these days, as shown by the number of functional elements C# has gained over the past few versions.

However, there is a tradeoff, mostly in terms of how much control you have over what the lambda does. For example, in your case you have a Run method that initially took an Action<int> and an int and called one with the other. That probably means that your Run method assumes that, at some point, the method you pass into it will do something with some integer value (look it up in a database, compute some result with it, etc.).

If you instead switch over to a closure, you now allow the caller to do literally anything they want in the passed-in delegate, e.g.:

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

That may be perfectly fine in your case; you may not really care what goes on inside the delegate. But if your logic assumes something particular about the method that is being passed in, using anonymous delegates can very easily circumvent those expectations.

Altri suggerimenti

You can pass a lambda expression that captures the argument in a closure:

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

private void ProcessMethod(Action method)
{
    method();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top