我正在创建一个用于XNA游戏的消息传递系统。我的消息类型是结构,因为我希望它们以值类型的方式运行。
struct MyMessageType1 : IMessage {}
struct MyMessageType2 : IMessage {}
List<IMessage> messageQueue = new List<IMessage>();
我希望能够在我的消息队列中存储不同类型的消息,但我想这样做而不会将它们中的任何一个打包。
如果我有结构实现了一个接口,如IMessage,我尝试将它们存储在List中,它们就会被装箱。
我不知道所有可能的消息类型,所以我不能只为每种类型硬编码一个列表。
所以问题是如何在没有盒装的情况下存储不同类型的结构列表?
答案 0 :(得分:7)
这不可能。
但是,您可以使用两个列表(List<MyMessageType1>
和List<MyMessageType2>
)来模拟内容。
然后您编写一个超级索引(可能只是另一个整数数组(多头?)),以便(可以)间接地对项目进行寻址,就像它是一个列表一样。
您可能希望优化索引(runlength编码:仅存储支持阵列切换的索引:这在迭代已知在其中一个支持数组中连续的子范围时也会非常有用)
列表在内部使用数组存储,所以 - 你没有拳击 - 快速随机访问 - 使用list.ForEach进行炽热迭代
查看StructLayout属性,并以某种方式通过执行所有操作来模拟Union。如果你真的准备好弄脏你的东西,抛出unsafe {}
块(并使用/ unsafe编译)......但是,认真考虑P / Invoke C DLL或使用C ++ / CLI如果重要 那 很多
因为我真的很喜欢Marc Gravell指出你可以使用我提到的StructLayout这一事实,以确定同一偏移量的 union .NET结构的所有三个成员;我以为我会采取额外的步骤,看看我是否能够做到更多 leaky 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);
}
}
}
我将把这个穷人的变种作为锻炼的问题留给你。简单的方法是向AnyMessage
结构添加一个字段,但根据有效负载,其他策略可能更多(空间/时间)效率。
我的$ 0.02
哦,我从来没有真正这样做过,因为它似乎过于复杂。我假设你有正当理由来优化这个
PS。如果你在这里阅读我的答案之后问这个问题(昨天:Should I use a struct or a class to represent a Lat/Lng coordinate?),我将快速判断这个过早优化
答案 1 :(得分:6)
基本上,你不能很好地;
object
或接口:盒装object
,装箱dynamic
:基本上object
,装箱是选项,但封装对象在更大的结构中,即
struct AnyMessage {
public TypeA A;
public TypeB B;
public TypeC C;
}
struct TypeA {...}
struct TypeB {...}
struct TypeC {...}
现在,这应该可行,但显然是更大的缺点。您可能能够使用显式布局解决这个问题,将它们全部放在字节0处(制作 union ),但我怀疑这个在xbox上是不允许的。但是在常规的.NET上:
[StructLayout(LayoutKind.Explicit)] struct AnyMessage {
[FieldOffset(0)] public TypeA A;
[FieldOffset(0)] public TypeB B;
[FieldOffset(0)] public TypeC C;
}
答案 2 :(得分:2)
我认为你不能。一般性是有代价的。我的建议是,如果您担心的是性能,请不要过早优化。如果它不是,你真的需要按值行为复制考虑使用inmutable类型(一个系统.String)
答案 3 :(得分:2)
你可以创建一个存储你的结构而不用装箱的队列,然后使用带有这样的通用方法的接口来处理它:
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);
}
}
为每种类型的邮件保留一个单独的队列,使用它可以完全避免装箱,没有任何StructLayout
诡计,也不会事先知道所有可能的邮件。
答案 4 :(得分:1)
完全在托管代码中,可以创建一个实现接口的单一非泛型类型的结构(我称之为MagicInvoker),并保存对实现相同接口的任意数量的其他结构的引用,所有这些都不使用反射,装箱或其他会导致GC压力的东西。实际上,一旦数组达到了它们的最大大小,就可以创建和删除数十亿的值类型对象,而无需进行任何堆分配。
这种方法最大的警告是,这种结构在许多方面变得像“旧C”中的指针一样。虽然MagicInvokers本身就是一个值类型,并且它们引用了值类型,但它们的语义更像是旧式指针。如果复制一个MagicInvoker,它将引用与原始相同的结构。创建一个MagicInvoker然后在不使用Dispose的情况下放弃它会导致内存泄漏,并且使用或尝试处理已经处置的MagicInvoker副本将导致未定义的行为。
Public Interface IDoSomething Sub Dosomething() End Interface Structure MagicInvoker Implements IDoSomething, IDisposable Private holder As InvokerBase Private index As Integer Sub DoSomething() Implements IDoSomething.Dosomething holder.DoDoSomething(index) End Sub Shared Function Create(Of T As IDoSomething)(ByVal thing As T) As MagicInvoker Dim newInvoker As MagicInvoker newInvoker.holder = Invoker(Of T).HolderInstance newInvoker.index = Invoker(Of T).DoAdd(thing) Return newInvoker End Function Function Clone() As MagicInvoker Dim newHolder As New MagicInvoker newHolder.holder = Me.holder newHolder.index = Me.holder.DoClone(Me.index) Return newHolder End Function Private MustInherit Class InvokerBase MustOverride Sub DoDoSomething(ByVal Index As Integer) MustOverride Function DoClone(ByVal srcIndex As Integer) As Integer MustOverride Sub DoDelete(ByVal srcIndex As Integer) End Class Private Class Invoker(Of T As IDoSomething) Inherits InvokerBase Shared myInstances(15) As T, numUsedInstances As Integer Shared myDeleted(15) As Integer, numDeleted As Integer Public Shared HolderInstance As New Invoker(Of T) Overrides Sub DoDoSomething(ByVal index As Integer) myInstances(index).Dosomething() End Sub Private Shared Function GetNewIndex() As Integer If numDeleted > 0 Then numDeleted -= 1 Return myDeleted(numDeleted) Else If numUsedInstances >= myInstances.Length Then ReDim Preserve myInstances(myInstances.Length * 2 - 1) End If numUsedInstances += 1 Return numUsedInstances - 1 End If End Function Public Shared Function DoAdd(ByVal value As T) As Integer Dim newIndex As Integer = GetNewIndex() myInstances(newIndex) = value Return newIndex End Function Public Overrides Sub DoDelete(ByVal srcIndex As Integer) If numDeleted >= myDeleted.Length Then ReDim Preserve myDeleted(myDeleted.Length * 2 - 1) End If myDeleted(numDeleted) = srcIndex numDeleted += 1 End Sub Public Overrides Function DoClone(ByVal srcIndex As Integer) As Integer Dim newIndex As Integer = GetNewIndex() myInstances(newIndex) = myInstances(srcIndex) Return newIndex End Function End Class ' Note: Calling Dispose on a MagicInvoker will cause all copies of it to become invalid; attempting ' to use or Dispose one will cause Undefined Behavior. Conversely, abandoning the last copy of ' a MagicInvoker will cause a memory leak. Public Sub Dispose() Implements System.IDisposable.Dispose If holder IsNot Nothing Then holder.DoDelete(index) holder = Nothing End If End Sub End Structure
MagicInvoker拥有一个派生自InvokerBase的类的实例(对于某些实现IDoSomething的T来说恰好是一个Invoker&lt; T&gt;)和一个数组索引。对于与MagicInvoker.Create一起使用的每个类型T,将存在一个类Invoker&lt; T&gt;的实例。创建;同一个实例将用于从该类型创建的所有MagicInvokers。