I'm creating a messaging system for use in an XNA game. My Message types are structs because I want them to behave in a Value Type way.
struct MyMessageType1 : IMessage {}
struct MyMessageType2 : IMessage {}
List<IMessage> messageQueue = new List<IMessage>();
I want to be able to store Messages of different types in my message queue, but I want to do so without any of them being boxed.
If I have the structs implement an interface such as IMessage and I try to store them in a List, they get boxed.
I don't know all the possible message types ahead of time, so I can't just hard code one List for each type.
So the question is how can I store a list of structs of different types without them being boxed?
I don't think you can. Generality comes at a cost. My advice is do not do premature optimization if what you are worried about is performance. If Its not and you really need copy by value behavior think about using inmutable types (a la System.String)
You could create a queue that stores your structs without boxing, and then processes it using an interface with generic method like this:
You keep a separate queue for each type of message and using that you can avoid boxing of messages altogether, without any
StructLayout
trickery and without knowing all possible message typyes beforehand.It's possible, entirely within managed code, to create a single non-generic type of structure (which I'll call a MagicInvoker) which implements an interface, and holds references to an arbitrary number of other structures implementing that same interface, all without using reflection, boxing, or anything else that would cause GC pressure. Indeed, once arrays have reached their maximum sizes, one can create and delete billions of value-type objects without any more heap allocations.
The biggest caveat with such an approach is that such structures become in many ways like the pointers in "old C". Although the MagicInvokers are themselves a value types, and they refer to value types, their semantics are more like old-style pointers. If one copies a MagicInvoker, it will refer to the same structure as the original. Creating a MagicInvoker and then abandoning it without Dispose will cause a memory leak, and using or attempting to Dispose a copy of a MagicInvoker that has already been disposed will cause Undefined Behavior.
A MagicInvoker holds an instance of some class derived from InvokerBase (which will happen to be an Invoker<T> for some T that implements IDoSomething), and an array index. For every type T which is used with MagicInvoker.Create, there will be one instance of class Invoker<T> created; that same instance will be used for all MagicInvokers created from that type.
This cannot be done.
Alternative 1
However, you can emulate things, by using two Lists (
List<MyMessageType1>
andList<MyMessageType2>
).You then concoct one Super Index (possibly, just another array of ints (longs?)) to make it possible to (indirectly) address an item as if it were one list.
You might want to optimize the index (runlength encoding: store just the indexes where the backing array switches: this will also enormously help when iterating a subrange that is known to be contiguous in one of the backing arrays)
Lists use Array storage internally, so - you get no boxing - fast random access - blazing iteration with list.ForEach
Alternative 2
Look at the StructLayout attribute and somehow emulate a Union by doing all the manipulations. If you are really prepared to get your hands dirty, throw in
unsafe {}
blocks (and compile with /unsafe) ... however, seriously consider P/Invoke a C DLL or use C++/CLI if it matters that muchAlternative 3 (added)
Because I really liked the fact that Marc Gravell pointed out you can use the StructLayout that I mentioned, to pinpoint all three members of a
union.NET struct at the same offset; I thought I'd go the extra step and see whether I could make that a hell of a lot moreleakytranparent still. This comes pretty close to being transparent:I'll leave the problem of discriminating this poor men's variant as an exercise to you. The simplist way would be to add a field to the
AnyMessage
struct, but depending on the payload, other strategies might be much more (space/time) efficient.My $0.02
Oh, I'd never actually do this, because it seems like overcomplicated. I'm assuming you have a valid reason to optimize this
PS. If you are asking this after reading my answer here (yesterday: Should I use a struct or a class to represent a Lat/Lng coordinate?), I'm going to snap-judge this premature optimization
Basically, you can't nicely;
object
or an interface: boxedobject
, boxeddynamic
: essentiallyobject
, boxedThere is the option, however, of encapsulating the object in a bigger struct, i.e.
now, this should work but hsa the downside of being much bigger, obviously. You might be able to work around this using explicit-layout to position them all at byte 0 (making a union), but I suspect this isn't allowed on xbox. But on regular .NET: