SpawnDev.GenericInvocation 1.0.1

Prefix Reserved
dotnet add package SpawnDev.GenericInvocation --version 1.0.1
                    
NuGet\Install-Package SpawnDev.GenericInvocation -Version 1.0.1
                    
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="SpawnDev.GenericInvocation" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SpawnDev.GenericInvocation" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="SpawnDev.GenericInvocation" />
                    
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 SpawnDev.GenericInvocation --version 1.0.1
                    
#r "nuget: SpawnDev.GenericInvocation, 1.0.1"
                    
#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 SpawnDev.GenericInvocation@1.0.1
                    
#: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=SpawnDev.GenericInvocation&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=SpawnDev.GenericInvocation&version=1.0.1
                    
Install as a Cake Tool

SpawnDev.GenericInvocation

A high-performance reflection utility for .NET 8, 9, and 10 that bridges the gap between runtime Type variables and compile-time generic (<T>) method signatures.

By utilizing one-time compiled Expression Trees and cached non-boxing generic bridges, SpawnDev.GenericInvocation completely avoids the heavy runtime performance penalties of MethodInfo.Invoke and the memory churn of the C# dynamic keyword call-site binder.

🚀 Why Use It?

In advanced architecture patterns (like high-throughput Marshallers, Serializers, or JS-Interop pipelines), you often encounter situations where you know an object's destination type only at runtime, but you need to feed it into a heavily optimized, strongly-typed method blueprint:

// You have this at runtime:
Type runtimeType = typeof(double);

// And you need to invoke this without causing a massive bottleneck:
async ValueTask<T> WriteTypedValue<T>() { ... }

The Old Ways vs. SpawnDev

  1. MethodInfo.Invoke: Extremely slow. Forces the runtime to run heavy security validation checks and box value types (like primitives or ValueTask) into heap-allocated object structures on every single call.
  2. dynamic Dispatch: Faster on warm paths due to DLR caching, but introduces silent heap allocations, state-machine tracking overhead, and forces struct-boxing on value-type tasks like ValueTask<T>.
  3. SpawnDev.GenericInvocation: Inspects metadata and compiles a native execution lambda expression exactly once per type combination. Subsequent hot-path invocations execute at raw, near-handwritten speed with zero allocation overhead.

⚡ Performance Benchmarks

Target Frameworks: .NET 8, 9, and 10 | 1,000,000 Hot-Path Loop Iterations calling ValueTask<T>

Implementation Strategy Cost Per Call Total Elapsed Time
Hardcoded Direct Native Call (Baseline Limit) ~0.44 μs 447,260 μs
SpawnDev.GenericInvocation ~2.41 μs 2,414,099 μs
Dynamic Keyword (dynamic) Unwrapping ~4.36 μs 4,418,700 μs

By eliminating call-site binder churn and preventing value-type unboxing, SpawnDev.GenericInvocation delivers a ~38% raw performance leap over standard dynamic async unwrapping strategies.


💻 Quick Start & Examples

1. Single Type Parameter Invocations

using SpawnDev.GenericInvocation;

// Create a delegate targeting your generic method signature
var myMethod = ((Delegate)MyAction<object>).InvokeGenericAsync(typeof(double));

async ValueTask<T> MyAction<T>() 
{
    Console.WriteLine(\$"Executed natively with type: {typeof(T).Name}");
    return default;
}

2. Multi-Type Parameter Fallbacks

The library intelligently handles single and multi-type cached dimensions dynamically without forcing array mutations on the heap on cache hits:

Type[] runtimeTypes = [typeof(string), typeof(int)];

// Dispatches seamlessly into multi-generic signatures
await myMethodGroup.InvokeGenericAsync(runtimeTypes, "Hello World", 42);

3. Native Task & ValueTask Auto-Unwrapping

Whether your generic target returns a Task<T>, ValueTask<T>, or synchronous primitives, the under-the-hood expression compiler automatically extracts the inner value seamlessly:

// Returns the actual unwrapped type without executing a slow reflection lookup on Task.Result
object? result = await myDelegate.InvokeGenericAsync(typeof(int));

⚙️ Features Under the Hood

  • Zero Array Heap Allocations: Leverages Span<object?> parameter layouts combined with conditional key-cloning to ensure that checking cache dictionaries is entirely garbage-collection silent.
  • Auto-Target Resolution: Native support for instance methods, static methods, and complex captured local functions alike without triggering delegate targeting binding failures (Arg_DlgtTargMeth).
  • Built for the Future: Designed specifically with lightweight runtime features optimized for high-throughput UI wrappers, framework components, and Blazor WebAssembly environments.

✂️ Trimming & Native AOT

This library turns a runtime Type into a compile-time <T> using MakeGenericMethod / MakeGenericType and compiled Expression Trees. That is fundamentally reflection + runtime code generation, so a general-purpose invoker cannot promise the trimmer or the AOT compiler that an arbitrary caller's generic method - and the types passed to it - will survive. The requirement isn't even expressible as a single annotation: one caller's T might need public constructors, another's public methods, another nothing.

So the public API is honestly labelled:

  • [RequiresUnreferencedCode] - a trimmed consumer gets a clear IL2026 at their own call site.
  • [RequiresDynamicCode] - an AOT consumer gets a clear IL3050 at their own call site.

What this means for you

  • Blazor WebAssembly (default, interpreted): works today - WASM is trimmed but keeps the IL interpreter, so MakeGenericMethod and compiled expressions run fine at runtime. You'll see the IL2026/IL3050 warnings; suppress them at the call site once you've ensured your target method and types are preserved (see below).
  • Native AOT / fully-AOT WASM (no interpreter): not supported - the runtime code generation this library depends on is unavailable. The IL3050 warning is telling you the truth.

Suppressing at the call site (when you know it's safe)

If you control the generic method being closed and know its type parameters carry no DynamicallyAccessedMembers requirements (e.g. an internal dispatch method), suppress right where you call in:

[UnconditionalSuppressMessage("Trimming", "IL2026",
    Justification = "Closes MyDispatch<T>, whose type parameter declares no DynamicallyAccessedMembers requirement.")]
[UnconditionalSuppressMessage("AOT", "IL3050",
    Justification = "Interpreted runtime (e.g. Blazor WASM); not built for Native AOT.")]
void CallSite() => ((Delegate)MyDispatch<object>).InvokeGeneric(runtimeType);

If the closed method (or a passed type) does need members preserved, keep them alive with [DynamicallyAccessedMembers], a [DynamicDependency], or a trimmer descriptor instead of suppressing.


📄 License

This project is licensed under the MIT License.

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.1 108 8/14/2026
1.0.0 100 8/12/2026

v1.0.1 - Trimming support