[英]How to instantiate a class as the interface that it derives from with constrained generic type parameter
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.我想将数据包处理程序列表注入到管理数据包处理的 class 中,以便将来可以使用其他数据包处理程序对其进行扩展。
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?)
我收到错误
Cannot implicitly convert type 'FooPacketHandler' to 'IPacketHandler<IPacket>. An explicit conversion exists (are you missing a cast?)
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 .我看了一个类似的问题: 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.
在那个问题中,OP没有显示接口的成员,只是从generics的角度来定义它。 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.据我所知,如果我的接口没有使用泛型类型参数作为输入,我可以使用
out
关键字使其成为协变的,但这不适用于此处。
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
.问题的核心是最终你会调用你的处理程序,将一个具体的数据包(具体类型)作为参数传递给它,即使你将参数隐藏在
IPacket
后面。
Somehow then, trying to call the HandlePacket( FooPacket )
with BarPacket
argument would have to fail, the only question is when/where it fails.不知何故,尝试使用
BarPacket
参数调用HandlePacket( FooPacket )
将不得不失败,唯一的问题是它何时/何地失败。
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, ie to have您删除通用参数的想法,即拥有
public interface IPacketHandler
{
void HandlePacket(IPacket packet);
}
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:您还可以做的是通过为其引入合同来使此运行时检查更加明确:
public interface IPacketHandler
{
bool CanHandlePacket(IPacket packet);
void HandlePacket(IPacket packet);
}
This makes it cleaner for the consumer to safely call HandlePacket
- assuming they get a positive result from calling CanHandlePacket
before.这使消费者更容易安全地调用
HandlePacket
- 假设他们之前调用CanHandlePacket
得到了肯定的结果。
For example, a possible naive loop over a list of packets and calling your handlers would become例如,一个可能的对数据包列表的简单循环并调用您的处理程序将变为
foreach ( var packet in _packets )
foreach ( var handler in _handlers )
if ( handler.CanHandlePacket(packet) )
handler.HandlePacket(packet);
You can solve this with a little bit of reflection.你可以通过一点反思来解决这个问题。
Firstly, for convenience (and to help slightly with type-safety), introduce a "Tag" interface which all your IPacketHandler<T>
interfaces will implement:首先,为了方便(并且对类型安全有一点帮助),引入一个“Tag”接口,你的所有
IPacketHandler<T>
接口都将实现它:
public interface IPacketHandlerTag // "Tag" interface.
{
}
This is not really necessary, but it means you can use IEnumerable<IPacketHandlerTag>
instead of IEnumerable<object>
later on, which does make things a little more obvious.这并不是真正必要的,但这意味着您可以稍后使用
IEnumerable<IPacketHandlerTag>
而不是IEnumerable<object>
,这确实使事情变得更加明显。
Then your IPacketHandler<T>
interface becomes:然后你的
IPacketHandler<T>
接口变成:
public interface IPacketHandler<in T> : IPacketHandlerTag where T : IPacket
{
void HandlePacket(T packet);
}
Now you can write a PacketHandlerManager
that uses reflection to pick out the method to use to handle a packet, and add it to a dictionary like so:现在您可以编写一个
PacketHandlerManager
使用反射来挑选用于处理数据包的方法,并将其添加到字典中,如下所示:
public class PacketHandlerManager
{
public PacketHandlerManager(IEnumerable<IPacketHandlerTag> packetHandlers)
{
foreach (var packetHandler in packetHandlers)
{
bool appropriateMethodFound = false;
var handlerType = packetHandler.GetType();
var allMethods = handlerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
foreach (var method in allMethods.Where(m => m.Name == "HandlePacket"))
{
var args = method.GetParameters();
if (args.Length == 1 && typeof(IPacket).IsAssignableFrom(args[0].ParameterType))
{
_handlers.Add(args[0].ParameterType, item => method.Invoke(packetHandler, new object[]{item}));
appropriateMethodFound = true;
}
}
if (!appropriateMethodFound)
throw new InvalidOperationException("No appropriate HandlePacket() method found for type " + handlerType.FullName);
}
}
public void HandlePacket(IPacket packet)
{
if (_handlers.TryGetValue(packet.GetType(), out var handler))
{
handler(packet);
}
else
{
Console.WriteLine("No handler found for packet type " + packet.GetType().FullName);
}
}
readonly Dictionary<Type, Action<IPacket>> _handlers = new Dictionary<Type, Action<IPacket>>();
}
If a packet handler passed to the PacketHandlerManager
constructor does not implement a method called HandlePacket
with a single argument that is assignable from IPacket
, it will throw an InvalidOperationException
.如果传递给
PacketHandlerManager
构造函数的数据包处理程序未使用可从IPacket
分配的单个参数实现称为HandlePacket
的方法,它将抛出InvalidOperationException
。
For example, attempting to use an instance of the following class would cause the constructor to throw:例如,尝试使用以下 class 的实例将导致构造函数抛出:
public class BadPacketHandler: IPacketHandlerTag
{
public void HandlePacket(string packet)
{
Console.WriteLine("Handling string");
}
}
Now you can call use it thusly:现在您可以这样调用它:
var packetHandlers = new List<IPacketHandlerTag>
{
new FooPacketHandler(),
new BarPacketHandler()
};
var manager = new PacketHandlerManager(packetHandlers);
var foo = new FooPacket();
var bar = new BarPacket();
var baz = new BazPacket();
manager.HandlePacket(foo);
manager.HandlePacket(bar);
manager.HandlePacket(baz);
Putting it all together into a compilable console app:将它们放在一个可编译的控制台应用程序中:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace ConsoleApp1
{
public interface IPacket
{
int Size { get; }
}
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; }
}
public class BazPacket : IPacket
{
public int Size => 20;
public string BazProperty { get; set; }
}
public interface IPacketHandlerTag // "Tag" interface.
{
}
public interface IPacketHandler<in T> : IPacketHandlerTag where T : IPacket
{
void HandlePacket(T packet);
}
public class FooPacketHandler : IPacketHandler<FooPacket>
{
public void HandlePacket(FooPacket packet)
{
Console.WriteLine("Handling FooPacket");
}
}
public class BarPacketHandler : IPacketHandler<BarPacket>
{
public void HandlePacket(BarPacket packet)
{
Console.WriteLine("Handling BarPacket");
}
}
public class PacketHandlerManager
{
public PacketHandlerManager(IEnumerable<IPacketHandlerTag> packetHandlers)
{
foreach (var packetHandler in packetHandlers)
{
bool appropriateMethodFound = false;
var handlerType = packetHandler.GetType();
var allMethods = handlerType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
foreach (var method in allMethods.Where(m => m.Name == "HandlePacket"))
{
var args = method.GetParameters();
if (args.Length == 1 && typeof(IPacket).IsAssignableFrom(args[0].ParameterType))
{
_handlers.Add(args[0].ParameterType, item => method.Invoke(packetHandler, new object[]{item}));
appropriateMethodFound = true;
}
}
if (!appropriateMethodFound)
throw new InvalidOperationException("No appropriate HandlePacket() method found for type " + handlerType.FullName);
}
}
public void HandlePacket(IPacket packet)
{
if (_handlers.TryGetValue(packet.GetType(), out var handler))
{
handler(packet);
}
else
{
Console.WriteLine("No handler found for packet type " + packet.GetType().FullName);
}
}
readonly Dictionary<Type, Action<IPacket>> _handlers = new Dictionary<Type, Action<IPacket>>();
}
class Program
{
public static void Main()
{
var packetHandlers = new List<IPacketHandlerTag>
{
new FooPacketHandler(),
new BarPacketHandler()
};
var manager = new PacketHandlerManager(packetHandlers);
var foo = new FooPacket();
var bar = new BarPacket();
var baz = new BazPacket();
manager.HandlePacket(foo);
manager.HandlePacket(bar);
manager.HandlePacket(baz);
}
}
}
The output of this is: output 是:
Handling FooPacket
处理 FooPacket
Handling BarPacket
处理 BarPacket
No handler found for packet type ConsoleApp1.BazPacket
找不到数据包类型 ConsoleApp1.BazPacket 的处理程序
Thanks for the answers.感谢您的回答。 The solution I ended up with is this, starting with the library code:
我最终得到的解决方案是这样的,从库代码开始:
public enum PacketType
{
Foo,
Bar
}
public interface IPacket
{
PacketType Type { get; }
}
public class FooPacket : IPacket
{
public PacketType Type => PacketType.Foo;
public string FooProperty { get; }
}
public class BarPacket : IPacket
{
public PacketType Type => PacketType.Bar;
public string BarProperty { get; }
}
The above version is a better approximation of the real thing.上面的版本是一个更好的近似真实的东西。
public interface IPacketHandler
{
void HandlePacket(IPacket packet);
}
public abstract class PacketHandler<T> : IPacketHandler where T : IPacket
{
public abstract PacketType HandlesPacketType { get; }
public void HandlePacket(IPacket packet)
{
if (packet is T concretePacket)
{
HandlePacket(concretePacket);
}
}
protected abstract void HandlePacket(T packet);
}
public class FooPacketHandler : PacketHandler<FooPacket>
{
public override PacketType HandlesPacketType => PacketType.Foo;
protected override void HandlePacket(FooPacket packet) { /* some logic that accesses FooProperty */ }
}
public class BarPacketHandler : PacketHandler<BarPacket>
{
public override PacketType HandlesPacketType => PacketType.Bar;
protected override void HandlePacket(BarPacket packet) { /* some logic that accesses BarProperty */ }
}
public class PacketHandlerManager
{
public PacketHandlerManager(Library library, IEnumerable<IPacketHandler> packetHandlers)
{
foreach (var packetHandler in packetHandlers)
{
library.Bind(packetHandler.HandlesPacketType, packetHandler.HandlePacket);
}
}
}
There's some more logic in PacketHandlerManager
which I've omitted here. PacketHandlerManager
中还有一些逻辑,我在这里省略了。 library
dispatches packets to handlers, so I don't have to deal with that explicitly after I register handlers using the Bind
method. library
将数据包分派给处理程序,因此在使用Bind
方法注册处理程序后,我不必显式处理它。
It's not exactly what I imagined, but it'll do.这不是我想象的那样,但它会做到的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.