2

Control.BeginInvoke:

enter image description here

enter image description here

In both cases, it seems clear that the compiler has all the information it needs to infer the delegate type. Yet in neither case does the type inference seem to work:

 BeginInvoke(myMethodThatTakesNoParams);

produces the compiler error

Error 105 The best overloaded method match for 'System.Windows.Forms.Control.BeginInvoke(System.Delegate)' has some invalid arguments

as does

BeginInvoke(ShowProcessErrors, new object[] { process });

Both method calls only compile if I change them to explitly create a delegate and pass that. Both of the following compile fine:

BeginInvoke(new MethodInvoker(myMethodThatTakesNoParams));

and

BeginInvoke(new ProcessErrorDelegate(ShowProcessErrors), new object[] { process });

There doesn't seem to be any obvious reason why type inference won't work here. Is there a way to call BeginInvoke without explicitly creating a delegate?

Reg Edit
  • 6,719
  • 1
  • 35
  • 46

2 Answers2

7

The issue is that myMethodThatTakesNoParams isn't really a delegate but a so-called "method group" by the compiler. A method group isn't a real type in the CLR. It must be converted to delegate type to be used. When you use a method group like this:

Action a = myMethodThatTakesNoParams;

The compiler recognizes that you want to convert the method group to a delegate and inserts the conversion for you. It produces IL that effectively says:

Action a = new Action(myMethodThatTakesNoParams);

When you say:

Delegate d = myMethodThatTakesNoParams

The compiler doesn't really know what to do. It could theoretically pick any compatible delegate type for you, but C#, in general, does not insert types you did not use into expressions. Since it does not know what delegate you want the method group converted to, the compiler produces an error.

I used variable assignment in my examples, but the same logic applies for parameters to methods.

A work around would be to write your own extension method that has a specific delegate type in it:

static class ControlExtensions
{
    public static IAsyncResult BeginInvoke(this Control c, Action a)
    {
        return c.BeginInvoke(a);
    }
}
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
  • But since I could [use an anonymous method](http://stackoverflow.com/questions/1007438/anonymous-method-as-parameter-to-begininvoke), why doesn't the compiler just infer what the method signature should be, and create an anonymous method itself with that signature? – Reg Edit Aug 09 '14 at 21:37
  • @RegEdit Anonymous methods/lambdas work the same way. They must be converted to a delegate type. The compiler doesn't know what delegate type you want to use and it is not going to insert one for you. – Mike Zboray Aug 09 '14 at 21:46
  • @RegEdit lambdas aren't implicitly convertible to `delegate` per the spec. This is because .NET needed hard-coded delegate types pre-generics in 1.0. Now it is a legacy feature of the platform. C# could be made to just pick Action and Func, which I'd support. – usr Aug 09 '14 at 21:50
4

This usually comes as a surprise to .NET programmers, the C# Language Specific in section 15.1 explains:

Note that System.Delegate is not itself a delegate type; it is a class type from which all delegate types are derived

And of course there is no conversion of a method to a class. The first argument of BeginInvoke() must be a delegate type to keep the compiler happy. Maybe that sounds like an arbitrary limitation, it is most definitely not. A very important property of delegates is that they are type-safe. A pretty big deal in a statically typed language like C#. You can't invoke a delegate with too few arguments, or too many, or arguments of the wrong type. Checked when the delegate is created, you get a compile time error while you are still in your pajamas or the comfort of your cubicle. No surprises at runtime with your program suddenly keeling over at the most inopportune time of the day. This type checking of course cannot work for Delegate. So it is not a delegate type.

This does go wrong with Control.BeginInvoke(), it uses a back-door to get the method invoked. Kaboom when you pass Math.Pi instead of progress, you can't find out until you run the code. Not a pleasant exception either because it is unclear whether you got the BeginInvoke() call wrong or whether the invoked method threw an exception. Actually much more of a problem with Invoke().

Anyhoo, gotta give the compiler a delegate, more than one way to do that:

The venerable anonymous method syntax still works pretty well in this context:

  this.BeginInvoke(delegate() { ShowProcessErrors(process); });

You already found MethodInvoker, I usually go for Action since it is shorter:

  this.BeginInvoke(new Action(() => ShowProcessErrors(process)));

And you can of course always keep the compiler happy with an extension method:

  this.BeginInvoke(() => ShowProcessErrors(process));

with:

static class Extensions {
    public static void BeginInvoke(this Control ctl, Action a) {
        ctl.BeginInvoke(a);
    }
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I don't think that first one works. I get "Cannot convert anonymous method to type 'System.Delegate' because it not a delegate type." – Mike Zboray Aug 09 '14 at 22:04
  • Tested code, I can't phantom what went wrong. Watch the braces. – Hans Passant Aug 09 '14 at 22:13
  • @mike z you're right... and it's exactly the same point, Hans Passant it came as a surprise to your fingers just then too perhaps :) All the explanations given seem clear, as a description of how things are. But *why* are they this way? The whole thing about type inference is, *compiler, you can see what I mean, and I care about the method you use, not its type, so please make it so, in a strongly typed way*. This is surely the case here too: the compiler could dynamically create an anonymous type, and create a delegate *of that type*. Is there any technical reason it could not do that? – Reg Edit Aug 09 '14 at 22:23
  • Expanded the post a bit to explain why. – Hans Passant Aug 09 '14 at 22:43
  • @RegEdit I didn't design the language so I can't comment on why it is the way that it is. Maybe you'll get lucky and Eric Lippert will leave an insightful post for you. There's probably no technical reason it couldn't be done a different way. He had a good post on [why delegates do not support structural identity](http://blog.coverity.com/2014/06/18/delegates-structural-identity/#.) which is related to this. He makes the point that C# has a more "this code looks like it might be wrong so I’m going to bring it to the attention of the developer" kind of design than say VB. – Mike Zboray Aug 10 '14 at 01:06
  • The delegate as a typesafe method reference is indeed a strong feature of the language. But here, type safety doesn't seem to be helping us at all. `BeginInvoke` wants a `Delegate`, which, as you've pointed out, we are to note `is not itself a delegate type; it is a class type from which all delegate types are derived`. Since `BeginInvoke` can therefore accept *any* delegate type, we can easily pass a reference to the wrong method. And choosing the right method will always be *our* responsibility; surely the compiler could create the delegate type for us? Maybe in a future version... & @mikez – Reg Edit Aug 10 '14 at 11:51