Is there a way to cause type inference of the dele

2019-02-26 21:50发布

问题:

Control.BeginInvoke:

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?

回答1:

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);
    }
}


回答2:

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);
    }
}