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<String>
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
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.