简体   繁体   English

如何使用受约束的泛型类型参数将 class 实例化为派生接口

[英]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.

相关问题 实例化从MonoBehaviour派生的类 - Instantiate a class that derives from MonoBehaviour 如何使用泛型参数实例化接口 - How to instantiate an interface, with a generic parameter 检查类型是否派生自具有多个通用参数的接口 - Check if a Type derives from a Interface with more than one Generic Argument 类型库导出程序遇到了从通用类派生的类型 - Type library exporter encountered a type that derives from a generic class 如何强制一个类实现从特定基类/接口(而不是特定类型)派生的属性 - How to force a class to implement a property that derives from a specific base class/interface (rather than is of a specific type) 创建一个List实例,其类型派生自基类和接口 - Create an instance of a List where the type derives from a base class and an interface 为什么我不能将&#39;as&#39;用于限制为接口的泛型类型参数? - Why can't I use 'as' with generic type parameter that is constrained to be an interface? 如何测试Type是否从抽象基类泛型类型派生? - How to test if Type derives from an abstract base generic type? 获取从泛型类型派生的具体类型,而不管泛型类型参数如何 - Get concrete type that derives from generic type, regardless of the generic type parameter 约束通用类型参数的继承 - Inheritance on a constrained generic type parameter
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM