A struct type is a value type typically used to encapsulate small groups of related variables. It is often used for interop with native DLLs ([DllImport]). Structs are also used for performance reasons. Value types are allocated on the stack, which reduces the number of objects the garbage collector (GC) must collect. In addition, structs are smaller than classes in memory because they don't have a header. There are also CLR-specific optimizations such as struct promotion that can improve struct performance. However, there is another point to consider when you care about performance: the order of fields in the struct. By default, a user-defined struct that contains only primitive fields (blittable types) has the Sequential layout with Pack equal to 0. Here is a rule that the CLR follows:
Each field must align with fields of its size (1, 2, 4, 8, etc., bytes) or the alignment of the type, whichever is smaller. Because the default alignment of the type is the size of its largest element, which is greater than or equal to all other field lengths, this usually means that fields are aligned by their size. For example, even if the largest field in a type is a 64-bit (8-byte) integer or the Pack field is set to 8, Byte fields align on 1-byte boundaries, Int16 fields align on 2-byte boundaries, and Int32 fields align on 4-byte boundaries. If a struct contains a reference type, its layout is changed to Auto.
StructLayoutAttribute.Pack Field
Let's see what it means by inspecting the layout of a struct. You can use ObjectLayoutInspector to view the layout of an object.
C#
class Program
{
static void Main()
{
TypeLayout.PrintLayout<Sample>();
}
}
struct Sample
{
byte field1; // 1 byte
int field2; // 4 bytes
bool field3; // 1 byte
short field4; // 2 bytes
}
Type layout for 'Sample'
Size: 12 bytes. Paddings: 4 bytes (%33 of empty space)
|================================|
| 0: Byte field1 (1 byte) |
|--------------------------------|
| 1-3: padding (3 bytes) | 👈 Int32 field is align on a multiple of 4, so it needs 3 padding bytes
|--------------------------------|
| 4-7: Int32 field2 (4 bytes) |
|--------------------------------|
| 8: Boolean field3 (1 byte) |
|--------------------------------|
| 9: padding (1 byte) | 👈 Int16 field is align on a multiple of 2, so it needs 1 padding byte
|--------------------------------|
| 10-11: Int16 field4 (2 bytes) |
|================================|
As you can see, .NET adds padding so that fields are memory-aligned. You can reorder the fields to eliminate padding and save some bytes:
C#
// Change the order of the fields to remove the paddings
struct Sample
{
int field2; // 4 bytes
short field4; // 2 bytes
byte field1; // 1 byte
bool field3; // 1 byte
}
Type layout for 'Sample'
Size: 8 bytes. Paddings: 0 bytes (%0 of empty space) 👈 No more padding
|================================|
| 0-3: Int32 field2 (4 bytes) |
|--------------------------------|
| 4-5: Int16 field4 (2 bytes) |
|--------------------------------|
| 6: Byte field1 (1 byte) |
|--------------------------------|
| 7: Boolean field3 (1 byte) |
|================================|
There is no more padding in the struct and the size of the struct is now 8 bytes instead of 12 bytes. By re-ordering the fields in the struct, we have reduced its size by 33%!
Instead of reordering all fields in your structs manually, you can use the attribute [StructLayout(LayoutKind.Auto)] to let .NET automatically reorder the fields in the most efficient way to avoid padding:
C#
[StructLayout(LayoutKind.Auto)]
struct Sample
{
byte field1; // 1 byte
int field2; // 4 bytes
bool field3; // 1 byte
short field4; // 2 bytes
}
Type layout for 'Sample'
Size: 8 bytes. Paddings: 0 bytes (%0 of empty space)
|================================|
| 0-3: Int32 field2 (4 bytes) |
|--------------------------------|
| 4-5: Int16 field4 (2 bytes) |
|--------------------------------|
| 6: Byte field1 (1 byte) |
|--------------------------------|
| 7: Boolean field3 (1 byte) |
|================================|
Note that sequential layout is only possible when a struct contains no reference types. If a struct has at least one reference-type field, the layout is automatically changed to LayoutKind.Auto. For instance, if we replace the int with a string in the first example, the layout changes to Auto:
C#
struct Sample
{
byte field1; // 1 byte
string field2; // 4 bytes (Reference type => .NET uses LayoutKind.Auto automatically)
bool field3; // 1 byte
short field4; // 2 bytes
}
Type layout for 'Sample'
Size: 8 bytes. Paddings: 0 bytes (%0 of empty space)
|================================|
| 0-3: String field2 (4 bytes) | 👈 .NET has re-order the fields
|--------------------------------|
| 4-5: Int16 field4 (2 bytes) |
|--------------------------------|
| 6: Byte field1 (1 byte) |
|--------------------------------|
| 7: Boolean field3 (1 byte) |
|================================|
#Adding the attribute using a Roslyn Analyzer
You can check whether you should add [StructLayout(LayoutKind.Auto)] for structs in your applications using a Roslyn analyzer. The free analyzer I've built already includes rules for this: https://github.com/meziantou/Meziantou.Analyzer.
You can install the Visual Studio extension or the NuGet package to analyze your code:

#Additional references:
Do you have a question or a suggestion about this post? Contact me!