C# sizeof Keyword: Compile-Time and Runtime Behavior
c# sizeof keyword: Understand the C# sizeof keyword: compile-time constants, unsafe context requirements, struct behavior, and differences from Marshal.SizeOf.
The c# sizeof keyword 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 primitive types listed below, the compiler substitutes a constant, so the expression can appear in constant contexts such as array sizes or const declarations.
Compile-Time Constants for Primitive 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); byte[] buffer = new byte[sizeof(long) * count];
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 primitive list, sizeof requires an unsafe context. That includes enums, user-defined structs, and other value types. The compiler rejects the expression when the type is not known to have a fixed size.
unsafe { int size = sizeof(MyStruct); }
The unsafe keyword must be applied either to the enclosing block or to the containing method. 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 a struct, sizeof returns the total managed 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; 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<byte> over a struct:
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 primitive types, sizeof has zero runtime cost because the compiler substitutes the constant. For structs in an unsafe context, the size is computed at runtime, but the cost is a single metadata lookup, not a measurement of the actual memory.
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-primitive 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.