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 withCustomer. 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.
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:
Basket<decimal>creates a closed generic type whereTmeansdecimal.Itemsis therefore aList<decimal>.PrintItemType()evaluatestypeof(T).- Since
Tisdecimal, the result is aTypeobject representingSystem.Decimal. .NameproducesDecimal.- 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}");
}
}
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
| Approach | What it tells you | Requires an object? | Works for an empty list? |
|---|---|---|---|
typeof(T) | The generic type argument | No | Yes |
value.GetType() | The runtime type of one object | Yes | No |
typeof(List<T>) | The collection type, such as List<string> | No | Yes |
typeof(List<T>).GetGenericArguments()[0] | The element type from the collection type | No | Yes |
PropertyInfo.PropertyType | The declared type of a reflected property | No | Yes |
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.
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 constructingTand without list items.GetType()is an instance method, so it needs a non-null object.typeof(T)returnsSystem.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().
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
Typewithout 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.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.