Skip to content

Commit 73aa9f6

Browse files
jeremydmillerclaude
andcommitted
Exclude keyed registrations from IEnumerable<T> / T[] resolution
MS DI's `IServiceProvider.GetServices(T)` and `T[]` resolution return only non-keyed registrations; keyed registrations are looked up explicitly via `GetKeyedService(T, key)`. Lamar's `ListInstance` / `ArrayInstance` populated their `Elements` straight from `ServiceGraph.FindAll`, which is built from `ServiceFamily.All` — the flat union of keyed and non-keyed instances for a given service type. As a result a keyed registration of `T` ended up as an `IEnumerable<T>` element, deviating from MS DI semantics and, more importantly, causing infinite recursion for code-generating consumers: JasperFx's `EnumerableSingletons.KeyedMirror` registers a keyed Singleton lambda whose factory calls `sp.GetServices(elementType)` to return the shared non-keyed singleton instance. When Lamar inlines the mirror's Singleton via `InjectedServiceField.ToVariableExpression`'s `QuickResolve`, the factory invokes `sp.GetServices(T)`, Lamar resolves the `IEnumerable<T>` family (still including the mirror itself), the generated build frame inlines the mirror again, and so on — ~750-frame stack overflow before any handler runs. Filter `IsKeyedService` out of `createPlan` in both `ListInstance` and `ArrayInstance`, matching MS DI's behaviour and breaking the cycle. Explicit keyed lookups (`GetKeyedService(T, key)`) are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 372b588 commit 73aa9f6

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

src/Lamar/IoC/Enumerables/ArrayInstance.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,13 @@ protected override IEnumerable<Instance> createPlan(ServiceGraph services)
6363
}
6464
else
6565
{
66-
Elements = services.FindAll(typeof(T));
66+
// MS DI's IServiceProvider.GetServices(T) (and by extension T[] resolution) returns
67+
// only non-keyed registrations; keyed registrations are looked up explicitly via
68+
// GetKeyedService(T, key). Match that here so a keyed-mirror registration of the
69+
// same service type doesn't end up as a T[] element. Otherwise inlining the
70+
// mirror's Singleton via QuickResolve invokes the mirror's factory, which calls
71+
// sp.GetServices(T) again and recursively re-enters codegen — stack overflow.
72+
Elements = services.FindAll(typeof(T)).Where(x => !x.IsKeyedService).ToArray();
6773
}
6874

6975
return Elements;

src/Lamar/IoC/Enumerables/ListInstance.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ protected override IEnumerable<Instance> createPlan(ServiceGraph services)
5656
}
5757
else
5858
{
59-
Elements = services.FindAll(typeof(T));
59+
// MS DI's IServiceProvider.GetServices(T) returns only non-keyed registrations;
60+
// keyed registrations are looked up explicitly via GetKeyedService(T, key). Match
61+
// that here so a keyed-mirror registration of the same service type doesn't end up
62+
// as an IEnumerable<T> element. Otherwise inlining the mirror's Singleton via
63+
// QuickResolve invokes the mirror's factory, which calls sp.GetServices(T) again
64+
// and recursively re-enters ListInstance/ArrayInstance code-gen — stack overflow.
65+
Elements = services.FindAll(typeof(T)).Where(x => !x.IsKeyedService).ToArray();
6066
}
6167

6268
return Elements;

0 commit comments

Comments
 (0)