There's the following interface which defines a packet.
public interface IPacket
{
int Size { get; }
}
There are two implementations, each with its own additional property.
public class FooPacket : IPacket
{
public int Size => 10;
public string FooProperty { get; set; }
}
public class BarPacket : IPacket
{
public int Size => 20;
public string BarProperty { get; set; }
}
The above is library code I have no control over. I want to create a handler for packets
public interface IPacketHandler<T> where T : IPacket
{
void HandlePacket(T packet) ;
}
and create two implementations for the concrete packets.
public class FooPacketHandler : IPacketHandler<FooPacket>
{
public void HandlePacket(FooPacket packet) { /* some logic that accesses FooProperty */ }
}
public class BarPacketHandler : IPacketHandler<BarPacket>
{
public void HandlePacket(BarPacket packet) { /* some logic that accesses BarProperty */ }
}
I'd like to inject a list of packet handlers into a class that manages packet handling so that it can be extended in the future with additional packet handlers.
public class PacketHandlerManager
{
public PacketHandlerManager(IEnumerable<IPacketHandler<IPacket>> packetHandlers)
{
}
}
The trouble I'm having is when creating the injected parameter. I cannot do
var packetHandlers = new List<IPacketHandler<IPacket>>
{
new FooPacketHandler(),
new BarPacketHandler()
};
because I cannot create an instance like so:
IPacketHandler<IPacket> packetHandler = new FooPacketHandler();
I get the error Cannot implicitly convert type 'FooPacketHandler' to 'IPacketHandler<IPacket>. An explicit conversion exists (are you missing a cast?)
I had a look at a similar question: Casting generic type with interface constraint. In that question, OP didn't show the members of the interface, only the definition of it from a generics point of view. From what I can see, if my interface didn't use the generic type parameter as an input, I could make it covariant using the out keyword, but that doesn't apply here.
How do I achieve making manager adhere to the open-closed principle? Is my only recourse changing the interface definition to
public interface IPacketHandler
{
void HandlePacket(IPacket packet);
}
and then casting to a particular packet in the implementation?
The core of the issue is that ultimately you would call your handler passing a concrete packet (of a concrete type) to it as an argument, even though you hide the argument behind
IPacket.Somehow then, trying to call the
HandlePacket( FooPacket )withBarPacketargument would have to fail, the only question is when/where it fails.As you already noticed, introducing the generic parameter to the packet handler makes it fail in the compile time and there is no easy workaround over it.
Your idea to drop the generic parameter, i.e. to have
is a possible solution. It however pushes the possible failure to the runtime, where you now have to check if a handler is called with inappropriate argument.
What you could also do is to make this runtime check more explicit by introducing a contract for it:
This makes it cleaner for the consumer to safely call
HandlePacket- assuming they get a positive result from callingCanHandlePacketbefore.For example, a possible naive loop over a list of packets and calling your handlers would become