Skip to content

Compono.RowInvokerRegistry

Compono

Compono

RowInvokerRegistry Class

A non-generic, System.Type-keyed registry of Resolve<TValue>(CompositionRequestDescriptor)/ ResolveShared<TValue>(CompositionRequestDescriptor)/ ShareExplicit<TValue>(CompositionRequestDescriptor, TValue) dispatch delegates, per ADR-0041 Amendment 2.

public static class RowInvokerRegistry

Inheritance System.Object → RowInvokerRegistry

Remarks

A test-framework integration's binding algorithm only ever has a runtime System.Type for a test parameter (reflected off the test method's own signature), never a compile-time T - unlike PlanCache<T>, whose only reader is itself generic with T bound at a real call site, there is no call site here to bind a closed generic field against. A System.Type object is an ordinary runtime value under Native AOT; only *dynamic instantiation* of a generic method/type from one (MethodInfo.MakeGenericMethod) is unsafe, and this registry never does that - every Resolve<T>()/ResolveShared<T>()/ShareExplicit<T>() call a registered entry actually makes is written directly, with a compile-time-known T, in generator-emitted source.

Register(Type, ResolveInvoker, ResolveSharedInvoker, ShareExplicitInvoker) is populated by a generated module initializer in the consuming assembly (never by Compono itself), the same cross-assembly reason PlanCache<T>'s own setter is public despite coding-standards.md's "no static singletons" rule. Two consuming assemblies loaded into the same process that both discover the same parameter type (e.g. both composing string as a [Compose] parameter) each run their own generated module initializer against this same registry - backed by a System.Collections.Concurrent.ConcurrentDictionary<> with an atomic System.Collections.Concurrent.ConcurrentDictionary<>.GetOrAdd(@0,@1), never a throwing or blind-overwrite registration, because a plain, non-concurrent dictionary's internal structure can corrupt under genuinely concurrent writes from two module initializers running on different threads during assembly load - a strictly worse failure mode than "last write wins." This is safe specifically because every registration for a given System.Type is functionally interchangeable regardless of which assembly generated it (the emitted lambda is always the same shape, (row, descriptor) => row.Resolve<T>(descriptor), for the same T) - unlike PlanCache<T>'s own genuine "which plan is correct" ambiguity, there is no real question to defer here (ADR-0041 Amendment 3).

Left undecorated with no System.ComponentModel.EditorBrowsableAttribute - a deliberate choice, matching PlanCache<T>/CollectionPlanCache<T>, its two closest precedents as "generator infrastructure, not consumer-facing" public types that carry no such attribute either, rather than making this one type inconsistent with them.

Every entry stored here permanently roots its registered delegates (and the generating assembly) - this registry has no closed-generic-instantiation home-context tie the way PlanCache<T>/ CollectionPlanCache<T> do, so the limitation is broader in scope than CollectionPlanCache<T>'s own already-documented one (scoped only to collections whose type arguments are entirely BCL types). Deferred, same disposition as that existing limitation - see docs/architecture/current/generated-plans-and-discovery.md's collectible-System.Runtime.Loader.AssemblyLoadContext-rooting note, extended to name this registry.

Methods
Register(Type, ResolveInvoker, ResolveSharedInvoker, ShareExplicitInvoker) Idempotently registers the three dispatch delegates for type - a second registration for a type already present (e.g. from another assembly's own generated module initializer) is a no-op, never a throw or an overwrite.
TryGet(Type, ResolveInvoker, ResolveSharedInvoker, ShareExplicitInvoker) Looks up the three dispatch delegates registered for type, or false if none has been registered - either because type was never discovered at a dispatch-eligible [Compose]-family parameter, or because the consuming assembly's module initializers haven't run yet.