4

Consider this tiny program.

public class Program
{
    public static void Main()
    {
        // the first path compiles
        RunAction(() => { });

        // the second path does not compile
        RunDelegate(() => {});
    }

    private static void RunAction(Action run) => RunDelegate(run);
    private static void RunDelegate(Delegate run) { }
}

The first path compiles and implies that

  1. the () => {} lambda is an Action,
  2. the Action is a Delegate, and
  3. therefor that the () => {} lambda is a Delegate.

Why does the second path not compile?

Usually the compiler is able to make the leap between steps (1) and (3). Here is an example of how the compiler usually handles this kind of nested is-a relationships.

public class Program2
{
    public static void Main()
    {
        // both of these comple
        AcceptPerson(new Programmer());
        AcceptAnimal(new Programmer());
    }

    private static void AcceptPerson(Person p) => AcceptAnimal(p);
    private static void AcceptAnimal(Animal a) { }
}

public class Programmer : Person { }
public class Person : Animal { }
public class Animal { }
Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • 1
    Unfortunately, when you're dealing with `Delegate` as a type, the compiler has to infer an *actual* delegate type to use for your delegate, and there may be any number of possible delegate types to use for your actual code. The compiler simply says "no, can't do that" because that's what the language designers decided to do in this case. – Lasse V. Karlsen May 16 '18 at 21:49
  • 2
    Assumption (1.) is not correct. `() => {}` isn't an `Action` without a type declaration of `Action` *somewhere* in the code that allows the compiler to make the inference. – spender May 16 '18 at 21:53
  • @spender Right. I recall now that `Action run = () => {};` and `RunAction(() => {});` are both declaring the lambda as an `Action`. That's less intuitive in the second case. – Shaun Luttin May 16 '18 at 21:57
  • To be specific, the problem isn't that the compiler cannot determine that you're dealing with a `Delegate`, the problem is that the compiler doesn't understand which specific delegate you're dealing with, and they would all be instances that derive from `Delegate `. – Lasse V. Karlsen May 16 '18 at 22:21

1 Answers1

4

There is no "default" delegate type for an anonymous function such as () => { }. Such an expression is implicitly convertible to any concrete delegate type of the right signature and return type (subject to various natural variance rules).

You can have many delegate types that match:

namespace TestFramework
{
  public delegate void TestDelegate();
}

namespace System
{
  public delegate void Action();
}

namespace System.Threading
{
  public delegate void ThreadStart();
}

...

Among all such possible concrete delegate types (each of which as the abstract System.Delegate as its (indirect) base class) there is no preferred one.

There is an implicit conversion from the expression () => { } to each of them:

TestDelegate f = () => { };
Action g = () => { };
ThreadStart h = () => { };
...

Because of the implicit conversion, your call RunAction(() => { }); works.

But with RunDelegate(() => {}); there is no telling what concrete delegate type to use. You have to do one of:

RunDelegate((TestDelegate)(() => { }));
RunDelegate((Action)(() => { }));
RunDelegate((ThreadStart)(() => { }));
...

The comments to your question are correct.

Related to this is the fact that this will not compile:

var f = () => { };  // illegal, shows that '() => { }' does NOT have type System.Action

And you cannot use a sub-expression like myBool ? (() => { }) : null, and so on.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • It's an interesting decision that `var x = new {}` works for anonymous objects but `var y = () => {}` does not work for lambdas. It surprises me that the compiler does not figure out that the latter is an `Action`. – Shaun Luttin May 16 '18 at 22:28
  • A further asymmetry is that we can cast lambdas but cannot cast anonymous objects. – Shaun Luttin May 16 '18 at 22:30