Stop Guessing Types in C#: typeof, GetType and IsAssignableFrom Explained
Let's break down the real jobs of typeof, object.GetType and Type.IsAssignableFrom with small, runnable examples and a few nerdy references for fun. You will learn when to reach for each API, why EF Core and infrastructure code lean on assignability checks, and how pattern matching fits into the story. We will cover null pitfalls, open generic comparisons and proxy types. By the end, your type checks will read clearly and behave predictably.
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:
GetTypethrows if the instance is null. Check for null first.GetTypereturns 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 throwIsAssignableFrom 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
typeofworks on types, not values. If you only have a value, you cannot pass it totypeof.object.GetTyperequires a non null instance. Guard your calls.- Proxies and dynamic types mean
GetTypemay 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.
typeofis cheap, but repeated reflection lookups and attribute scans are not. - Open generic types like
List<>are not the same asList<string>. Compare using GetGenericTypeDefinition when needed.
The short version
typeofgives you the type you name.GetTypegives you the type the object is right now.IsAssignableFromtells 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.