C# sizeof Keyword: Compile-Time and Runtime Behavior
Learn how the C# sizeof operator returns type sizes, when unsafe context is required for structs, and how it differs from Marshal.SizeOf.
The C# sizeof operator returns the number of bytes a type occupies in memory. For the built-in value types, the result is a compile-time constant. For user-defined structs, it is evaluated at runtime and requires an unsafe context. Knowing which case applies determines whether your code compiles cleanly or fails with a compiler error.
What the sizeof Keyword Returns
sizeof(type) takes a type name and returns an int representing the size in bytes. The syntax is direct:
int size = sizeof(int); // 4
For the built-in types listed below, the compiler substitutes a constant, so the expression can appear in constant contexts such as const declarations.
Compile-Time Constants for Built-in Types
The following table shows the sizes returned for the built-in types. These values are fixed by the C# specification and do not change across platforms:
| Type | sizeof value |
|---|---|
| byte | 1 |
| sbyte | 1 |
| short | 2 |
| ushort | 2 |
| int | 4 |
| uint | 4 |
| long | 8 |
| ulong | 8 |
| char | 2 |
| float | 4 |
| double | 8 |
| bool | 1 |
| decimal | 16 |
Because these are constants, you can use them where a constant expression is required:
const int BytesPerInt = sizeof(int); const int BufferBytes = sizeof(long) * 4;
Note that char is 2 bytes because .NET strings use UTF-16. A common mistake is assuming char matches a single byte.
When Unsafe Context Is Required
For any type not in the built-in list, sizeof requires an unsafe context. That includes enums and user-defined structs that are otherwise valid unmanaged types. The compiler rejects the expression for reference types and for structs that contain reference-type fields.
unsafe { int size = sizeof(MyStruct); }
The unsafe modifier can be applied to the enclosing block, method, or type. You also need to enable the AllowUnsafeBlocks compiler option, which is off by default in most project templates.
Behavior with Structs and Unmanaged Types
For an unmanaged struct, sizeof returns the total size, including any padding the runtime inserts for alignment. Consider:
struct Point { public int X; public int Y; } unsafe { int size = sizeof(Point); // 8 }
Two int fields produce 8 bytes with no padding. The size changes when the struct contains fields of mixed sizes, because the runtime aligns each field to its natural boundary. The result is not simply the sum of the field sizes.
This behavior applies only to unmanaged types. A struct that contains a reference-type field cannot be used with sizeof, because the reference type has no fixed size.
sizeof vs Marshal.SizeOf
Marshal.SizeOf returns the size of a type after marshaling to an unmanaged representation. For blittable types the two values often match, but they can diverge when marshaling applies layout transformations, string conversions, or explicit field offsets.
using System.Runtime.InteropServices; unsafe { int managedSize = sizeof(Point); } int marshaledSize = Marshal.SizeOf<Point>();
Use sizeof when you are reasoning about managed memory layout, such as when allocating a buffer for Span<T>. Use Marshal.SizeOf when you are preparing data for a native API call, because that is the size the unmanaged side expects.
Practical Use Cases
The most common use of sizeof is sizing buffers for unmanaged or performance-sensitive code. For example, when working with Span<Point> over a struct:
unsafe { Span<Point> points = stackalloc Point[count]; int bytes = count * sizeof(Point); }
The expression sizeof(Point) keeps the buffer size in sync with the struct definition. If the struct gains a field, the size updates automatically, which is safer than hard-coding a literal byte count.
Performance and Maintainability Considerations
For built-in types, sizeof has zero runtime cost because the compiler substitutes a constant. For structs in unsafe code, sizeof produces a runtime value, so it cannot be used in a constant expression. The distinction matters more for where the expression can appear than for performance.
The maintainability benefit is that the size stays correct when the type changes. The risk is in the opposite direction: code that depends on a specific size, such as a serialization format or a native struct layout, can break silently if the struct changes. In those cases, prefer an explicit StructLayout attribute with fixed field offsets so the layout is deterministic.
Common Mistakes and Edge Cases
Three errors account for most sizeof misuse. First, applying it to a reference type fails to compile, because reference types have no fixed size. Second, forgetting the unsafe context produces a compile-time error for non-built-in types. Third, assuming sizeof matches the marshaled size can produce buffer overruns or underruns in interop code.
Also be aware that sizeof(bool) is 1, which surprises developers coming from C++, where bool size is implementation-defined. The C# value is fixed and will not change.