[英]How to instantiate a class as the interface that it derives from with constrained generic type parameter
以下接口定义了一个数据包。
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 interface IPacketHandler<T> where T : IPacket
{
void HandlePacket(T packet) ;
}
并为具体数据包创建两个实现。
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 */ }
}
我想将数据包处理程序列表注入到管理数据包处理的 class 中,以便将来可以使用其他数据包处理程序对其进行扩展。
public class PacketHandlerManager
{
public PacketHandlerManager(IEnumerable<IPacketHandler<IPacket>> packetHandlers)
{
}
}
我遇到的麻烦是在创建注入参数时。 我不能做
var packetHandlers = new List<IPacketHandler<IPacket>>
{
new FooPacketHandler(),
new BarPacketHandler()
};
因为我不能像这样创建一个实例:
IPacketHandler<IPacket> packetHandler = new FooPacketHandler();
我收到错误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?)
我看了一个类似的问题: Casting generic type with interface constraint 。 在那个问题中,OP没有显示接口的成员,只是从generics的角度来定义它。 据我所知,如果我的接口没有使用泛型类型参数作为输入,我可以使用out
关键字使其成为协变的,但这不适用于此处。
如何做到让管理者坚持开闭原则? 我唯一的办法是将接口定义更改为
public interface IPacketHandler
{
void HandlePacket(IPacket packet);
}
然后在实现中转换为特定的数据包?
问题的核心是最终你会调用你的处理程序,将一个具体的数据包(具体类型)作为参数传递给它,即使你将参数隐藏在IPacket
后面。
不知何故,尝试使用BarPacket
参数调用HandlePacket( FooPacket )
将不得不失败,唯一的问题是它何时/何地失败。
正如您已经注意到的那样,将通用参数引入数据包处理程序会使其在编译时失败,并且没有简单的解决方法。
您删除通用参数的想法,即拥有
public interface IPacketHandler
{
void HandlePacket(IPacket packet);
}
是一个可能的解决方案。 然而,它将可能的失败推送到运行时,您现在必须检查是否使用不适当的参数调用了处理程序。
您还可以做的是通过为其引入合同来使此运行时检查更加明确:
public interface IPacketHandler
{
bool CanHandlePacket(IPacket packet);
void HandlePacket(IPacket packet);
}
这使消费者更容易安全地调用HandlePacket
- 假设他们之前调用CanHandlePacket
得到了肯定的结果。
例如,一个可能的对数据包列表的简单循环并调用您的处理程序将变为
foreach ( var packet in _packets )
foreach ( var handler in _handlers )
if ( handler.CanHandlePacket(packet) )
handler.HandlePacket(packet);
你可以通过一点反思来解决这个问题。
首先,为了方便(并且对类型安全有一点帮助),引入一个“Tag”接口,你的所有IPacketHandler<T>
接口都将实现它:
public interface IPacketHandlerTag // "Tag" interface.
{
}
这并不是真正必要的,但这意味着您可以稍后使用IEnumerable<IPacketHandlerTag>
而不是IEnumerable<object>
,这确实使事情变得更加明显。
然后你的IPacketHandler<T>
接口变成:
public interface IPacketHandler<in T> : IPacketHandlerTag where T : IPacket
{
void HandlePacket(T packet);
}
现在您可以编写一个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>>();
}
如果传递给PacketHandlerManager
构造函数的数据包处理程序未使用可从IPacket
分配的单个参数实现称为HandlePacket
的方法,它将抛出InvalidOperationException
。
例如,尝试使用以下 class 的实例将导致构造函数抛出:
public class BadPacketHandler: IPacketHandlerTag
{
public void HandlePacket(string packet)
{
Console.WriteLine("Handling string");
}
}
现在您可以这样调用它:
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);
将它们放在一个可编译的控制台应用程序中:
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);
}
}
}
output 是:
处理 FooPacket
处理 BarPacket
找不到数据包类型 ConsoleApp1.BazPacket 的处理程序
感谢您的回答。 我最终得到的解决方案是这样的,从库代码开始:
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; }
}
上面的版本是一个更好的近似真实的东西。
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);
}
}
}
PacketHandlerManager
中还有一些逻辑,我在这里省略了。 library
将数据包分派给处理程序,因此在使用Bind
方法注册处理程序后,我不必显式处理它。
这不是我想象的那样,但它会做到的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.