Porticle.Reflection.Extensions 1.0.0

dotnet add package Porticle.Reflection.Extensions --version 1.0.0
                    
NuGet\Install-Package Porticle.Reflection.Extensions -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Porticle.Reflection.Extensions" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Porticle.Reflection.Extensions" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Porticle.Reflection.Extensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Porticle.Reflection.Extensions --version 1.0.0
                    
#r "nuget: Porticle.Reflection.Extensions, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Porticle.Reflection.Extensions@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Porticle.Reflection.Extensions&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Porticle.Reflection.Extensions&version=1.0.0
                    
Install as a Cake Tool

Porticle.Reflection.Extensions

NuGet Version NuGet Downloads Build and Test Release codecov License

Overview

A .NET library that provides extension methods for converting .NET reflection types into human-readable type strings. This library handles complex scenarios including nullable reference types, nullable value types, generic types, arrays, and C# type aliases, making reflection output easier to read and understand.

For example, when you have a Propterty of type like IDictionary<MyClass?[][], Dictionary<object?,int?[,]?>>?, the library is able to convert it to a readable c# type name that looks mostly exact like the normal c# type name in most cases.

When you use .ToString() you will get

System.Collections.Generic.IDictionary`2[MyClass[][],System.Collections.Generic.Dictionary`2[System.Object,System.Nullable`1[System.Int32][,]]]

When you use this library you will get

IDictionary<MyClass?[][], Dictionary<object?,int?[,]?>>?

Key Features

The library automatically converts reflection types to readable strings:

  • Nullable reference types (string?, List<string>?)
  • Nullable value types (int?, DateTime?)
  • Generic types with proper formatting (List<T>, Dictionary<TKey, TValue>)
  • Arrays (single-dimensional [], jagged [][], and multi-dimensional [,])
  • C# type aliases (int, string, bool instead of Int32, String, Boolean)
  • Event handlers (EventHandler<T>, Action<T>, Func<T>)
  • Nested generic types (List<List<string?>>?)

Installation

Install via NuGet:

dotnet add package Porticle.Reflection.Extensions

Or via Package Manager Console:

Install-Package Porticle.Reflection.Extensions

Usage

Basic Type Conversion

using Porticle.Reflection.Extensions;

// Simple types
Type intType = typeof(int);
Console.WriteLine(intType.ToReadableTypeString()); // Output: int

Type stringType = typeof(string);
Console.WriteLine(stringType.ToReadableTypeString()); // Output: string

// Nullable value types
Type nullableInt = typeof(int?);
Console.WriteLine(nullableInt.ToReadableTypeString()); // Output: int?

// Generic types
Type listType = typeof(List<string>);
Console.WriteLine(listType.ToReadableTypeString()); // Output: List<string>

Type dictType = typeof(Dictionary<int, string>);
Console.WriteLine(dictType.ToReadableTypeString()); // Output: Dictionary<int, string>

Working with Properties (Nullable Reference Types)

public class User
{
    public string Name { get; set; } = "";
    public string? Email { get; set; }
    public List<string>? Tags { get; set; }
    public List<string?> Nicknames { get; set; } = new();
}

// Using PropertyInfo to get nullable reference type information
PropertyInfo nameProperty = typeof(User).GetProperty("Name")!;
Console.WriteLine(nameProperty.ToReadableTypeString()); // Output: string

PropertyInfo emailProperty = typeof(User).GetProperty("Email")!;
Console.WriteLine(emailProperty.ToReadableTypeString()); // Output: string?

PropertyInfo tagsProperty = typeof(User).GetProperty("Tags")!;
Console.WriteLine(tagsProperty.ToReadableTypeString()); // Output: List<string>?

PropertyInfo nicknamesProperty = typeof(User).GetProperty("Nicknames")!;
Console.WriteLine(nicknamesProperty.ToReadableTypeString()); // Output: List<string?>

Working with Method Parameters

public class DataService
{
    public void ProcessData(
        List<string> requiredItems,
        List<string>? optionalItems,
        List<string?> itemsWithNullableElements)
    {
    }
}

MethodInfo method = typeof(DataService).GetMethod("ProcessData")!;
ParameterInfo[] parameters = method.GetParameters();

Console.WriteLine(parameters[0].ToReadableTypeString()); // Output: List<string>
Console.WriteLine(parameters[1].ToReadableTypeString()); // Output: List<string>?
Console.WriteLine(parameters[2].ToReadableTypeString()); // Output: List<string?>

Working with Fields and Events

public class EventDemo
{
    public List<string>? Items;
    public event EventHandler<string>? DataReceived;
}

// Fields
FieldInfo field = typeof(EventDemo).GetField("Items")!;
Console.WriteLine(field.ToReadableTypeString()); // Output: List<string>?

// Events
EventInfo evt = typeof(EventDemo).GetEvent("DataReceived")!;
Console.WriteLine(evt.ToReadableTypeString()); // Output: EventHandler<string>?

Advanced Features

Full Type Names

Use useFullNames: true to include full namespaces:

Type listType = typeof(List<string>);
Console.WriteLine(listType.ToReadableTypeString(useFullNames: true));
// Output: System.Collections.Generic.List<System.String>

Native .NET Type Names

Use useInternalTypeNames: false to get .NET type names instead of C# aliases:

Type intType = typeof(int);
Console.WriteLine(intType.ToReadableTypeString(useInternalTypeNames: false));
// Output: Int32

Type stringType = typeof(string);
Console.WriteLine(stringType.ToReadableTypeString(useInternalTypeNames: false));
// Output: String

Arrays

// Single-dimensional array
Type intArray = typeof(int[]);
Console.WriteLine(intArray.ToReadableTypeString()); // Output: int[]

// Jagged array
Type jaggedArray = typeof(int[][]);
Console.WriteLine(jaggedArray.ToReadableTypeString()); // Output: int[][]

// Multi-dimensional array
Type multiArray = typeof(int[,]);
Console.WriteLine(multiArray.ToReadableTypeString()); // Output: int[,]

Limitations

Type Erasure

Due to .NET's type erasure for nullable reference types, typeof() cannot preserve nullability information for generic type arguments:

// ❌ This will NOT show the nullable string
Type listType = typeof(List<string?>);
Console.WriteLine(listType.ToReadableTypeString()); // Output: List<string> (nullability lost)

// ✅ Use PropertyInfo, ParameterInfo, FieldInfo, or EventInfo instead
public class Example
{
    public List<string?> Items { get; set; } = new();
}

PropertyInfo property = typeof(Example).GetProperty("Items")!;
Console.WriteLine(property.ToReadableTypeString()); // Output: List<string?> (correct)

Note: Nullable value types (like List<int?>) work correctly with typeof() because Nullable<T> is a real runtime type.

Target Frameworks

  • .NET 10.0
  • .NET 9.0
  • .NET 8.0

Performance

The library is optimized for performance with:

  • ThreadLocal NullabilityInfoContext: Reuses context instances per thread to avoid repeated allocations
  • Dictionary-based type aliases: O(1) lookup for C# type aliases instead of linear if-chains
  • StringBuilder for arrays: Efficient string building for complex array types
  • Minimal allocations: Recursive implementation minimizes unnecessary object creation

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

  1. Clone the repository
  2. Ensure you have .NET 8.0, 9.0, or 10.0 SDK installed
  3. Run dotnet restore in the Source directory
  4. Run dotnet build to build the project
  5. Run dotnet test to run all tests

Code Style

This project uses an .editorconfig file to maintain consistent code style. Please ensure your IDE respects these settings.

Running Tests

# Run all tests
dotnet test

# Run with code coverage
dotnet test --collect:"XPlat Code Coverage"

# Run a specific test
dotnet test --filter "FullyQualifiedName~ToReadableTypeString_IntType"

Changelog

See CHANGELOG.md for a list of changes in each version.

License

MIT License - see LICENSE file for details.

Acknowledgments

  • Built with ❤️ using .NET
  • Inspired by the need for better reflection type string formatting in .NET applications
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 2,289 1/12/2026
1.0.0-beta4 94 1/12/2026
0.1.1 94 1/12/2026
0.1.0 129 1/12/2026
0.0.6 132 1/12/2026