Back to Blog
C#

C# Reflection GetType: Inspect Runtime Types

c# reflection gettype: Learn how to use C# reflection GetType to inspect runtime types, compare with typeof, and avoid common pitfalls in real-world code.

C# ReflectionGetType MethodType ObjectRuntime Type Inspectiontypeof Operator
A magnifying glass inspecting a C# object to reveal its runtime Type, symbolizing reflection GetType.

When you call GetType() on an object in C#, you get a System.Type instance that describes the object's exact runtime type. This is the foundation of reflection: it lets you discover type information that is not known at compile time. The c# reflection gettype pattern appears in serializers, ORMs, dependency injection containers, and any code that must handle objects polymorphically.

Consider a simple example:

object value = "hello"; Type type = value.GetType(); Console.WriteLine(type.FullName); // System.String

The variable value is declared as object, but at runtime it holds a string. GetType() returns the actual type of the object, not the type of the variable. This is the core behavior that distinguishes GetType() from compile-time type checks.

What GetType Returns and When to Use It

GetType() is a method on System.Object, so it is available on every object in .NET. It returns a Type object that contains metadata about the type: its name, namespace, base type, implemented interfaces, properties, methods, and more. The returned Type instance is the entry point for all reflection operations.

Use GetType() when you need to make decisions based on the actual runtime type of an object. For example, a logging library might inspect the type of an exception to format a message differently:

void LogException(Exception ex) { Type exType = ex.GetType(); Console.WriteLine($"Exception type: {exType.Name}"); Console.WriteLine($"Message: {ex.Message}"); }

Here, ex is an Exception, but the actual type could be InvalidOperationException, ArgumentException, or any custom subclass. GetType() reveals the concrete type, which is often more useful for diagnostics than the base type.

GetType vs typeof: Choosing the Right API

typeof is a compile-time operator that returns the Type for a known type name. GetType() is a runtime method that returns the Type for an object's actual type. The distinction is critical.

object obj = "text"; Type fromGetType = obj.GetType(); // System.String Type fromTypeof = typeof(object); // System.Object

typeof(object) always returns System.Object because the type is known at compile time. GetType() returns System.String because that is what the object actually is at runtime.

Use typeof when you know the type at compile time and want a Type reference for comparison or reflection. Use GetType() when you only have an object reference and need its runtime type.

A common pattern is comparing GetType() to a typeof result:

if (obj.GetType() == typeof(string)) { // obj is exactly a string, not a subclass }

This exact comparison is different from is or as, which also consider inheritance. GetType() == typeof(...) checks for the exact type, not assignability.

Using GetType with Inheritance and Interfaces

GetType() returns the most derived type in the inheritance chain. If a class Derived inherits from Base, and you have a Base reference pointing to a Derived instance, GetType() returns Derived.

class Base { } class Derived : Base { } Base b = new Derived(); Console.WriteLine(b.GetType().Name); // Derived

This behavior is useful when you need to handle objects differently based on their concrete type, but it also means you cannot use GetType() to check whether an object implements an interface. For that, use is or Type.IsAssignableFrom.

If you need to test interface implementation, use the Type object's methods:

if (obj.GetType().GetInterface("IDisposable") != null) { // obj implements IDisposable }

But in most scenarios, the is operator is simpler and faster:

if (obj is IDisposable) { // obj implements IDisposable }

Prefer is and as for type checks that involve inheritance or interfaces. Use GetType() only when you need the exact runtime type, not just assignability.

Performance Considerations for Frequent GetType Calls

GetType() is not free. It involves a virtual method call and metadata lookup. In most applications, the cost is negligible, but in tight loops or high-frequency code paths, it can add measurable overhead.

The .NET runtime caches Type objects, so calling GetType() on the same type repeatedly does not allocate new Type instances. The overhead is mainly the method dispatch and the metadata resolution on first access. Still, if you find yourself calling GetType() in a loop over thousands of items, consider caching the Type result when the object type does not change.

Type cachedType = null; foreach (var item in items) { Type current = item.GetType(); if (current != cachedType) { cachedType = current; // Do expensive reflection setup only when type changes } // Use cachedType for the rest of the loop }

This pattern is common in serializers that need to build a property map per type. The first time a type is seen, you build the map; subsequent objects of the same type reuse it.

Another performance concern is the use of GetType() combined with string comparisons. For example, checking obj.GetType().Name == "SomeType" is fragile and slower than a direct type comparison. Use typeof or is instead whenever possible.

Common Mistakes with GetType and Null References

A frequent mistake is calling GetType() on a null reference. GetType() is an instance method, so it throws a NullReferenceException if the object is null.

object obj = null; Type type = obj.GetType(); // NullReferenceException

Always check for null before calling GetType() if the object might be null. This is especially important when dealing with untyped inputs from external sources.

Type type = obj?.GetType(); // type is null if obj is null

The null-conditional operator ?. returns null if obj is null, avoiding the exception. But then you need to handle the null Type appropriately in your logic.

Another mistake is assuming GetType() returns the same type as typeof for a variable of a base type. As shown earlier, it returns the runtime type, which can be a derived type. This is not a mistake per se, but it can lead to unexpected behavior if you expect the base type.

Practical Example: Building a Simple Type-Based Dispatcher

A common use of c# reflection gettype is to dispatch an object to a handler based on its exact runtime type. Here is a minimal example that processes different shapes without using pattern matching:

class Shape { } class Circle : Shape { public double Radius; } class Rectangle : Shape { public double Width, Height; } void ProcessShape(Shape shape) { Type type = shape.GetType(); if (type == typeof(Circle)) { Circle circle = (Circle)shape; Console.WriteLine($"Circle area: {Math.PI * circle.Radius * circle.Radius}"); } else if (type == typeof(Rectangle)) { Rectangle rect = (Rectangle)shape; Console.WriteLine($"Rectangle area: {rect.Width * rect.Height}"); } else { Console.WriteLine("Unknown shape"); } }

This works, but in modern C# you would typically use pattern matching (shape is Circle circle) which is more readable and safer. The reflection approach is still relevant when the set of types is dynamic, such as when loading plugins or handling data from a database where the type is not known at compile time.

In such dynamic scenarios, you might use GetType() to look up a handler in a dictionary:

Dictionary<Type, Action<object>> handlers = new Dictionary<Type, Action<object>> { { typeof(Circle), obj => HandleCircle((Circle)obj) }, { typeof(Rectangle), obj => HandleRectangle((Rectangle)obj) } }; void Dispatch(object obj) { Type type = obj.GetType(); if (handlers.TryGetValue(type, out var handler)) { handler(obj); } else { // Handle unknown types } }

This pattern avoids a long chain of if-else and makes it easy to register new handlers at runtime. The dictionary lookup is fast, but you must ensure the type keys are exact matches.

Compatibility and Versioning Notes

GetType() has been part of .NET since the beginning and is available in all modern versions, including .NET Framework, .NET Core, and .NET 5+. There is no difference in behavior across these platforms for basic type inspection.

One subtle point is that GetType() on a nullable value type returns the underlying value type, not the Nullable<T> wrapper. For example:

int? number = 42; Type type = number.GetType(); // System.Int32, not Nullable<Int32>

This is because boxing a nullable value type with a value boxes the underlying type. If you need to detect whether the original type was nullable, you must use typeof(int?) or inspect the type with Nullable.GetUnderlyingType.

Another version-related consideration is that reflection metadata can be trimmed in AOT (Ahead-of-Time) compilation scenarios, such as .NET Native or Blazor WebAssembly. If you rely on GetType() to discover types that are not directly referenced, the runtime might not have the metadata available. In such cases, you may need to use a DynamicDependency attribute or a trimming-safe approach. This is an advanced scenario, but it is worth knowing when deploying to platforms that use trimming.

Finally, be aware that GetType() returns a Type object that is not guaranteed to be the same instance across calls for the same type, although in practice the runtime caches them. Do not rely on reference equality between Type objects from different calls; use the == operator, which is overloaded for Type to compare by type identity, not reference.

c# reflection gettype: Practical Usage and Code Examples | RYUSLOG DEV