Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't Func<...> and Action unify?

I find myself constantly wanting to pass a Func with a return and no inputs in place of an Action, for example

Func<int> DoSomething = ...;

Task.Run(DoSomething);

where, I don't really care about the return value of DoSomething.

These types don't unify, however, and I end up wrapping the call

Task.Run(() => { DoSomething(); });

Is there a way to make these types unify without wrapping? Also, are there good design reasons why they don't unify?

like image 292
mjgpy3 Avatar asked Oct 15 '15 13:10

mjgpy3


People also ask

Is there any difference between action and function?

They are basically the same thing, there are no performance advantages when using one or another. The main difference between them is that a function can be used inside expressions directly on the screen, while an action can't.

What are Func and Action Why do we use?

Func is a delegate that points to a method that accepts one or more arguments and returns a value. Action is a delegate that points to a method which in turn accepts one or more arguments but returns no value. In other words, you should use Action when your delegate points to a method that returns void.

Could you explain the difference between func vs Action vs Predicate?

Func is a delegate (pointer) to a method, that takes zero, one or more input parameters, and returns a value (or reference). Predicate is a special kind of Func often used for comparisons (takes a generic parameter and returns bool).

Is there any difference between action and function C#?

Actions can only take input parameters, while Funcs can take input and output parameters. It is the biggest difference between them. An action can not return a value, while a func should return a value in the other words.


1 Answers

You want the following statement to be true:

If I have a Func<T>, I should be able to use it where an Action is required.

That would require that Func<T> is (A) assignable to Action or (B) implicitly convertible to Action.

If we assume (A) that would require T, which can be any type, assignable to void.

Eric Lippert answers this question in his blog:

Shouldn’t “void” be considered a supertype of all possible types for the purposes of covariant return type conversions from method groups to delegate types?

His answer is "No," because that is ultimately incompatible with the CLI spec. The CLI spec requires that return values go on the stack, so void functions don't end up generating a "pop" instruction while those that do return something, do generate a "pop" instruction. If there was some way to have an "action" which could contain a void function or a function that returned something, which wasn't known at compile-time, the compiler wouldn't know whether or not to generate the "pop" instruction.

He goes on to say this:

Had the CLI specification said “the returned value of any function is passed back in a ‘virtual register’” rather than having it pushed onto the stack, then we could have made void-returning delegates compatible with functions that returned anything. You can always just ignore the value in the register. But that’s not what the CLI specified, so that’s not what we can do.

In other words, if there was this "virtual register" where return values of functions were stored (that presumably doesn't exist in the CLI spec), the writers of C# and its compiler could have done what you want, but they cannot since they couldn't diverge from the CLI spec.

If we assume (B), there would be a breaking change, as Eric Lippert explains in this blog. Adapting the example from his blog to this, if there was an implicit conversion from Func<T> to Action, some programs wouldn't compile anymore that used to (breaking change). This program currently compiles, but try un-commenting the implicit conversion operator, akin to what you'd be asking for, it doesn't compile.

public class FutureAction
{
    public FutureAction(FutureAction action)
    {
    }

    //public static implicit operator FutureAction(Func<int> f)
    //{
    //    return new FutureAction(null);
    //}

    public static void OverloadedMethod(Func<FutureAction, FutureAction> a)
    {
    }

    public static void OverloadedMethod(Func<Func<int>, FutureAction> a)
    {
    }

    public static void UserCode()
    {
        OverloadedMethod(a => new FutureAction(a));
    }
}

(This isn't exactly what they'd be doing, obviously, since this only works for Func<int> and not Func<T>, but it illustrates the problem.)

Summary

I think the problem you're facing is an artifact of the CLI spec that probably wasn't forseen at the time and I'm guessing they don't want to introduce breaking changes to allow for implicit conversion for it to just work.

like image 57
Jesus is Lord Avatar answered Oct 22 '22 01:10

Jesus is Lord