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.
|