feat: add Func<T> and Lazy<T> relationship types#21
Conversation
Constructor parameters (and direct resolutions) typed as Func<TService> or Lazy<TService> are now satisfied automatically by synthesizing a factory or lazy that resolves TService from the owning container or scope. Func<T> returns a fresh resolve on each call, respecting the target's lifetime; Lazy<T> memoizes per owner instance. The underlying service must still be registered, otherwise AWT101 fires on it. Relationship-typed parameters defer resolution, so they are excluded from the dependency graph used for cycle detection (AWT102) and captive-dependency analysis (AWT105): a Func<T>/Lazy<T> breaks what would otherwise be a cycle and does not capture a shorter-lived dependency. Constructor selection unwraps Func/Lazy so a relationship parameter counts as resolvable when its target is registered. The constructor-parameter model gains a DependencyKind and is captured as ParameterModel rather than a bare service-type string, and the static dispatch table now also maps Func<T>/Lazy<T> over each service so they are resolvable directly, not only via constructor injection. No new diagnostic is introduced. The prototype referenced this feature against AWK101/AWK102/AWK103; on main those map to AWT101 (missing dependency), AWT102 (cycle) and AWT105 (captive dependency).
🚀 Benchmark ResultsDetails
Details
|
…ship diagnostics Building the static dispatch table emitted a Func<T> and Lazy<T> entry for every service unconditionally, so an explicitly registered relationship type (e.g. a registered Lazy<T>) collided with the synthetic entry, producing a duplicate dictionary key that threw from the static initializer at first resolve. BuildDispatchEntries now adds explicit service registrations first and skips any synthetic relationship entry whose key is already claimed, so explicit registrations win their slot and the table never contains a duplicate key. ClassifyParameter unwrapped a single level of Func<T>/Lazy<T> regardless of what the inner type was, so a relationship over another relationship (Func<Func<T>>) was reported as a missing registration of the inner Func<T> even though that is resolvable. It now leaves a nested relationship as a direct dependency so the diagnostic names the full unregistered service type; one level of nesting is supported. Display stripped only a leading global:: alias, leaking it into diagnostics for generic type arguments (System.Func<global::MyCode.Leaf>). It now strips every occurrence so messages render the alias-free type. Adds tests for the duplicate-key fix, the nested-relationship diagnostic, and the documented behavior that a singleton's Func<Scoped> resolves from the root container rather than a later child scope.
Comments that explained why an assertion holds are folded into the assertion's .Because(...) reason so the rationale travels with the failure message instead of sitting in a detached code comment. Covers the relationship, dispatch-table and lifetime tests. Comments that describe arrange/setup or type declarations are left in place. Also reorders a few test members alphabetically with no behavioral change.
… entries Service types are unique across instances because registrations are coalesced, so the seen.Add guard on the direct-registration pass was always true. Seed the seen set explicitly and add each entry unconditionally; the synthetic Func<T>/Lazy<T> pass still checks seen so an explicit relationship registration keeps its dispatch slot. Resolves the lone-if loop that the analyzer flagged without pushing a side-effecting predicate into a Where call.
fc47d00 to
f0f1db5
Compare
|
…ionship types (#21) by Valentin Breuß
…ionship types (#21) by Valentin Breuß
|
This is addressed in release v0.1.0. |



Constructor parameters (and direct resolutions) typed as
Func<TService>orLazy<TService>are now satisfied automatically by synthesizing a factory or lazy that resolvesTServicefrom the owning container or scope.Func<T>returns a fresh resolve on each call, respecting the target's lifetime;Lazy<T>memoizes per owner instance. The underlying service must still be registered, otherwise AWT101 fires on it.Relationship-typed parameters defer resolution, so they are excluded from the dependency graph used for cycle detection (AWT102) and captive-dependency analysis (AWT105): a
Func<T>/Lazy<T>breaks what would otherwise be a cycle and does not capture a shorter-lived dependency. Constructor selection unwrapsFunc/Lazyso a relationship parameter counts as resolvable when its target is registered.The constructor-parameter model gains a DependencyKind and is captured as
ParameterModelrather than a bare service-type string, and the static dispatch table now also mapsFunc<T>/Lazy<T>over each service so they are resolvable directly, not only via constructor injection.No new diagnostic is introduced. The prototype referenced this feature against AWK101/AWK102/AWK103; on main those map to AWT101 (missing dependency), AWT102 (cycle) and AWT105 (captive dependency).