Question

Get a Generic Type Parameter at Runtime in C# with typeof(T)

csharp.netgenerics

Question

Given a generic class or generic method in C#, how can you determine the runtime Type represented by the type parameter T?

public class Foo<T>
{
    public List<T> Bar { get; set; } = new();

    public void Baz()
    {
        // How can I get the Type represented by T?
    }
}

For example, if Foo<MyTypeObject1> is instantiated, Bar is a List<MyTypeObject1>. The same question applies to a generic method:

public class Foo
{
    public void Bar<T>()
    {
        var baz = new List<T>();

        // How can I get the Type represented by T?
    }
}

How can the type stored in Bar or baz be determined when the list may be empty, so Bar[0].GetType() cannot be used?

Short Answer

Use typeof(T) inside a generic class or generic method to get a System.Type object for the actual type argument. This works even when a List<T> is empty because it uses generic type metadata rather than inspecting an object instance.

Concept

C# generics use a type parameter such as T as a placeholder. When code uses a generic type or method, it supplies a type argument.

Foo<string> strings = new();

Here, T is string within that particular Foo<T> instance. Inside a generic context, write typeof(T) to retrieve the corresponding System.Type:

Type itemType = typeof(T);

This is different from calling GetType() on an object:

  • typeof(T) asks: “What type was supplied for this generic parameter?”
  • value.GetType() asks: “What is the actual runtime type of this particular object?”

Because typeof(T) does not need an object, it is safe and useful for empty collections, validation, logging, serialization, reflection, and type-based decisions.

Mental Model

Think of T as a labeled blank on a reusable form.

  • The generic class or method is the blank form: Foo<T>.
  • Creating Foo<Customer> fills in the blank with Customer.
  • typeof(T) reads what was written in that blank: typeof(Customer).

A List<T> is only a container built using that information. You do not need to open the container or inspect an item to know its declared item type.

Take Quiz

Syntax and Examples

The core syntax is:

Type type = typeof(T);

Generic class example

using System;
using System.Collections.Generic;

public class Foo<T>
{
    public List<T> Bar { get; set; } = new();

    public Type GetItemType()
    {
        return typeof(T);
    }
}

Foo<int> numbers = new();
Console.WriteLine(numbers.GetItemType()); // System.Int32

Foo<string> words = new();
Console.WriteLine(words.GetItemType());   // System.String

GetItemType returns the type argument supplied when each Foo<T> object was created. It works even though both lists are empty.

Generic method example

using System;
using System.Collections.Generic;

public class Foo
{
    public void Bar<T>()
    {
        var baz = new List<T>();
        Type itemType = typeof(T);

        Console.WriteLine(itemType);
    }
}

var foo = new Foo();
foo.Bar<DateTime>(); // System.DateTime

The method call supplies DateTime, so typeof(T) is typeof(DateTime) during that invocation.

Step by Step Execution

Consider this code:

using System;
using System.Collections.Generic;

public class Basket<T>
{
    public List<T> Items { get; } = new();

    public void PrintItemType()
    {
        Console.WriteLine(typeof(T).Name);
    }
}

var basket = new Basket<decimal>();
basket.PrintItemType();

Execution steps:

  1. Basket<decimal> creates a closed generic type where T means decimal.
  2. Items is therefore a List<decimal>.
  3. PrintItemType() evaluates typeof(T).
  4. Since T is decimal, the result is a Type object representing System.Decimal.
  5. .Name produces Decimal.
  6. The program prints Decimal.

No list item is created or inspected at any point.

Real World Use Cases

typeof(T) is useful whenever code needs the declared generic type without requiring data.

  • Serialization: select a serializer or type metadata for T.
  • Validation: reject unsupported type arguments, such as allowing only numeric types in a calculation component.
  • Logging: include the processed type in diagnostic messages.
  • Dependency injection and factories: request or create a service based on the generic type.
  • Data import/export: choose a file mapping based on the item type.
  • Empty collections: identify the intended element type before any elements have been added.

Example: logging the type processed by a repository.

public class Repository<T>
{
    public void LoadAll()
    {
        Console.WriteLine($"Loading records of type {typeof(T).Name}");
    }
}
Take Quiz

Real Codebase Usage

In production code, typeof(T) is often combined with clear type checks, generic constraints, and metadata APIs.

Type-based guard clause

public static void EnsureSupported<T>()
{
    if (typeof(T) != typeof(string) && typeof(T) != typeof(int))
    {
        throw new NotSupportedException(
            $"Type {typeof(T).Name} is not supported.");
    }
}

Use this pattern when only a small, explicit set of types is valid.

Generic cache keyed by type

public static class TypeCache<T>
{
    public static readonly string DisplayName = typeof(T).Name;
}

Each closed generic type gets its own static members, so TypeCache<Customer> and TypeCache<Order> maintain separate values.

Reflection on a collection property

If code has a PropertyInfo rather than access to T, inspect the property's declared type:

PropertyInfo property = typeof(Foo<string>).GetProperty(nameof(Foo<string>.Bar))!;
Type listType = property.PropertyType;                // List<string>
Type itemType = listType.GetGenericArguments()[0];    // string

Prefer typeof(T) when you are already inside the generic class or method. Use reflection when examining a type or member dynamically from outside it.

Common Mistakes

Using GetType() on an item in an empty list

This fails because index 0 does not exist when the list is empty:

// Broken when Bar is empty.
Type itemType = Bar[0].GetType();

Use typeof(T) inside Foo<T> instead:

Type itemType = typeof(T);

Confusing declared type and runtime object type

List<object> values = new() { "hello" };

Console.WriteLine(typeof(object));       // System.Object
Console.WriteLine(values[0].GetType());  // System.String

typeof(T) gives the declared generic argument. GetType() gives an individual object's runtime type, which can be a derived type.

Trying to use typeof with a variable

string name = "Ada";

// Invalid C#:
// Type type = typeof(name);

typeof requires a type name, not a variable. Use name.GetType() when an object instance exists, or typeof(T) for a generic parameter.

Assuming every generic type argument is a concrete class

T can be a value type, interface, array, nullable type, or another generic type:

Console.WriteLine(typeof(int));            // System.Int32
Console.WriteLine(typeof(IEnumerable<int>));
Console.WriteLine(typeof(List<string>));

Treat the returned value as a Type and avoid assumptions based only on its name.

Comparisons

ApproachWhat it tells youRequires an object?Works for an empty list?
typeof(T)The generic type argumentNoYes
value.GetType()The runtime type of one objectYesNo
typeof(List<T>)The collection type, such as List<string>NoYes
typeof(List<T>).GetGenericArguments()[0]The element type from the collection typeNoYes
PropertyInfo.PropertyTypeThe declared type of a reflected propertyNoYes

Use typeof(T) when code is already inside a generic class or method. Use GetType() when polymorphism means you specifically need the runtime type of an existing object.

Take Quiz

Cheat Sheet

// Inside a generic class or method:
Type type = typeof(T);

// Check for an exact type:
if (typeof(T) == typeof(string))
{
    // T is string
}

// Get a readable name:
string name = typeof(T).Name;

// Get the generic collection type:
Type listType = typeof(List<T>);

// Get T from List<T> metadata:
Type itemType = typeof(List<T>).GetGenericArguments()[0];

Key rules:

  • typeof(T) works without constructing T and without list items.
  • GetType() is an instance method, so it needs a non-null object.
  • typeof(T) returns System.Type.
  • A generic method can use typeof(T) just like a generic class can.
  • typeof(T) reports the declared type argument, not necessarily the runtime type of every object stored through a base-type or interface reference.

FAQ

How do I get the type of T in C#?

Use typeof(T) inside the generic class or generic method.

Type type = typeof(T);

Does typeof(T) work when List<T> is empty?

Yes. It reads generic type metadata and does not depend on collection contents.

Is typeof(T) the same as object.GetType()?

No. typeof(T) returns the declared generic type argument. GetType() returns the runtime type of a particular object instance.

How do I get the element type from List<T>?

Inside generic code, use typeof(T). If you only have the list type, use:

Type itemType = typeof(List<T>).GetGenericArguments()[0];

Can typeof(T) be used in a generic method?

Yes.

void PrintType<T>() => Console.WriteLine(typeof(T));

What does typeof(T).Name return?

It returns a short type name such as String, Int32, or Customer. The Type itself contains more complete metadata.

Can I create an instance from typeof(T)?

Sometimes, but not all types have a usable parameterless constructor. Prefer a where T : new() constraint when generic code must create new T().

Take Quiz

Mini Project

Description

Build a small generic inventory container that reports the type of items it is designed to store. This demonstrates that a collection's declared item type can be obtained before any item is added.

Goal

Create a generic inventory that prints its item type and item count, including when it is empty.

Requirements

  • Create a generic Inventory<T> class.
  • Store items in a List<T> property or field.
  • Add a method that returns the item Type without inspecting an item.
  • Add a method for adding an item.
  • Demonstrate the class with at least two different type arguments.
  • Show that an empty inventory still reports its item type.
Take Quiz

Keep learning

Related questions

View more