17.6 Simulating a C Union

Each field in a struct is given enough room to store its data. Consider a struct containing one int and one char. The int is likely to start at an offset of 0, and is guaranteed at least four bytes. So, the char could start at an offset of 4. If, for some reason, the char started at an offset of 2, then you'd change the value of the int if you assigned a value to the char. Sounds like mayhem, doesn't it? Strangely enough, the C language supports a variation on a struct called a union that does exactly this. You can simulate this in C# using LayoutKind.Explicit and the FieldOffset attribute.

It might be hard to think of a case in which this would be useful. However, consider a situation in which you want to create a vast quantity of different primitive types, but store them in one array. You have to store them all in an object array, and the primitive values are boxed every time they go into the array. This means you're going to start using a lot of heap memory, and you'll pay the cost of boxing and unboxing as you go from primitive to object and back again. This example shows how this works with three objects, but if it were thousands or millions of objects, the impact on performance would be noticeable:

using System;
public class BoxMe {
  public static void Main( ) {
    // Stuff some primitive values into an array.
    object[ ] a = new object[3];
    a[0] = 1073741824;
    a[1] = 'A';
    a[2] = true;
    // Display each value
    foreach (object o in a) {
      Console.WriteLine("Value: {0}", o);

To avoid the boxing and unboxing operations, you can use a union to create a variant type, which is a type that can take on one value at a time, but can represent a variety of types. Your variant type has a flag field that tells you the type that the variant represents. It also includes one value field for each of the possible types it can take on. Since each value field starts at the same offset, a variant instance never takes up more memory than the largest type plus the size of the flag field (it may also take a couple of bytes to optimally align the fields in memory).

The next example shows the use of a variant struct called MyVariant. It can be an int, char, or bool. The value fields intVal, charVal, and boolVal are stored at an offset (sizeof(byte)) that sets aside enough memory for the flag field vType. This means that the value fields are overlapped. Since the variant can only represent one value at any given time, initializing charVal as "A" will produce that same value even if you inspect intVal. However, the value is treated as an int giving 65, the ASCII value of "A", as shown in this example:

using System;
using System.Runtime.InteropServices;
// Enumerate the possible types for MyVariant
public enum MyVariantType : byte { Int, Char, Bool };
// Define a structure for MyVariant
public struct MyVariant {
  // Type flag
  [FieldOffset (0)] public MyVariantType vType;
  // Start the fields, leaving enough room for vType
  [FieldOffset (sizeof(byte))] public int  intVal;
  [FieldOffset (sizeof(byte))] public char charVal;
  [FieldOffset (sizeof(byte))] public bool boolVal;
  // Return a string representation of this Variant
  public override string ToString( ) {
    switch (vType) {
      case MyVariantType.Int:
        return intVal.ToString( );
      case MyVariantType.Char:
        return charVal.ToString( );
      case MyVariantType.Bool:
        return boolVal.ToString( );
    throw new Exception("Unknown Variant type: " + vType);
// Create an array of variants and display their values
public class VariantTest {
  public static void Main( ) {
    MyVariant[ ] a = new MyVariant[3];
    a[0].vType = MyVariantType.Int;
    a[0].intVal = 1073741824;
    a[1].vType = MyVariantType.Char;
    a[1].charVal = 'A';
    a[2].vType = MyVariantType.Bool;
    a[2].boolVal = true;
    // Display each variant's value
    foreach (MyVariant mv in a) {
      Console.WriteLine("Value: {0}", mv);
    // Reinterpret the char as an int
    Console.WriteLine("{0} is: {1}", a[1], a[1].intVal);

    Part II: Programming with the .NET Framework
    Part IV: API Quick Reference