Action/Func vs Methods, what's the point?
Asked Answered
E

5

44

I know how to use Action and Func in .NET, but every single time I start to, the exact same solution can be achieved with a regular old Method that I call instead.

This excludes when an Action or Func is used as an argument for something I don't control, like LINQ's .Where.

So basically my question is...why do these exist? What do they give me extra and new that a simple Method doesn't?

Erminna answered 3/10, 2011 at 2:13 Comment(5)
I assume by "function", you mean "method", right? Want to make sure you're not referring to something more esoteric.Argenteuil
@MichaelPetrotta : Yes I mean "method", I always use those words for the same thing even know I know they aren't.Erminna
You've already identified a very powerful reason for Func<> to exist: Linq. The fact that you can do other things with them is a very nice bonus.Hypochondria
Check out this article: blackwasp.co.uk/FuncAction.aspxPriddy
possible duplicate of func-delegate-vs-functionGlanders
G
44

I think other answers here talk about what an Action/Func is and its use. I will try to answer how to choose between Action/Func and method. The differences first:

1) From a raw performance point of view, delegates are slower compared to direct method calls, but it's so insignificant that worrying about it is a bad practice.

2) Methods can have overloads (same function names with different signatures) but not Action/Func delegates since they are declared as variables and by C# rules you cant have two variables with the same name in a given scope.

bool IsIt() { return 1 > 2; }
bool IsIt(int i) { return i > 2; } //legal

Func<bool> IsIt = () => 1 > 2; 
Func<int, bool> IsIt = i => i > 2; //illegal, duplicate variable naming

3) Consequently, Action/Func are reassignable and can point to any function, while methods once compiled remain to be the same forever. It is semantically wrong to use Func/Action if the method it points to never changes during run time.

bool IsIt() { return 1 > 2; } //always returns false

Func<bool> IsIt = () => 1 > 2; 
IsIt = () => 2 > 1; //output of IsIt depends on the function it points to.

4) You can specify ref/out parameters for normal methods. For eg, you can have

bool IsIt(out string p1, ref int p2) { return 1 > 2; } //legal

Func<out string, ref int, bool> IsIt; //illegal

5) You cannot introduce new generic type parameter for Action/Func (they are generic already btw, but the type arguments can only be a known type or types specified in the parent method or class), unlike methods.

bool IsIt<A, R>() { return 1 > 2; } //legal

Func<bool> IsIt<A, R> = () => 1 > 2; //illegal

6) Methods can have optional parameters, not Action/Func.

bool IsIt(string p1 = "xyz") { return 1 > 2; } //legal

Func<string, bool> IsIt = (p1 = "xyz") => 1 > 2; //illegal

7) You can have params keyword for parameters of a method, not so with Action/Func.

bool IsIt(params string[] p1) { return 1 > 2; } //legal

Func<params string[], bool> IsIt = p1 => 1 > 2; //illegal

8) Intellisense plays well with parameter names of methods (and accordingly you have cool XML documentation available for methods), not so with Action/Func. So as far as readability is concerned, regular methods win.

9) Action/Func have a parameter limit of 16 (not that you can't define your own ones with more) but methods support more than you will ever need.

As to when to use which, I would consider the following:

  1. When you are forced to use one based on any of the above points, then you anyway have no other choice. Point 3 is the most compelling I find upon which you will have to base your decision.

  2. In most normal cases, a regular method is the way to go. It's the standard way of refactoring a set of common functionality in C# and VB.NET world.

  3. As a rule of thumb, if the function is more than a line, I prefer a method.

  4. If the function has no relevance outside a specific method and the function is too trivial, like a simple selector (Func<S, T>) or a predicate (Func<bool>) I would prefer Action/Func. For eg,

    public static string GetTimeStamp() 
    {
        Func<DateTime, string> f = dt => humanReadable 
                                       ? dt.ToShortTimeString() 
                                       : dt.ToLongTimeString();
        return f(DateTime.Now);
    }
    
  5. There could be situations where Action/Func makes more sense. For instance if you have to build a heavy expression and compile a delegate, its worth doing it only once and caching the compiled delegate.

    public static class Cache<T> 
    { 
        public static readonly Func<T> Get = GetImpl();
    
        static Func<T> GetImpl()
        {
            //some expensive operation here, and return a compiled delegate
        }
    }
    

    instead of

    public static class Cache<T> 
    {
        public static T Get()
        {
            //build expression, compile delegate and invoke the delegate
        }
    }
    

    In the first case when you call Get, GetImpl is executed only once, where as in the second case, (expensive) Get will be called every time.


Not to forget anonymous method itself will have certain limits unrelated to Func/Action, making the use little different. Also see this for a related question.

Glanders answered 29/6, 2013 at 13:12 Comment(3)
Whats about performance ? Action/Func are implemented as delegates. Delegates are implemented in IL as compiler-generated classes with an Invoke() method. Calling foo() when foo is a delegate actually compiles down to calling foo.Invoke(), which in turn calls the destination code. If foo is an actual method instead of a delegate, calling foo() calls directly to the destination code with no Invoke() intermediate. See ILDASM for proof. https://mcmap.net/q/197163/-why-is-action-func-better-than-a-regular-method-in-netCameral
@Cameral that's true, but it's so insignificant. I wouldn't worry about it, ever. I mentioned the negligible difference still in the first point.Glanders
@Cameral I'm not getting your point. The answer in your link shows methods are faster to invoke than a delegate handle. I concur in the answer. Am I missing something?Glanders
S
24

Action and Func are framework-provided Delegate types. Delegates allow functions to be treated like variables, meaning that you can (among other things) pass them from method to method. If you have ever programmed in C++, you can think of Delegates as function pointers that are restricted by the signature of the method they refer to.

Action and Func specifically are generic delegates (meaning they take type parameters) with some of the most common signatures- almost any method in most programs can be represented using one or the other of those two, saving people a lot of time manually defining delegates like we did in .net prior to version 2. In fact, when I see code like this in a project, I can usually safely assume that the project was migrated from .net 1.1:

// This defines a delegate (a type that represents a function)
// but usages could easily be replaced with System.Action<String>
delegate void SomeApplicationSpecificName(String someArgument);

I'd recommend that you look into delegates some more. They are a hugely powerful feature of the C# language.

Scholarship answered 3/10, 2011 at 2:28 Comment(1)
I know Delegates too (though have worked with them little). Your statement that they can be passed as arguments between methods really hits home as that is something I find incredibly helpful in Javascript and never put 2 and 2 together with Delegates.Erminna
I
8

I use them to create an array of functions. For instance, I may have a ComboBox full of actions that could be taken. I populate the ComboBox with items of a class or structure:

public class ComboBoxAction
{
    private string text;
    private Action method;

    public ComboBoxAction(string text, Action method)
    {
        this.text = text;
        this.method = method;
    }

    public override string ToString()
    {
        return this.text;
    }

    public void Go()
    {
        this.method();
    }
}

Then when someone selects an item, I can call the action.

CType(ComboBox1.SelectedItem, ComboBoxAction).Go()

This is far easier than having a Select statement determine which method to call based on the ComboBox's text.

Imponderabilia answered 3/10, 2011 at 2:33 Comment(0)
U
3

There's plenty of cases where a Func can help where a Method wouldn't.

public void DoThing(MyClass foo, Func<MyClass, string> func)
{
    foo.DoSomething;
    var result = func(foo);
    foo.DoStringThing(result);
}

So you can specify a different Func whenever you call this method - the DoThing method doesn't need to know what's being done, just that whatever it is will return a string.

You can do this without using the Func keyword by using the delegate keyword instead; it works much the same way.

Urfa answered 3/10, 2011 at 2:33 Comment(0)
C
3

One great use of action and func are when we need to perform some operation (before or after a method), irrespective of what the method is. For example, we need to retry the method 10 times if exception occurs.

Consider the following method – its return type is generic. So it can be applied on func with any return type.

public static T ExecuteMultipleAttempts<T>(Func<T> inputMethod, Action additionalTask, int wait, int numOfTimes)
        {
            var funcResult = default(T);
            int counter = 0;
            while (counter < numOfTimes)
            {
                try
                {
                    counter++;
                    funcResult = inputMethod();

                    //If no exception so far, the next line will break the loop.
                    break;
                }
                catch (Exception ex)
                {
                    if (counter >= numOfTimes)
                    {
                        //If already exceeded the number of attemps, throw exception
                        throw;
                    }
                    else
                    {
                        Thread.Sleep(wait);
                    }

                    if (additionalTask != null)
                    {
                        additionalTask();
                    }
                }
            }

            return funcResult;
        }
Ced answered 30/12, 2016 at 18:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.