Registrar 1.1.0

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

Registrar

A tiny, zero-dependency (other than the BCL) library that provides a simple, namespaced identifier type and in-memory registries for mapping those identifiers to arbitrary reference-type objects. Targets .NET 8.0 and .NET Standard 2.1 for broad compatibility.

Lightweight, strongly-typed key space for plugins, data packs, game content, or any scenario needing stable IDs like my-mod:items/sword.

Features

  • Identifier struct with validated namespace:path format
  • Simple registration API: Registry.Register(registry, id, value)
  • Raw integer IDs auto-assigned in registration order
  • Defaulted registry (SimpleDefaultedRegistry<T>) with fallback value/id/raw id
  • Reverse lookups now O(1) (value → identifier / rawId) with enforced uniqueness
  • Thread-safe registration & lookups (coarse locking) + lock-elided reads after freeze
  • Freezing: convert a registry to immutable state (registry.Freeze()) to prevent further mutation
  • Enumeration support (snapshot enumeration for safety)
  • Multi-targeted build (net8.0 + netstandard2.1)
  • MIT licensed
  • Unit tests including concurrency & freeze behavior

Identifier Format

namespace:path
  • Namespace: [a-z0-9_.-]+
  • Path: [a-z0-9_.\-/]+
  • Examples: example_namespace:some/path, vanilla:items/health_potion

Creation helpers:

  • Identifier.FromNamespaceAndPath(ns, path) – validates & throws on failure.
  • Identifier.Parse("ns:path") – throws on invalid format.
  • Identifier.TryParse(string, out Identifier? id) – returns true/false without throwing.

Static vs Dynamic Registries

Inspired by Minecraft’s model:

  • Static registry: Hard-coded (vanilla/core) values only; frozen after bootstrap; further registrations throw InvalidOperationException("Registry is already frozen").
  • Dynamic registry (future extension): Intended to allow late additions (e.g. data packs / mods) and remains unfrozen (or supports controlled reload cycles).

Currently all registries can be frozen manually by calling Freeze(). A recommended pattern is to freeze core registries after loading vanilla + mod content.

Bootstrap Lifecycle Example

public interface IRegistrar { void Register(); }

public static class Items : IRegistrar {
    public static readonly SimpleRegistry<Item> Registry = new();
    public static readonly Item HealthPotion = Registry.Register(
        Identifier.FromNamespaceAndPath("vanilla", "health_potion"), new Item());
    public void Register() { /* static field initializers already executed */ }
}

public static class Bootstrap {
    public static void Initialize(IEnumerable<IRegistrar> registrars) {
        foreach (var r in registrars) r.Register(); // perform all registrations
        Items.Registry.Freeze(); // freeze static registry
    }
}

Attempting to register AFTER freezing:

Registry.Register(Items.Registry, Identifier.FromNamespaceAndPath("vanilla","late"), new Item());
// => InvalidOperationException("Registry is already frozen")

Reverse Lookup & Uniqueness

Value → Identifier / RawId lookups are now O(1). A single value may only be registered under one identifier. Attempting to register the SAME value under a different identifier throws. Re-registering an existing identifier returns the already stored value (idempotent for that ID).

Quick Start

var registry = new SimpleRegistry<string>();
var swordId = Identifier.FromNamespaceAndPath("demo", "items/iron_sword");
Registry.Register(registry, swordId, "Sword");
registry.Freeze(); // make immutable
var sword = registry.Get(swordId); // "Sword"

Defaulted Registry

var defaultId = Identifier.FromNamespaceAndPath("base", "items/missing");
var defaulted = new SimpleDefaultedRegistry<string>("<missing>", defaultId);
// Not found -> returns default
var missing = defaulted.Get(Identifier.FromNamespaceAndPath("demo","items/not_there")); // "<missing>"

Freezing Details

  • Freeze() is idempotent.
  • After freeze: all read APIs skip locks for performance.
  • Registering after freeze throws.
  • Concurrency: safe to call Freeze() while no registration is in progress (call it after bootstrap phase).

Error Handling

  • Parse / validation methods throw on malformed identifiers (ArgumentException / FormatException).
  • Lookup returns fallback (null or defaulted value) when missing.
  • GetRandom(Random) throws if registry empty.
  • Registration after freeze throws.

Testing

dotnet test

Covers identifier validation, defaulted behavior, reverse lookup, concurrency, and freezing.

Versioning & Breaking Changes

Potential roadmap items may introduce breaking changes until 1.0 (e.g., specialized dynamic registries). Uniqueness enforcement & proper TryParse pattern already introduced.

Roadmap (Planned / Ideas)

  • Dynamic registry abstraction (data-driven reloadable registries)
  • Bulk registration & removal APIs
  • Optional multi-value mapping mode (value → multiple identifiers)
  • Benchmark project (BenchmarkDotNet) for lock vs freeze read performance
  • Source generator for strongly-typed constants

Contributing

  1. Fork & branch
  2. Add/adjust tests
  3. Keep public APIs documented
  4. Open PR referencing related issue(s)

License

MIT – see LICENSE.md

Disclaimer

Small by design; clarity over premature optimization. Contributions welcome.

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 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 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 netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen 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.
  • .NETStandard 2.1

    • No dependencies.
  • net8.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.1.0 71 9/5/2025
1.0.2 47 8/23/2025
1.0.1 44 8/23/2025
1.0.0 50 8/22/2025