Question

C# Type Checking Explained: typeof vs GetType() vs is

csharptypestypeofgettype

Question

In C#, I have seen several ways to check types:

Type t = typeof(SomeType);
if (t == typeof(int))
{
    // Some code here
}

I also know this can be written using GetType():

if (obj1.GetType() == typeof(int))
{
    // Some code here
}

Or by using the is operator:

if (obj1 is int)
{
    // Some code here
}

The is version looks the cleanest to me, but I am not sure whether these approaches are truly equivalent. What are the differences between typeof, GetType(), and is in C#? When should each one be used, and which is generally the best choice?

Short Answer

By the end of this page, you will understand what typeof, GetType(), and is actually do in C#, how they differ, and when each one is appropriate. You will also learn the important difference between checking an exact type and checking whether a value can be treated as a type, especially when inheritance is involved.

Concept

In C#, typeof, GetType(), and is are related to types, but they answer different questions.

  • typeof(T) asks: "What is the Type object for this known type name?"
  • obj.GetType() asks: "What is the object's exact runtime type?"
  • obj is T asks: "Can this object be treated as a T?"

These are not interchangeable in all situations.

typeof

typeof is used with a type name known at compile time.

Type t = typeof(int);

This does not inspect an object instance. It simply gives you the System.Type metadata for a type.

Use it when you need:

  • reflection
  • type comparison against known types
  • generic type metadata
  • attributes or framework code

GetType()

GetType() is an instance method available on all objects.

Type t = obj.GetType();

This returns the object's exact runtime type.

Important:

  • It only works if obj is not null
  • It does not match base classes or interfaces when compared directly

Example:

class Animal {}
class Dog : Animal {}

Animal a = new Dog();

Console.WriteLine(a.GetType() == typeof(Animal)); // false
Console.WriteLine(a.GetType() == typeof(Dog));    // true

is

The is operator checks whether a value is compatible with a given type.

if (obj is int)
{
    // obj can be treated as int
}

This includes:

  • exact type matches
  • derived types
  • implemented interfaces
  • pattern matching forms

Example:

class Animal {}
class Dog : Animal {}

Animal a = new Dog();

Console.WriteLine(a is Animal); // true
Console.WriteLine(a is Dog);    // true

This is why is is often the best choice when your goal is: "Can I safely use this as that type?"

Why this matters

In real programs, type checks affect:

  • inheritance behavior
  • interface handling
  • null safety
  • readability
  • correctness of branching logic

Choosing the wrong form can introduce subtle bugs. For example, using GetType() == typeof(BaseClass) can reject derived objects even when they should be accepted.

Mental Model

Think of type checking like checking a person's role in a company.

  • typeof(Employee) is like looking up the job description document for Employee.
  • person.GetType() is like asking: "What is this person's exact job title right now?"
  • person is Employee is like asking: "Does this person count as an employee for this rule?"

If someone is a Manager, then:

  • their exact type is Manager
  • they are also an Employee

So:

  • GetType() == typeof(Employee) would be false
  • is Employee would be true

That is the key difference: exact identity versus type compatibility.

Take Quiz

Syntax and Examples

Core syntax

// 1. Get Type metadata from a known type name
Type t1 = typeof(int);

// 2. Get exact runtime type from an object instance
Type t2 = obj.GetType();

// 3. Check whether an object is compatible with a type
if (obj is int)
{
    // use it as an int
}

Example: exact type vs compatible type

using System;

class Animal {}
class Dog : Animal {}

class Program
{
    static void Main()
    {
        Animal pet = new Dog();

        Console.WriteLine(pet.GetType() == typeof(Animal)); // false
        Console.WriteLine(pet.GetType() == typeof(Dog));    // true

        Console.WriteLine(pet is Animal); // true
        Console.WriteLine(pet is Dog);    // true
    }
}

Explanation

  • pet.GetType() returns Dog, because the object created at runtime is a Dog
  • pet is Animal is true because a Dog can be used anywhere an Animal is expected

Example: safer modern is pattern

object value = 42;

if (value is int number)
{
    Console.WriteLine(number + 10);
}

Why this is useful

This checks the type and gives you a strongly typed variable number in one step.

Example: comparing against a known type

Type targetType = typeof(string);
Console.WriteLine(targetType.Name); // String

This is a common use of typeof in reflection or metadata-based code.

Step by Step Execution

Consider this example:

using System;

class Animal {}
class Dog : Animal {}

class Program
{
    static void Main()
    {
        Animal pet = new Dog();

        Console.WriteLine(pet.GetType() == typeof(Animal));
        Console.WriteLine(pet.GetType() == typeof(Dog));
        Console.WriteLine(pet is Animal);
        Console.WriteLine(pet is Dog);
    }
}

Step by step

1. Animal pet = new Dog();

  • The variable type is Animal
  • The actual object created is Dog

This distinction is important:

  • reference type: Animal
  • runtime type: Dog

2. pet.GetType() == typeof(Animal)

  • pet.GetType() returns the exact runtime type: Dog
  • typeof(Animal) is the Type object for Animal
  • Dog is not exactly Animal
  • result: false

3. pet.GetType() == typeof(Dog)

  • pet.GetType() is still Dog
  • typeof(Dog) is also Dog
  • exact match
  • result: true

4. pet is Animal

  • A Dog inherits from Animal
  • therefore a Dog can be treated as an Animal
  • result: true

5. pet is Dog

  • The object is actually a Dog
  • result: true

Output

False
True
True
True

This example shows the central rule:

  • GetType() == typeof(T) checks exact runtime type
  • is T checks whether the object is assignable to T

Real World Use Cases

When to use is

Use is when your code needs to know whether a value can be used as a certain type.

Examples

  • handling different request types in an API
  • checking whether an exception is a specific exception type or derived from it
  • working with interface-based designs
  • validating deserialized objects
if (service is IDisposable disposable)
{
    disposable.Dispose();
}

When to use GetType()

Use GetType() when the exact runtime type matters.

Examples

  • serialization frameworks
  • logging exact object types
  • strict type-based branching
  • debugging tools
Console.WriteLine(obj.GetType().FullName);

When to use typeof

Use typeof when you need a Type object for a known type name.

Examples

  • reflection
  • attribute lookup
  • dependency injection registration
  • generic metadata handling
Type controllerType = typeof(HomeController);

Example from validation logic

if (input is string text)
{
    Console.WriteLine(text.Trim());
}

This is clearer than checking exact type unless you specifically want to reject derived or compatible forms.

Take Quiz

Real Codebase Usage

In real projects, developers usually choose based on intent.

Common pattern: use is for branching logic

if (command is CreateUserCommand createUser)
{
    HandleCreateUser(createUser);
    return;
}

This is common because it is:

  • readable
  • safe
  • concise
  • inheritance-friendly

Common pattern: use typeof in framework or infrastructure code

var handlers = new Dictionary<Type, object>();
handlers[typeof(string)] = new StringHandler();
handlers[typeof(int)] = new IntHandler();

This appears in:

  • dependency injection
  • event handler registration
  • serializers
  • mappers

Common pattern: use GetType() for exact matching

if (obj != null && obj.GetType() == typeof(SpecialCaseMessage))
{
    // Only this exact type, not subclasses
}

This is less common in business logic, but useful when exact type identity matters.

Guard clause example

if (value is not string text)
{
    return;
}

Console.WriteLine(text.Length);

This is a clean modern C# pattern for validation and early return.

Error handling example

catch (Exception ex) when (ex is InvalidOperationException)
{
    Console.WriteLine("Invalid operation");
}

In many codebases, is is preferred in conditional logic because it communicates meaning more directly than manual type comparisons.

Common Mistakes

1. Assuming GetType() == typeof(BaseType) matches derived classes

Broken example:

class Animal {}
class Dog : Animal {}

Animal pet = new Dog();

if (pet.GetType() == typeof(Animal))
{
    Console.WriteLine("This will not run");
}

Why it fails

GetType() returns Dog, not Animal.

Fix

Use is if you want compatibility:

if (pet is Animal)
{
    Console.WriteLine("This runs");
}

2. Calling GetType() on null

Broken example:

object obj = null;

if (obj.GetType() == typeof(string))
{
    Console.WriteLine("string");
}

Problem

This throws a NullReferenceException.

Fix

Check for null first, or use is:

if (obj is string)
{
    Console.WriteLine("string");
}

obj is string safely returns false when obj is null.

3. Using typeof as if it inspects an object

Broken idea:

// typeof does not read the runtime type of obj
Type t = typeof(string);

Clarification

typeof(string) always means the Type for string. It does not look at any variable's current value.

4. Choosing exact type comparison when polymorphism is intended

If your design uses base classes or interfaces, exact type checks can make the code too rigid.

Broken example:

if (payment.GetType() == typeof(IPaymentMethod))
{
    // not meaningful for interface compatibility checks
}

Better

if (payment is IPaymentMethod method)
{
    method.Process();
}

5. Forgetting that is can also declare a variable

Many beginners write:

if (obj is string)
{
    string text = (string)obj;
    Console.WriteLine(text.Length);
}

This works, but modern C# is cleaner:

if (obj is string text)
{
    Console.WriteLine(text.Length);
}

Comparisons

ApproachWhat it checksWorks with inheritance/interfaces?Null-safe?Common use
typeof(T)Gets the Type object for a known typeNot a runtime compatibility checkYesReflection, metadata, registration
obj.GetType()Object's exact runtime typeNo, not when compared for equalityNoExact type matching, diagnostics
obj is TWhether object is compatible with TYesYesBranching, validation, pattern matching

GetType() == typeof(T) vs is T

ExpressionMeaning
obj.GetType() == typeof(T)The object is exactly T
obj is TThe object is T, or a subtype, or implements T

Which one should you prefer?

Prefer is when:

  • you want to know if an object can be used as a type
  • inheritance or interfaces should count
  • you want readable branching logic

Prefer GetType() when:

  • only the exact runtime type should match
  • derived types must be excluded

Prefer typeof when:

  • you need a Type instance from a known type name
  • you are doing reflection or metadata work
Take Quiz

Cheat Sheet

typeof(int)              // Type metadata for int
obj.GetType()            // Exact runtime type of obj
obj is int               // True if obj can be treated as int
obj is int number        // Type check + typed variable
obj is not null          // Null check using pattern syntax

Quick rules

  • Use typeof(T) for known type metadata
  • Use obj.GetType() for exact runtime type
  • Use obj is T for compatibility checks
  • GetType() throws if obj is null
  • is returns false for null
  • GetType() == typeof(BaseType) does not match subclasses
  • is BaseType does match subclasses

Best default choice

If you are writing normal conditional logic, is is usually the best default because it is readable and works well with inheritance.

Common patterns

if (obj is string text)
{
    Console.WriteLine(text);
}

if (obj != null && obj.GetType() == typeof(MyType))
{
    Console.WriteLine("Exact match");
}

Type t = typeof(MyType);

FAQ

Is is faster than GetType() in C#?

In most application code, performance differences are usually not the deciding factor. Choose based on correctness and clarity first.

Does is check derived classes in C#?

Yes. is returns true for compatible derived classes and implemented interfaces.

Does GetType() return the declared variable type?

No. It returns the object's actual runtime type.

Can GetType() be called on null?

No. Calling GetType() on null throws a NullReferenceException.

When should I use typeof in C#?

Use typeof when you need the Type object for a known type, especially for reflection or metadata-based code.

Are obj is T and obj.GetType() == typeof(T) the same?

No. is checks compatibility, while GetType() == typeof(T) checks exact type equality.

Which is most readable in everyday code?

Usually is, especially with pattern matching like if (obj is MyType value).

Should I avoid exact type checks?

Not always. Use exact checks when your logic truly depends on the object being that specific runtime type and not a subtype.

Take Quiz

Mini Project

Description

Build a small console program that classifies objects using typeof, GetType(), and is. This project helps you see the difference between exact type checks and compatibility checks in a practical way, especially with inheritance and interfaces.

Goal

Create a C# console app that prints how different type-checking approaches behave for strings, integers, derived classes, interfaces, and null values.

Requirements

  • Create at least one base class and one derived class.
  • Create at least one interface implemented by a class.
  • Test several values, including null, a primitive value, and an object stored in a base-class variable.
  • For each value, show the result of is and, when safe, GetType() == typeof(...).
  • Include at least one example using typeof to print type metadata.
Take Quiz

Keep learning

Related questions

View more