DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.1 Creating Union-Type Structures

Problem

You need to create a data type that operates similar to a union type in C++. A union type is useful mainly in interop scenarios where the unmanaged code accepts and/or returns a union type; we suggest that you do not use it in other situations.

Solution

Use a structure and mark it with the StructLayout attribute (specifiying the LayoutKind.Explicit layout kind in the constructor). In addition, mark each field in the structure with the FieldOffset attribute. The following structure defines a union in which a single signed numeric value can be stored:

using System.Runtime.InteropServices;

[StructLayoutAttribute(LayoutKind.Explicit)]
struct SignedNumber 
{
    [FieldOffsetAttribute(0)] 
    public sbyte Num1;
    
    [FieldOffsetAttribute(0)] 
    public short Num2;

    [FieldOffsetAttribute(0)] 
    public int Num3;

    [FieldOffsetAttribute(0)] 
    public long Num4;

    [FieldOffsetAttribute(0)] 
    public float Num5;

    [FieldOffsetAttribute(0)] 
    public double Num6;

    [FieldOffsetAttribute(0)] 
    public decimal Num7;
}

The next structure is similar to the SignedNumber structure, except that it also can contain a String type in addition to the signed numeric value:

[StructLayoutAttribute(LayoutKind.Explicit)]
struct SignedNumberWithText 
{
    [FieldOffsetAttribute(0)] 
    public sbyte Num1;
    
    [FieldOffsetAttribute(0)] 
    public short Num2;

    [FieldOffsetAttribute(0)] 
    public int Num3;

    [FieldOffsetAttribute(0)] 
    public long Num4;

    [FieldOffsetAttribute(0)] 
    public float Num5;

    [FieldOffsetAttribute(0)] 
    public double Num6;

    [FieldOffsetAttribute(0)] 
    public decimal Num7;

    [FieldOffsetAttribute(16)] 
    public string Text1;
}

Discussion

Unions are structures usually found in C++ code; however, there is a way to duplicate that type of structure using a C# structure data type. A union is a structure that accepts more than one type at a specific location in memory for that structure. For example, the SignedNumber structure is a union-type structure built using a C# structure. This structure accepts any type of signed numeric type (sbyte, int, long, etc.), but it accepts this numeric type at only one location, or offset, within the structure.

Since StructLayoutAttribute can be applied to both structures and classes, a class can also be used when creating a union data type.


Notice the FieldOffsetAttribute has the value zero passed to its constructor. This denotes that this field will be at the zeroth offset (this is a byte offset) within this structure. This attribute is used in tandem with the StructLayoutAttribute to manually enforce where the fields in this structure will start (that is, at which offset from the beginning of this structure in memory each field will start at). The FieldOffsetAttribute can be used only with a StructLayoutAttribute set to LayoutKind.Explicit. In addition, it cannot be used on static members within this structure.

Unions can become problematic, since several types are essentially laid on top of one another. The biggest problem is extracting the correct data type from a union structure. Consider what happens if you choose to store the long numeric value long.MaxValue in the SignedNumber structure. Later, you might accidentally attempt to extract a byte data type value from this same structure. In doing so, you will get back only the first byte of the long value.

Another problem is starting fields at the correct offset. The SignedNumberWithText union overlays numerous signed numeric data types at the zeroth offset. The last field in this structure is laid out at the sixteenth byte offset from the beginning of this structure in memory. If you accidentally overlay the string field Text2 on top of any of the other signed numeric data types, you will get an exception at runtime. The basic rule is that you are allowed to overlay a value type on another value type, but you cannot overlay a reference type over a value type. If the Text2 field were marked with the following attribute:

[FieldOffsetAttribute(14)]

this exception is thrown at runtime (note that the compiler does not catch this problem):

An unhandled exception of type 'System.TypeLoadException' occurred in 
Chapter_Code.exe.

Additional information: Could not load type Chapter_Code.SignedNumberWithText from 
assembly 14 because it contains an object field at offset 14 that is incorrectly 
aligned or overlapped by a non-object field.

It is imperative to get the offsets correct when using complex unions in C#.

See Also

See the "StructLayoutAttribute Class" topic in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section