Cannot convert from 'method group' to Action

Kiritonito picture Kiritonito · Nov 28, 2017 · Viewed 7.3k times · Source

Will be more easy to post the code first then ask why I'm getting this error.

Abstract class - Packet

abstract class Packet
{
    // base class!
}

My first packet

public sealed class FirstPacket : Packet
{
    // First packet implementations...
}

Another Packet

public sealed class AnotherPacket : Packet
{
    // Another packet implementations...
}

Packet OpCodes

public enum OpCode
{
    FirstPacket,
    AnotherPacket
}

Abstract class - BaseConnection

public abstract class BaseConnection
{
    private Dictionary<OpCode, Action<Packet>> _packetHandlers;

    public Connection() {
        _packetHandlers = new Dictionary<OpCode, Action<Packet>>();
    }
}

Finally, my Client

public sealed class Client : BaseConnection
{
    public Client() : base() {
        // Here will throw the errors...
        // CS1503   Argument 2: cannot convert from 'method group' to 'Action<Packet>'
        _packetHandlers.Add(OpCode.FirstPacket, OnReceiveFirst);
        _packetHandlers.Add(OpCode.AnotherPacket, OnReceiveAnother);
    }

    public void OnReceiveFirst(FirstPacket packet) {
    }

    public void OnReceiveAnother(AnotherPacket packet) {
    }
}

According to this answer, a derived class is an instance of its base class and no casting involved.

In my code, if both FirstPacket and AnotherPacket is Packet, why I have to "cast" using lambda?

public sealed class Client : BaseConnection
{
    public Client() : base() {
        // This works...
        _packetHandlers.Add(OpCode.FirstPacket, p => { OnReceiveFirst((FirstPacket)p); });
        _packetHandlers.Add(OpCode.AnotherPacket, p => { OnReceiveAnother((AnotherPacket)p); });
    }

    public void OnReceiveFirst(FirstPacket packet) {
    }

    public void OnReceiveAnother(AnotherPacket packet) {
    }
}

It doesn't make sense to me.

Answer

Sergey Kalinichenko picture Sergey Kalinichenko · Nov 28, 2017

First, note that your lambdas

p => { OnReceiveFirst((FirstPacket)p); }

would not compile without a cast.

The reason why you can do a cast is that you know enough about the logic of your system to decide that OnReceiveFirst would never be called with a parameter of SecondPacket. Hence you conclude that the cast is safe.

Compiler, on the other hand, cannot conclude the same thing, so it asks you to supply a cast manually.

Method groups provide a shortcut for situations when no casting is necessary. For example, if you rewrite your OnReceiveFirst like this

public void OnReceiveFirst(Packet packetOrig) {
    FirstPacket packet = (FirstPacket)packetOrig;
    ...
}

you would be able to use it with method group syntax:

_packetHandlers.Add(OpCode.FirstPacket, OnReceiveFirst); // Compiles

Here, too, casting remains your responsibility, in the sense that if the cast throws an exception, you will be able to trace the error to your own code, not to some compiler magic.