Structures,
like any other value type, implicitly inherit from
System.ValueType
. At first glance, a structure is
similar to a class, but they are actually very different. Knowing
when to use a structure over a class will help tremendously when
designing an application. Using a structure incorrectly can result in
inefficient and hard-to-modify code. Both structures and simple types
inherit from ValueType
.
Structures have two performance advantages over reference types. First, if a structure is allocated on the stack (i.e., it is not contained within a reference type), access to the structure and its data is somewhat faster than access to a reference type on the heap. Reference type objects must follow their reference, or pointer, onto the heap in order to get at their data. However, this performance advantage pales in comparison to the second performance advantage of structures: namely, that cleaning up the memory allocated to a structure on the stack requires a simple change of the address to which the stack pointer points, which is done at the return of a method call. This call is extremely fast compared to allowing the garbage collector to automatically clean up reference types for you in the managed heap.
Structure performance falls short in comparison to that of classes when they are passed by value to other methods. Because they reside on the stack, a structure and its data have to be copied to a new local variable (the method’s parameter that is used to receive the structure) when it is passed by value to a method. This copying takes more time than passing a single reference to a class’s object by value to a method—unless the structure is the same size as or smaller than the machine’s pointer size; thus, a structure with a size of 32 bits is just as cheap to pass as a reference (which happens to be the size of a pointer) on a 32-bit machine. Keep this in mind when choosing between a class and a structure. While creating, accessing, and destroying a class’s object may take longer, it also might not balance the performance hit when a structure is passed by value a large number of times to one or more methods. Keeping the size of the structure small minimizes the performance hit of passing it around by value.
Structures can also cause degradation in performance when they are passed to methods that require a reference type, such as any of the collection types in the FCL. Passing a structure (or any simple type, for that matter) into a method requiring a reference type causes the structure to be boxed. Boxing is wrapping a value type in an object. When the method returns, the value type will be unboxed, which means that the value type will be extracted from its object wrapper. Both of these operations are time-consuming and may degrade performance.
As concerns the object-oriented capabilities of classes and structures, classes have far more flexibility. A structure cannot contain a user-defined default constructor, since the C# compiler automatically provides a default constructor that initializes all the fields in the structure to their default values. This is also why no field initializers can be added to a structure. If you need to override the default field values, a structure might not be the way to go. However, a parameterized constructor can be created that initializes the structure’s fields to any value that is necessary.
Structures, like classes, can inherit from interfaces; but unlike classes, structures cannot inherit from a class or a structure. This limitation precludes creating structure hierarchies, as you can with classes. Polymorphism as implemented through an abstract base class is also prohibited when using a structure, since a structure cannot inherit from another class.
Its identity is important. Structures get copied implicitly when being passed by value into a method. You could pass a structure by reference, but then another object might not be able to hold a reference to the structure.
It will have a large memory footprint.
Its fields need initializers.
You need to inherit from a base class.
You need polymorphic behavior. That is, you need to implement an abstract base class from which you will create several similar classes that inherit from this abstract base class. (Note that polymorphism can be implemented via interfaces as well, but it is usually not a good idea to place an interface on a value type, since a boxing operation will occur if the structure is converted to the interface type.) For more on polymorphism through interfaces, see Recipe 3.17.
It will act like a primitive type (int
,
long
, byte
, etc.).
It must have a small memory footprint.
You are calling a P/Invoke method that requires a structure to be passed in by value. Platform Invoke, or P/Invoke for short, allows managed code to call out to an unmanaged method exposed from within a DLL. Many times an unmanaged DLL method requires a structure to be passed in to it; using a structure is an efficient method of doing this and is the only way if the structure is being passed by value.
You need to avoid the overhead of garbage collection.
Its fields need to be initialized only to their default values. This
value would be zero
for numeric types,
false
for Boolean types, and
null
for reference types.
You do not need to inherit from a base class (other than
ValueType
, from which all structs inherit).
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.
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; }
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#.