programming
data structures
object-oriented programming
C#
type checking

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.

csharp
1using System;
2
3struct Point { }
4class Customer { }
5
6Console.WriteLine(typeof(Point).IsClass);      // False
7Console.WriteLine(typeof(Point).IsValueType);  // True
8
9Console.WriteLine(typeof(Customer).IsClass);      // True
10Console.WriteLine(typeof(Customer).IsValueType);  // False

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:

csharp
1using System;
2
3static bool IsStructType(Type type)
4{
5    return type.IsValueType && !type.IsPrimitive && !type.IsEnum;
6}
7
8Console.WriteLine(IsStructType(typeof(int)));        // False
9Console.WriteLine(IsStructType(typeof(DayOfWeek)));  // False
10Console.WriteLine(IsStructType(typeof(DateTime)));   // True

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.

csharp
1using System;
2
3static bool IsClassType(Type type)
4{
5    return type.IsClass;
6}
7
8Console.WriteLine(IsClassType(typeof(string)));   // True
9Console.WriteLine(IsClassType(typeof(object)));   // True
10Console.WriteLine(IsClassType(typeof(int)));      // False

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.

csharp
1using System;
2
3static void Describe<T>()
4{
5    var type = typeof(T);
6    Console.WriteLine($"{type.Name}: IsClass={type.IsClass}, IsValueType={type.IsValueType}");
7}
8
9Describe<int?>();
10Describe<string>();
11Describe<Guid>();

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 null is 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 IsValueType as 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.IsClass to detect classes.
  • Use Type.IsValueType to 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.

Course illustration
Course illustration

All Rights Reserved.