TypeLevelPresets 0.1.1

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

Stand With Ukraine

Terms of use<sup>?</sup>

By using this project or its source code, for any purpose and in any shape or form, you grant your implicit agreement to all the following statements:

  • You condemn Russia and its military aggression against Ukraine
  • You recognize that Russia is an occupant that unlawfully invaded a sovereign state
  • You support Ukraine's territorial integrity, including its claims over temporarily occupied territories of Crimea and Donbas
  • You reject false narratives perpetuated by Russian state propaganda

To learn more about the war and how you can help, click here. Glory to Ukraine!

TypeLevelPresets

Turn a small, fixed set of options into CLR types, so generic static caches can be keyed by both a data type and a preset without a dictionary lookup on the typed hot path.

Why

.NET gives every closed generic type its own static fields. This makes a generic type a convenient per-type cache:

static class MetadataCache<T>
{
    public static readonly Metadata Value = Build(typeof(T));
}

MetadataCache<Invoice> and MetadataCache<Customer> each initialize and store a different Value. Microsoft documents this behavior explicitly: each closed generic type has its own copy of static members.

The pattern stops being direct when the cache key also contains values such as a bool, an enum, or BindingFlags: C# cannot use arbitrary values as generic arguments. The usual fallback is a runtime lookup:

Dictionary<(Type, FieldOptions), Metadata> cache;

TypeLevelPresets generates a marker type for each declared option tuple. The cache can then use a distinct closed generic type for every known combination:

MetadataCache<Invoice, FieldOptions.Public>.Value

Quick start

using System.Reflection;
using Raffinert.TypeLevelPresets;

[Preset("Public", BindingFlags.Instance | BindingFlags.Public)]
[Preset("AllInstance",
    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)]
public readonly partial struct FieldOptions
{
    public BindingFlags Flags { get; }
}

public static class Cache<T, TPreset>
    where TPreset : struct, IPreset<FieldOptions>
{
    public static readonly FieldInfo[] Value = typeof(T).GetFields(
        FieldOptions.GetValues<TPreset>().Flags);
}

var fields = Cache<Invoice, FieldOptions.Public>.Value;

The generator emits:

  • a zero-field readonly struct marker for each [Preset];
  • an immutable FieldOptionsValues value type;
  • a cached FieldOptions.GetValues<TPreset>() accessor.

The preset values are emitted as code. The typed path uses no attribute reflection and no options dictionary. Each Cache<T, TPreset> combination owns independent static storage and initializes it once.

Good fit

Use this pattern when all of the following are true:

  • the presets are finite and known at build time;
  • the same type-and-preset combinations are accessed repeatedly;
  • a lookup sits on a measured hot path;
  • separate generic-static storage is useful for metadata, delegates, serializers, mappers, or similar reusable artifacts.

Keep a normal dictionary for runtime-selected, user-defined, or high-cardinality options. This package is not a general configuration system. Every additional closed generic combination can add initialization work and may add JIT or native-code size, so benchmark the complete workload rather than only the lookup.

Supported values and rules

Schemas are top-level, non-generic, partial classes or structs. Their readable instance properties, in declaration order, define the tuple. Version 1 supports bool, integral primitives, char, string, enums, and System.Type supplied as typeof(...).

Floating point, nullable values, arrays, nested schemas, and schemas split across multiple user declarations are rejected. Invalid names or values, duplicate presets, and generated-name collisions produce TLP001TLP011 compiler diagnostics.

Benchmark

The benchmark preloads 100 dictionary entries and generates 100 presets. Each invocation reads all 100 values, and BenchmarkDotNet reports the time per lookup. Run it with:

dotnet run -c Release --project benchmarks/TypeLevelPresets.Benchmarks -- --filter '*FieldMetadataCacheBenchmarks*'

Representative short run on Windows 11 with .NET 10.0.11, x64 RyuJIT, and AVX2 (BenchmarkDotNet 0.15.2; three warmups and three measured iterations):

Access path Mean per lookup Allocated
Composite dictionary with 100 entries 6.6450 ns 0 B
Direct generic static across 100 presets 0.0013 ns¹ 0 B

¹ BenchmarkDotNet reported the generic-static read as indistinguishable from an empty method. Treat the near-zero value as below measurement resolution, not as a literal access time or an exact speedup. The benchmark shows that this typed path removed measurable dictionary-lookup overhead; it does not promise the same application-level gain.

Background reading

Build and test

dotnet build TypeLevelPresets.slnx
dotnet test TypeLevelPresets.slnx
dotnet pack src/TypeLevelPresets -c Release

The TypeLevelPresets package contains net45 and netstandard2.0 abstraction assemblies under lib/ and the incremental generator under analyzers/dotnet/cs/. Consumers install one package; Roslyn does not become a runtime dependency. See the changelog for release history.

See samples/TypeLevelPresets.Sample.Serialization for the reflection metadata cache example. Interceptors, runtime-generated types, arbitrary runtime values, and call-site rewriting are not part of v1.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net45 is compatible.  net451 was computed.  net452 was computed.  net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.5

    • No dependencies.
  • .NETStandard 2.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
0.1.1 89 8/14/2026
0.1.0 88 8/14/2026