15

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?

mjgpy3
  • 8,597
  • 5
  • 30
  • 51
  • 1
    I think it's just legacy. `void` in C# is not a type, so there's an (unnecessary) difference between method that do, and methods that don't return a value. – SWeko Oct 15 '15 at 14:03
  • 5
    They could unify, if C# had been designed with some sort of Unit type instead of `void`. Then an `Action` would simply be a `Func`. – Dennis_E Oct 15 '15 at 14:03
  • 2
    If you find yourself *constantly* passing `Func` as `Action`, why not use F#? Unlike C#, it's been designed from the ground up as a functional language. C# simply carries a lot of the baggage from its not-very-functional past. As evident by `void`, and functions taking more than a single argument, for example :P If you just want to simplify your code, just make yourself some `AsAction` extension method, and you'll be fine. – Luaan Oct 15 '15 at 14:07
  • IMHO, adding a Unit type to C# should not be a breaking change - you can use void (and Action) if you want to or you can use Unit (and Func) if you want that. – SWeko Oct 15 '15 at 14:08
  • 4
    What's wrong with `Task.Run(DoSomething);`? It's gonna return `Task` which is still a `Task`. Which is what you'll get when you call `Task.Run(Action)` ? – Sriram Sakthivel Oct 15 '15 at 14:12
  • @SWeko Small correction: `void` is indeed a type in c#. It is an alias for `System.Void` struct. But.. it is a special type with some weird restrictions. – Sriram Sakthivel Oct 15 '15 at 14:17
  • 2
    @SriramSakthivel - it doesn't look like a type and it doesn't act like a type, so according to duck typing (pun intended), it's not a type :D – SWeko Oct 15 '15 at 14:30
  • 2
    @SriramSakthivel: `System.Void` only exists for reflection. `void` in C# is definitely not a type as far as the grammar is concerned. Every place in the grammar that requires a type has a special case for `void` (unless `void` is not allowed, such as in type arguments for example). – Joey Oct 15 '15 at 14:41
  • 1
    In the C# Specification, the chapter "Classes", the section "Methods" (section 10.6 in my version) the following terminology is used: A _method-header_ contains a _return-type_, and a _return-type_ can be either a _type_ or `void`. So if we adapt this, `void` is a _return-type_ but not a _type_. – Jeppe Stig Nielsen Oct 15 '15 at 15:41

2 Answers2

6

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.

Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
  • I disagree T would have to be assignable to Void for this to work. What you are saying is that Func must be castable to Action. That is just not true. The other possible mechanism would be implicit conversion. – Aron Oct 15 '15 at 15:30
  • @Aron. If I changed the wording of "assignable" to "implicitly convertible" in my answer would that be correct? ([I think casting refers to the usage of the casting operator](https://msdn.microsoft.com/en-us/library/ms173105.aspx), which OP didn't want to use I believe, so I don't want to mention casting.) – Jesus is Lord Oct 15 '15 at 16:36
  • My point is that there are multiple mechanisms for assigning objects to variables that are different types. When you assign an int value to a double variable the compiler knows that you need to do more than an assignment. It a lot more IL is generated. I am saying the same could technically be done without changing the CLI. – Aron Oct 15 '15 at 16:55
  • Good answer. @Aron, to address your concern, there is another possibility for option (B), and that is have the implicit conversion automatically do the wrapping that you are now doing "manually". Visual Basic does this; C# does not. The C# team felt that it would be confusing if an implicit conversion between two reference types did not preserve reference identity, as most implicit conversions between reference types do. Since the "manual" code is not very burdensome on the developer, it was felt to be a reasonable alternative. – Eric Lippert Oct 16 '15 at 14:42
  • 1
    One might then reasonably ask why the VB and C# design teams -- which of course have people in common! -- differ on this point. It comes down to the design philosophy of each language, which is hard to summarize in a short comment. – Eric Lippert Oct 16 '15 at 14:49
1

Pulling from the CLI Standard:

II.4.6.1 Delegate signature compatibility

Delegates can only be verifiably bound to target methods where:

  1. the signatures of the target method is delegate-assignable-to the signature of the delegate;

...

A target method or delegate of type T is delegate-assignable-to a delegate of type D if and only if all of the following apply:

  1. The return type U of T and return type V of D, V is assignable-to U.
Chris Pitman
  • 12,990
  • 3
  • 41
  • 56