如何在没有拳击的情况下存储不同类型的结构

Bow*_*opa 9 c# generics xna struct boxing

我正在创建一个用于XNA游戏的消息传递系统.我的消息类型是结构,因为我希望它们以值类型的方式运行.

struct MyMessageType1 : IMessage {}
struct MyMessageType2 : IMessage {}

List<IMessage> messageQueue = new List<IMessage>();
Run Code Online (Sandbox Code Playgroud)

我希望能够在我的消息队列中存储不同类型的消息,但我想这样做而不会将它们中的任何一个装箱.

如果我有结构实现了一个接口,如IMessage,我尝试将它们存储在List中,它们就会被装箱.

我不知道所有可能的消息类型,所以我不能只为每种类型硬编码一个列表.

所以问题是如何在不加框的情况下存储不同类型的结构列表?

seh*_*ehe 9

这是不可能做到的.

备选方案1

但是,您可以使用两个列表(List<MyMessageType1>List<MyMessageType2>)来模拟事物.

然后你编写一个超级索引(可能只是另一个整数数组(long?)),以便(可以)间接地对一个项目进行寻址,就像它是一个列表一样.

您可能希望优化索引(runlength编码:仅存储后备阵列切换的索引:当迭代已知在其中一个后备阵列中连续的子范围时,这也将非常有用)

列表在内部使用数组存储,所以 - 你没有拳击 - 快速随机访问 - 与list.ForEach的炽热迭代

备选方案2

查看StructLayout属性,并以某种方式通过执行所有操作来模拟Union.如果你真的准备好弄脏你的东西,抛出unsafe {}块(并使用/ unsafe编译)......但是,认真考虑P/Invoke C DLL或使用C++/CLI如果它很重要那么

备选方案3(已添加)

因为我真的很喜欢Marc Gravell指出你可以使用我提到的StructLayout这一事实,以相同的偏移量确定联合 .NET结构的所有三个成员; 我想我会去额外的步骤,看看我是否能做出很多的地狱更 tranparent依然.这非常接近透明:

using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace LeakyAbstractions
{
    struct TypeA {}
    struct TypeB {}
    struct TypeC {}

    [StructLayout(LayoutKind.Explicit)] internal struct AnyMessage {
        [FieldOffset(0)] public TypeA A;
        [FieldOffset(0)] public TypeB B;
        [FieldOffset(0)] public TypeC C;

        AnyMessage(TypeA a) { A = a; }
        AnyMessage(TypeB b) { B = b; }
        AnyMessage(TypeC c) { C = c; }

        public static implicit operator TypeA(AnyMessage msg) { return msg.A; }
        public static implicit operator TypeB(AnyMessage msg) { return msg.B; }
        public static implicit operator TypeC(AnyMessage msg) { return msg.C; }

        public static implicit operator AnyMessage(TypeA a) { return a; }
        public static implicit operator AnyMessage(TypeB b) { return b; }
        public static implicit operator AnyMessage(TypeC c) { return c; }
    }

    public class X
    {
        public static void Main(string[] s) 
        {
            var anyMessages = new List<AnyMessage> { 
                new TypeA(),
                new TypeB(),
                new TypeC(),
            };

            TypeA a = anyMessages[0];
            TypeB b = anyMessages[1];
            TypeC c = anyMessages[2];

            anyMessages.Add(a);
            anyMessages.Add(b);
            anyMessages.Add(c);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我将把这个可怜男人的变种作为锻炼的问题留给你.简单的方法是向结构添加一个字段AnyMessage,但根据有效负载,其他策略可能更多(空间/时间)有效.


我的0.02美元

哦,我从来没有真正这样做过,因为它似乎过于复杂.我假设你有正当理由来优化它


PS.如果你在这里阅读我的答案之后问这个问题(昨天:我应该使用结构或类来表示一个Lat/Lng坐标吗?),我将快速判断这个过早的优化


Mar*_*ell 7

基本上,你不能很好 ;

  • 作为object或接口处理:盒装
  • 用抽象基类包装泛型类型:重新创建一个盒子
  • 反射:用途object,盒装
  • dynamic:基本上object,盒装

存在该选项,然而,封装在一个更大的结构中的对象,即

struct AnyMessage {
    public TypeA A;
    public TypeB B;
    public TypeC C;
}
struct TypeA {...}
struct TypeB {...}
struct TypeC {...}
Run Code Online (Sandbox Code Playgroud)

现在,这应该有效,但显然是更大的缺点.您可以使用显式布局来解决这个问题,将它们全部放在字节0(建立联合),但我怀疑这在xbox上是不允许的.但在常规.NET上:

[StructLayout(LayoutKind.Explicit)] struct AnyMessage {
    [FieldOffset(0)] public TypeA A;
    [FieldOffset(0)] public TypeB B;
    [FieldOffset(0)] public TypeC C;
}
Run Code Online (Sandbox Code Playgroud)

  • 实际上,您可以在 Xbox 上使用 `StructLayout` 和 `FieldOffset`。 (2认同)

svi*_*ick 5

您可以创建一个无需装箱即可存储结构的队列,然后使用具有通用方法的接口处理它,如下所示:

interface IMessageProcessor
{
    void Process<T>(T message) where T : struct, IMessage;
}

class MessageQueue
{
    abstract class TypedMessageQueue
    {
        public abstract void ProcessNext(IMessageProcessor messageProcessor);
    }

    class TypedMessageQueue<T> : TypedMessageQueue where T : struct, IMessage
    {
        Queue<T> m_queue = new Queue<T>();

        public void Enqueue(T message)
        {
            m_queue.Enqueue(message);
        }

        public override void ProcessNext(IMessageProcessor messageProcessor)
        {
            messageProcessor.Process(m_queue.Dequeue());
        }
    }

    Queue<Type> m_queueSelectorQueue = new Queue<Type>();
    Dictionary<Type, TypedMessageQueue> m_queues =
        new Dictionary<Type, TypedMessageQueue>();

    public void Enqueue<T>(T message) where T : struct, IMessage
    {
        TypedMessageQueue<T> queue;
        if (!m_queues.ContainsKey(typeof(T)))
        {
            queue = new TypedMessageQueue<T>();
            m_queues[typeof(T)] = queue;
        }
        else
            queue = (TypedMessageQueue<T>)m_queues[typeof(T)];

        queue.Enqueue(message);
        m_queueSelectorQueue.Enqueue(typeof(T));
    }

    public void ProcessNext(IMessageProcessor messageProcessor)
    {
        var type = m_queueSelectorQueue.Dequeue();
        m_queues[type].ProcessNext(messageProcessor);
    }
}
Run Code Online (Sandbox Code Playgroud)

您为每种类型的消息保留一个单独的队列,使用它可以完全避免消息装箱,无需任何StructLayout技巧,也无需事先了解所有可能的消息类型。