Unbound Generic Types in `nameof` — C# 14 Makes Reflection Cleaner
Unbound Generic Types in nameof — C# 14 Makes Reflection Cleaner C# 14 continues its push for clarity and power with a subtle yet highly useful enhancement: the ability to use unbound generic types as arguments to the nameof operator. This change simplifies metadata-driven code, such as logging, code generation, and diagnostic tooling — making nameof even more expressive and refactor-safe. In this post, you’ll learn: What unbound generic types are What changed in C# 14 How to use nameof with generic type definitions Common pitfalls Best practices and real-world use cases What Are Unbound Generic Types? An unbound generic type is a generic type without any concrete type arguments. For example: typeof(List); // ✅ Unbound typeof(List); // ❌ Closed Unbound generics refer to the generic type definition itself, like List, Dictionary, Task. What’s New in C# 14? Before C# 14: nameof(List) // ✅ Outputs: "List" nameof(List) // ❌ Compile-time error Now in C# 14: nameof(List) // ✅ Outputs: "List" nameof(Dictionary) // ✅ Outputs: "Dictionary" This is especially powerful when working with generic metadata, source generators, or tooling that doesn't depend on specific type arguments. Practical Example — Diagnostic Logging void LogGenericType() { Console.WriteLine($"Executing for {nameof(List)}"); } LogGenericType(); // Output: Executing for List You now avoid hardcoding "List" or using reflection just to get the type name. Complex Case — Generic Type Arity Console.WriteLine(nameof(Dictionary)); // Outputs: "Dictionary" Console.WriteLine(nameof(Func)); // Outputs: "Func" Console.WriteLine(nameof(Func)); // Outputs: "Func" nameof returns the simple type name, not the generic arity (number of arguments). Pitfalls to Watch Out For Gotcha Explanation nameof(List) still must compile You can’t use malformed generics Does not return "List" Only the type name (not signature) Compiler-based, not reflection Doesn’t access runtime type info Use Cases in Real Projects Source Generators — emit code with clean, type-safe identifiers Testing frameworks — name test cases or assertions from types Structured logging — avoid hardcoding type names in log output Serialization diagnostics — identify generic types in error messages Custom analyzers — make Roslyn-based tooling more expressive Best Practices Use nameof(SomeGeneric) instead of hardcoding strings like "SomeGeneric" Keep nameof usage for compile-time diagnostics, not runtime reflection Combine with typeof(T) for dynamic metadata, if needed Learn More Feature Proposal on GitHub C# Language Reference - nameof Operator Type Names and Generic Type Definitions Final Thoughts This small change in C# 14 brings big wins in type safety, diagnostics, and clean tooling. If you're building advanced .NET systems, this is one more way nameof helps future-proof your code and reduce errors. Say goodbye to magic strings — let the compiler do the naming. Written by: [Cristian Sifuentes] – .NET Architect | Code Generator | Clean Code Advocate Have ideas for using this in analyzers or toolkits? Drop them in the comments.

Unbound Generic Types in nameof
— C# 14 Makes Reflection Cleaner
C# 14 continues its push for clarity and power with a subtle yet highly useful enhancement: the ability to use unbound generic types as arguments to the nameof
operator.
This change simplifies metadata-driven code, such as logging, code generation, and diagnostic tooling — making nameof
even more expressive and refactor-safe.
In this post, you’ll learn:
- What unbound generic types are
- What changed in C# 14
- How to use
nameof
with generic type definitions - Common pitfalls
- Best practices and real-world use cases
What Are Unbound Generic Types?
An unbound generic type is a generic type without any concrete type arguments.
For example:
typeof(List<>); // ✅ Unbound
typeof(List<int>); // ❌ Closed
Unbound generics refer to the generic type definition itself, like List<>
, Dictionary<,>
, Task<>
.
What’s New in C# 14?
Before C# 14:
nameof(List<int>) // ✅ Outputs: "List"
nameof(List<>) // ❌ Compile-time error
Now in C# 14:
nameof(List<>) // ✅ Outputs: "List"
nameof(Dictionary<,>) // ✅ Outputs: "Dictionary"
This is especially powerful when working with generic metadata, source generators, or tooling that doesn't depend on specific type arguments.
Practical Example — Diagnostic Logging
void LogGenericType<T>()
{
Console.WriteLine($"Executing for {nameof(List<>)}<{typeof(T).Name}>");
}
LogGenericType<string>();
// Output: Executing for List
You now avoid hardcoding "List"
or using reflection just to get the type name.
Complex Case — Generic Type Arity
Console.WriteLine(nameof(Dictionary<,>)); // Outputs: "Dictionary"
Console.WriteLine(nameof(Func<int, string>)); // Outputs: "Func"
Console.WriteLine(nameof(Func<,>)); // Outputs: "Func"
nameof
returns the simple type name, not the generic arity (number of arguments).
Pitfalls to Watch Out For
Gotcha | Explanation |
---|---|
nameof(List<>) still must compile |
You can’t use malformed generics |
Does not return "List<>" | Only the type name (not signature) |
Compiler-based, not reflection | Doesn’t access runtime type info |
Use Cases in Real Projects
- Source Generators — emit code with clean, type-safe identifiers
- Testing frameworks — name test cases or assertions from types
- Structured logging — avoid hardcoding type names in log output
- Serialization diagnostics — identify generic types in error messages
- Custom analyzers — make Roslyn-based tooling more expressive
Best Practices
- Use
nameof(SomeGeneric<>)
instead of hardcoding strings like"SomeGeneric"
- Keep
nameof
usage for compile-time diagnostics, not runtime reflection - Combine with
typeof(T)
for dynamic metadata, if needed
Learn More
- Feature Proposal on GitHub
- C# Language Reference - nameof Operator
- Type Names and Generic Type Definitions
Final Thoughts
This small change in C# 14 brings big wins in type safety, diagnostics, and clean tooling. If you're building advanced .NET systems, this is one more way nameof
helps future-proof your code and reduce errors.
Say goodbye to magic strings — let the compiler do the naming.
Written by: [Cristian Sifuentes] – .NET Architect | Code Generator | Clean Code Advocate
Have ideas for using this in analyzers or toolkits? Drop them in the comments.