Search code examples
c#subtype

Cannot convert from 'method group' to Action


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.


Solution

  • 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.