Wednesday, May 7, 2025

Unbound Generic Types in `nameof` — C# 14 Makes Reflection Cleaner

Programming LanguageUnbound 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<int>);       // ❌ Closed
Enter fullscreen mode

Exit fullscreen mode

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
Enter fullscreen mode

Exit fullscreen mode

Now in C# 14:

nameof(List<>)          // ✅ Outputs: "List"
nameof(Dictionary<,>)   // ✅ Outputs: "Dictionary"
Enter fullscreen mode

Exit fullscreen mode

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>
Enter fullscreen mode

Exit fullscreen mode

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"
Enter fullscreen mode

Exit fullscreen mode

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.

Check out our other content

Check out other tags:

Most Popular Articles