How to check programmatically if a type is a struct or a class?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In C#, the runtime already exposes enough metadata to tell whether a type is a reference type or a value type. In practice, checking whether something is a class or a struct comes down to reading Type metadata such as IsClass and IsValueType and then handling special cases like enums and primitive types explicitly.
The Core Reflection Properties
For most code, these two properties do the job:
- '
type.IsClass' - '
type.IsValueType'
A class returns true for IsClass. A struct returns true for IsValueType.
That already answers the common runtime question.
The Important Caveat About IsValueType
IsValueType is broader than "user-defined struct." It is also true for built-in numeric types, bool, DateTime, enums, and nullable value types. So if your real question is specifically "did the developer declare this with the struct keyword," you need a narrower test.
A practical filter is:
This treats framework structs such as DateTime the same way it treats your own custom structs, which is usually what you want.
If You Need a Class Check
The class side is simpler because IsClass already excludes interfaces, enums, and value types.
Remember that interfaces are not classes. If your branching logic groups "reference types" together, you may need a different condition than IsClass.
Nullable Structs and Generics
Nullable<T> is itself a value type, so typeof(int?).IsValueType is true. That is expected because nullable value types are wrappers around structs.
With generics, reflection still works after you get the concrete Type object.
This is useful in serializers, mappers, and validation frameworks that need different logic for value and reference semantics.
Why You Might Care
The distinction matters when you decide:
- whether
nullis a valid state - whether assignments copy values or references
- whether boxing may occur
- how default values behave
Reflection-based frameworks often branch on this metadata to choose constructors, handle defaults, or optimize conversions.
A Helper Method for Real Code
In application code, the cleanest approach is usually to hide the reflection rules behind helper methods rather than spreading IsClass and IsValueType checks throughout the codebase. That keeps serializer, mapper, or validation code easier to read and easier to correct later if the exact classification rule changes.
For example, one project may want to group all value types together, while another may want to separate primitives from richer structs such as Guid and DateTime. Wrapping the check in one method makes that policy explicit instead of accidental.
Common Pitfalls
- Treating
IsValueTypeas meaning only custom structs. It also includes primitives, enums, and nullable value types. - Forgetting that interfaces are not classes, even though they are reference types.
- Writing type-branching logic for runtime behavior when compile-time generics or overloads would be simpler.
- Assuming structs always live on the stack. Boxing and containment can change storage behavior.
- Checking for class versus struct when the real requirement is mutable versus immutable or reference versus value semantics.
Summary
- Use
Type.IsClassto detect classes. - Use
Type.IsValueTypeto detect value types, including structs. - Exclude primitives and enums if you specifically want struct-like user or framework value types.
- Nullable value types are still value types.
- Choose the check that matches your real requirement, not just the syntax keyword.

