9

Why does the following fail to infer R:

static R Foo<R>(Func<Action<R>, R> call) { ... }

While pretty much the 'same', works:

static R Foo<R>(Func<Action, R> call) { ... }

Usage:

var i = Foo(ec => -1);

Ways the first sample 'must' be called to compile:

var i = Foo<int>(ec => -1);

-- or --

var i = Foo((Action<int> ec) => -1);

Thoughts: As can be seen in the second snippet, R is already determined by the return type of the 'lambda'. Why can't the same apply to the first? Even with usage of ec (which should be another compiler hint), it fails to infer.

jalf
  • 243,077
  • 51
  • 345
  • 550
leppie
  • 115,091
  • 17
  • 196
  • 297
  • So, in your example, `ec` is convertible to a delegate that takes an `int` and returns `void`? – Frédéric Hamidi Jun 04 '12 at 15:42
  • The type inference rules in C# are made to cover the basic cases. There are some rare cases where the rules (intentionally) don't "take". – usr Jun 04 '12 at 15:50
  • Can you add an example declaration/implementation of `ec`? My guess is that the problem lies there. – Chris Shain Jun 04 '12 at 15:50
  • 1
    Here's an interesting article on the subject. http://msdn.microsoft.com/en-us/vstudio/jj131514.aspx. May or may not actually answer your question, but good background info. – SirPentor Jun 04 '12 at 15:53
  • @ChrisShain: Full example code: https://gist.github.com/2868328 – leppie Jun 04 '12 at 15:59
  • @SirPentor: Thanks. Edit: Does not cover the issue. – leppie Jun 04 '12 at 16:00
  • 2
    I may have found your duplicate ;) http://stackoverflow.com/questions/6229131/why-cant-c-sharp-infer-type-from-this-seemingly-simple-obvious-case – Raphaël Althaus Jun 04 '12 at 16:18
  • @RaphaëlAlthaus: Thanks, almost the same, will read a bit more to see if the same applies :) – leppie Jun 04 '12 at 16:21
  • @RaphaëlAlthaus: I think it is the same issue, except that I am experiencing it in a 'nested' environment. The answer given provides an acceptable understanding. Feel free to 'Mark as duplicate' though :) – leppie Jun 04 '12 at 16:26
  • @Ieppie nono, I like "mysterious" titles ;) – Raphaël Althaus Jun 04 '12 at 18:53

1 Answers1

4

I think that the problem is not that the compiler if failing to infer R for the function CallWithEscapeContinuation, it is that it is failing to infer a type for the lambda:

ec =>
{
  Enumerable.Range(0, 100).Select(x =>
  {
              // Called here, the compiler has no idea what signature to expect for `ec`
              // Could be Action<Int>, could be Action<Decimal> (for example).
    if (x == 40) ec(x);
    return x;
  }).ToList();
  return -1;
}

Whereas when you provide the int hint, it can infer what type the lambda is from that and the signature of CallWithEscapeContinuation.

When you just have Action (as opposed to Action<R>) the above is irrelevant because there are no type parameters affecting the possible signatures of the lambda.

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • Range returns `IEnumerable`, why would `x` be anything else than an `int`? – leppie Jun 04 '12 at 16:11
  • If you comment out everything and just return -1 it doesn't infer the return type either. – Slugart Jun 04 '12 at 16:12
  • It will fail on this too: `var index = CallWithEscapeContinuation(ec => { ec(1); return -1; });` – leppie Jun 04 '12 at 16:12
  • @Slugart that's because it still doesn't know what `ec` is. – Chris Shain Jun 04 '12 at 16:12
  • @ChrisShain: I disagree, it knows exactly what `ec` is. It does not have much of a choice at this point! – leppie Jun 04 '12 at 16:13
  • @leppie again, in the comment example, the compiler doesn't know what signature the lambda has because it doesn't know the signature of `ec`. Edit: for instance, it could (again) be `Action`. For instance: `var index = CallWithEscapeContinuation((Action ec) => { ec(1); return -1; });` – Chris Shain Jun 04 '12 at 16:13
  • @ChrisShain: That last example made sense :) – leppie Jun 04 '12 at 16:24
  • The compiler knows x is an int at that line so it should be able to infer that the first param to ec is an int. To take away the implicit conversions, what if the delegate body were { ec(""); return ""; } ? – Slugart Jun 04 '12 at 16:26
  • BTW, I am not entirely convinced the compiler cannot figure these things out. Eg `Foo(ec => { ec("1"); return "2";})` fails to compile. – leppie Jun 04 '12 at 16:28
  • @Slugart the compiler knows that you are *calling* `ec` with an argument of type `int`, but that doesn't mean that `ec`'s signature is `Action`. A variety of other signatures would work too, but may require type casting. – Chris Shain Jun 04 '12 at 16:28
  • @leppie that fails to compile for a different reason- it groks the lambda, but then the type of the lambda's first parameter (`Action`) conflicts with `Foo` – Chris Shain Jun 04 '12 at 16:30