3

Consider this snippet:

var bytes = new byte[] {0, 0, 0, 0};
bytes.ToList().ForEach(Console.WriteLine);

This will result in the compile time error:

No overload for 'System.Console.WriteLine(int)' matches delegate 'System.Action<byte>'

You can declare a lamdba as a workaround, bytes.ToList().ForEach((b => Console.WriteLine(b))), but why doesn't overload resolution work in the first case?

Kobi
  • 135,331
  • 41
  • 252
  • 292
jdphenix
  • 15,022
  • 3
  • 41
  • 74

2 Answers2

5

Your Foreach method will be inferred as Foreach(Action<byte> action).

There is no WriteLine overload which takes a single parameter byte and thus it doesn't compile.

What's up with that? Why can't it compile with Console.WriteLine(int)?

Because Action<int> isn't compatible to Action<byte>

From C# language specification

15.2 Delegate compatibility:

A method or delegate M is compatible with a delegate type D if all of the following are true:

  • D and M have the same number of parameters, and each parameter in D has the same ref or out modifiers as the corresponding parameter in M.
  • For each value parameter (a parameter with no ref or out modifier), an identity conversion (§6.1.1) or implicit reference conversion (§6.1.6) exists from the parameter type in D to the corresponding parameter type in M.
  • For each ref or out parameter, the parameter type in D is the same as the parameter type in M.
  • An identity or implicit reference conversion exists from the return type of M to the return type of D.

Overload resolution fails at an identity conversion (§6.1.1) or implicit reference conversion (§6.1.6) exist; None of them exist here. Byte doesn't have identity conversion to int or implicit reference conversion either. So, it can't compile to Console.WriteLine(int).

Why can't it compile with Console.WriteLine(int)?

Because Action<T> is contravariant on type parameter T and contravariance doesn't work for value types. If it were some other reference type, it would have compiled to Console.WriteLine(object) because contravariance do work with reference type.

For example:

Action<int> action1 = Console.WriteLine;//Compiles to Console.WriteLine(int)
Action<byte> action2 = Console.WriteLine;//Won't compile
Action<StringBuilder> action3 = Console.WriteLine;//Compiles to Console.WriteLine(object)

As you can see Action<StringBuilder> does compile even though there is no overload of Console.WriteLine(StringBuilder); It is because reference types supports contravariance.

Community
  • 1
  • 1
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • 4
    I'm not sure I see the relation between covariance/contravariance and implicit conversions. There is no inheritance between `int` and `byte`. – Kobi May 10 '15 at 09:17
  • @Kobi I don't get it. `ForEach` takes a `Action`; Implicit conversion doesn't apply there. It is contravariance which is relevant here. – Sriram Sakthivel May 10 '15 at 09:19
  • @Kobi, In this case, the type of the `Action`s won't match because of `int`and `byte` not being inherited in one way or the other. – molnargab May 10 '15 at 09:20
  • Ahh, I see. I just tried with an interface `IFoo` and `struct Foo : IFoo` and I could not assign a method `void Foo(IFoo f)` to an `Action` with the same compile time error - good call for sure :) – jdphenix May 10 '15 at 09:20
  • When you call `Console.WriteLine((byte)2)` you really call `Console.WriteLine(int)`. That is because there is an implicit operator converting `byte` to `int`, not because `byte` extends (inherits) `int`. So, I don't understand how covariance has anything to do with this issue, unless you're trying to explain why `Console.WriteLine(object)` isn't called. – Kobi May 10 '15 at 09:23
  • @Kobi That's a different story. You're talking about calling the method not method group conversion. Implicit delegate conversion doesn't care whether you have implicit conversion or not. – Sriram Sakthivel May 10 '15 at 09:26
  • @Kobi "unless you're trying to explain why Console.WriteLine(object) isn't called" I already explained you **value types doesn't support contravariance**. `byte` is a value type so it doesn't call `Console.WriteLine(object)`. – Sriram Sakthivel May 10 '15 at 09:33
  • @Sriram - I know you explained that, that's exactly what I said `:)`, but you didn't explain why `Console.WriteLine(int)` isn't called (but that's just a feature C# doesn't have). – Kobi May 10 '15 at 09:35
  • 1
    @SriramSakthivel So ultimately, my code doesn't compile because overload resolution (or rather, implicit conversion) functioned, but `Action` is not an `Action`? Or have I missed the boat here? – jdphenix May 10 '15 at 09:38
  • @Kobi I guess I understand your question. Updated my answer. See if that makes sense :) – Sriram Sakthivel May 10 '15 at 09:59
  • @jdphenix I've updated my answer with c# specs. In short `Action` is not compatible with `Action` and thus it doesn't compile. – Sriram Sakthivel May 10 '15 at 10:03
  • @SriramSakthivel Good deal, thanks. A little bit to wrap my head around for sure but your explanation helped. Thank you! – jdphenix May 10 '15 at 10:11
3

Simply put, the signatures don't match as there is no overload for Console.WriteLine(byte).

The lambda example works because you are providing a method that expects a byte which satisfies the signature, and then subsequently passing it into Console.WriteLine (which will implicitly handle byte).

James
  • 80,725
  • 18
  • 167
  • 237
  • 1
    When I call `Console.WriteLine()` with a `byte`, there is an implicit conversion to `int` so `Console.WriteLine(int)` is called. So what about lambdas makes this implicit conversion not work here? I'm sorry to change up the question on you but I think it's a better statement of my misunderstanding. – jdphenix May 10 '15 at 09:06
  • @jdphenix what makes you think it performs an implicit conversion to int? I think it's more likely that ToString is being called on the byte. The problem is the compiler sees Console.WriteLine(int) but is looking to pass a byte, it has no idea that the target method is capable of implicitly handling that type, it just sees a type mismatch. – James May 10 '15 at 09:12
  • I checked the IL emitted, ` IL_0004: call void [mscorlib]System.Console::WriteLine(int32)` when passed a byte – jdphenix May 10 '15 at 09:15