If you have ever stared at a line that mixes typeof, GetType and IsAssignableFrom and thought your code was speaking in riddles, you are not alone. I once spent a late night chasing a bug where two classes looked identical but behaved like siblings arguing over who touched the thermostat. The fix landed when I stopped treating type checks like a guessing game and learned what each tool really asks the runtime.

Meet the cast of characters

Think of typeof as the cast list, GetType as the actor on stage, and IsAssignableFrom as the casting rules. Each plays a different role, and mixing them up can turn your tidy drama into improv night.

typeof gets the type by name

typeof is a compile time expression that produces a System.Type object for a known type. No instance needed, no object inspected.

using System;
Type nameTag = typeof(string);
Console.WriteLine(nameTag.FullName);

That value is a Type describing string itself. It is a constant in your compiled code, which makes typeof fast and allocation free at runtime.

You can also point typeof at generics.

using System;
using System.Collections.Generic;
Console.WriteLine(typeof(List<>).IsGenericTypeDefinition);
Console.WriteLine(typeof(List<string>).GetGenericTypeDefinition() == typeof(List<>));

GetType asks the instance who it really is

object.GetType looks at the actual object and returns its runtime type. This is great when the variable is typed as a base class or interface but holds a derived instance.

object dwight = "Bears. Beets. Types.";
Console.WriteLine(dwight.GetType() == typeof(string));

But, there are two important notes you need to remember when it comes to GetType:

  1. GetType throws if the instance is null. Check for null first.
  2. GetType returns the precise runtime type, which might be a proxy when frameworks like EF Core create dynamic types.
object jim = null;
Console.WriteLine(jim is null);
// jim.GetType(); // would throw

IsAssignableFrom answers inheritance questions

Type.IsAssignableFrom is your yes or no for the question can I store an instance of that type in a variable of this type. It considers inheritance and interfaces.

using System;
class BaseEntity {}
class Order : BaseEntity {}
Console.WriteLine(typeof(BaseEntity).IsAssignableFrom(typeof(Order)));

Interface checks work the same way.

using System;
interface ISoftDelete {}
class User : ISoftDelete {}
Console.WriteLine(typeof(ISoftDelete).IsAssignableFrom(typeof(User)));

Newer runtimes also include Type.IsAssignableTo, which reads in the opposite direction. Pick the one that reads best for your team and stick with it.

A tiny EF Core style filter

Infrastructure code often needs to apply behaviors only to certain entities. Soft delete and audit fields are classic examples. You will see checks that look like this when scanning types.

using System;
using System.Reflection;
interface IJedi {}
class Luke : IJedi {}
foreach (var t in Assembly.GetExecutingAssembly().GetTypes())
if (typeof(IJedi).IsAssignableFrom(t) && t.IsClass)
Console.WriteLine(t.Name);

Here we grab types from the current assembly, keep only classes that implement IJedi, then act on those. Swap IJedi for your base entity or capability interface and you have a practical convention hook.

typeof vs GetType in practice

Use typeof when the type is known at compile time and you do not have or need an instance. Use GetType when you have an object and care about the exact runtime type.

using System;
object obiWan = 42;
Console.WriteLine(obiWan.GetType() == typeof(int));
Console.WriteLine(typeof(object) == obiWan.GetType());

The first comparison is true, because the instance is an int. The second is false, because an int is not exactly object.

Pattern matching pairs well with type checks

C# pattern matching gives you readable runtime checks without reaching for reflection until you must.

using System;
object frodo = "Second breakfast";
if (frodo is string s)
Console.WriteLine(s.ToUpperInvariant());

Reach for typeof and IsAssignableFrom when you are dealing with types, not values, such as scanning assemblies, building converters or authoring framework code.

Common gotchas and tips

  • typeof works on types, not values. If you only have a value, you cannot pass it to typeof.
  • object.GetType requires a non null instance. Guard your calls.
  • Proxies and dynamic types mean GetType may return a generated type. For comparisons in those scenarios, prefer assignability checks over exact equality.
  • Cache Type objects if you need them repeatedly in hot paths. typeof is cheap, but repeated reflection lookups and attribute scans are not.
  • Open generic types like List<> are not the same as List<string>. Compare using GetGenericTypeDefinition when needed.

The short version

  • typeof gives you the type you name.
  • GetType gives you the type the object is right now.
  • IsAssignableFrom tells you whether one type can stand in for another.

Once you know which question you are asking, the right API becomes obvious. Your future self, and your logs, will thank you when your type checks read like plain English rather than a riddle from a mysterious wizard.