From 2cbedb49b5b7f4ba56adc1976f9852ac5690459e Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Thu, 28 May 2026 20:36:58 -0500 Subject: [PATCH 1/2] Exclude keyed registrations from IEnumerable / T[] resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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` 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` 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) --- src/Lamar/IoC/Enumerables/ArrayInstance.cs | 8 +++++++- src/Lamar/IoC/Enumerables/ListInstance.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Lamar/IoC/Enumerables/ArrayInstance.cs b/src/Lamar/IoC/Enumerables/ArrayInstance.cs index 0f7ca3a5..ac92b01f 100644 --- a/src/Lamar/IoC/Enumerables/ArrayInstance.cs +++ b/src/Lamar/IoC/Enumerables/ArrayInstance.cs @@ -63,7 +63,13 @@ protected override IEnumerable createPlan(ServiceGraph services) } else { - Elements = services.FindAll(typeof(T)); + // MS DI's IServiceProvider.GetServices(T) (and by extension T[] resolution) returns + // only non-keyed registrations; keyed registrations are looked up explicitly via + // GetKeyedService(T, key). Match that here so a keyed-mirror registration of the + // same service type doesn't end up as a T[] element. Otherwise inlining the + // mirror's Singleton via QuickResolve invokes the mirror's factory, which calls + // sp.GetServices(T) again and recursively re-enters codegen — stack overflow. + Elements = services.FindAll(typeof(T)).Where(x => !x.IsKeyedService).ToArray(); } return Elements; diff --git a/src/Lamar/IoC/Enumerables/ListInstance.cs b/src/Lamar/IoC/Enumerables/ListInstance.cs index 494c723d..ef68c386 100644 --- a/src/Lamar/IoC/Enumerables/ListInstance.cs +++ b/src/Lamar/IoC/Enumerables/ListInstance.cs @@ -56,7 +56,13 @@ protected override IEnumerable createPlan(ServiceGraph services) } else { - Elements = services.FindAll(typeof(T)); + // MS DI's IServiceProvider.GetServices(T) returns only non-keyed registrations; + // keyed registrations are looked up explicitly via GetKeyedService(T, key). Match + // that here so a keyed-mirror registration of the same service type doesn't end up + // as an IEnumerable element. Otherwise inlining the mirror's Singleton via + // QuickResolve invokes the mirror's factory, which calls sp.GetServices(T) again + // and recursively re-enters ListInstance/ArrayInstance code-gen — stack overflow. + Elements = services.FindAll(typeof(T)).Where(x => !x.IsKeyedService).ToArray(); } return Elements; From f86d503b7a4d4be1d8064b4c9b56a9caefdeb2d3 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Thu, 28 May 2026 21:02:26 -0500 Subject: [PATCH 2/2] Target net9.0;net10.0 and bump to 16.0.0 Drop all target frameworks below .NET 9 across the solution and ensure the published nugets (Lamar, Lamar.Microsoft.DependencyInjection) target net9.0;net10.0 at version 16.0.0. Remove dead net6.0/net8.0 conditional package groups and the .NET 8 SDK setup in CI workflows. Closes #427 Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/dotnet.yml | 4 ---- .github/workflows/publish_lamar.yml | 5 ----- src/Aspect.Logger/Widget.Aspect.Logger.csproj | 2 +- .../Lamar.AspNetCoreTests.Integration.csproj | 20 +------------------ .../Lamar.AspNetCoreTests.csproj | 12 ++--------- ...Lamar.Microsoft.DependencyInjection.csproj | 4 ++-- src/Lamar.Testing/Lamar.Testing.csproj | 2 +- src/Lamar/Lamar.csproj | 10 ++-------- ...DiagnosticsWithNetCore3Demonstrator.csproj | 2 +- .../LamarWithAspNetCore3.csproj | 2 +- .../LamarWithAspNetCoreMvc3.csproj | 2 +- .../LamarWithIdentityOnNet5.csproj | 2 +- .../LamarWithMinimalApiOnNet6.csproj | 2 +- src/MinimalApiTests/MinimalApiTests.csproj | 2 +- .../StructureMap.Testing.ExeWidget.csproj | 2 +- ...StructureMap.Testing.GenericWidgets.csproj | 2 +- .../StructureMap.Testing.Widget.csproj | 2 +- .../StructureMap.Testing.Widget2.csproj | 2 +- .../StructureMap.Testing.Widget3.csproj | 2 +- .../StructureMap.Testing.Widget4.csproj | 2 +- .../StructureMap.Testing.Widget5.csproj | 2 +- src/UserApp/UserApp.csproj | 2 +- src/Widget.Core/Widget.Core.csproj | 2 +- src/Widget.Instance/Widget.Instance.csproj | 2 +- .../Widget.Registration.csproj | 2 +- 25 files changed, 26 insertions(+), 67 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 3fd77a62..fb46a019 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -30,10 +30,6 @@ jobs: uses: actions/setup-dotnet@v3 with: dotnet-version: 9.0.x - - name: Setup .NET 8 - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 8.0.x - name: Test diff --git a/.github/workflows/publish_lamar.yml b/.github/workflows/publish_lamar.yml index f3e4e6d4..4d0e2415 100644 --- a/.github/workflows/publish_lamar.yml +++ b/.github/workflows/publish_lamar.yml @@ -20,11 +20,6 @@ jobs: with: dotnet-version: 9.0.x - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 8.0.x - - name: Build run: dotnet build Lamar.sln --configuration Release diff --git a/src/Aspect.Logger/Widget.Aspect.Logger.csproj b/src/Aspect.Logger/Widget.Aspect.Logger.csproj index b7c1e304..7a0ca540 100644 --- a/src/Aspect.Logger/Widget.Aspect.Logger.csproj +++ b/src/Aspect.Logger/Widget.Aspect.Logger.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 diff --git a/src/Lamar.AspNetCoreTests.Integration/Lamar.AspNetCoreTests.Integration.csproj b/src/Lamar.AspNetCoreTests.Integration/Lamar.AspNetCoreTests.Integration.csproj index 9c04b6ff..d6713eb5 100644 --- a/src/Lamar.AspNetCoreTests.Integration/Lamar.AspNetCoreTests.Integration.csproj +++ b/src/Lamar.AspNetCoreTests.Integration/Lamar.AspNetCoreTests.Integration.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 false @@ -17,24 +17,6 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/Lamar.AspNetCoreTests/Lamar.AspNetCoreTests.csproj b/src/Lamar.AspNetCoreTests/Lamar.AspNetCoreTests.csproj index 77851d2a..bb6022ad 100644 --- a/src/Lamar.AspNetCoreTests/Lamar.AspNetCoreTests.csproj +++ b/src/Lamar.AspNetCoreTests/Lamar.AspNetCoreTests.csproj @@ -1,6 +1,6 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 false @@ -21,15 +21,7 @@ - - - - - - - - - + diff --git a/src/Lamar.Microsoft.DependencyInjection/Lamar.Microsoft.DependencyInjection.csproj b/src/Lamar.Microsoft.DependencyInjection/Lamar.Microsoft.DependencyInjection.csproj index bb76334a..01fdd0e8 100644 --- a/src/Lamar.Microsoft.DependencyInjection/Lamar.Microsoft.DependencyInjection.csproj +++ b/src/Lamar.Microsoft.DependencyInjection/Lamar.Microsoft.DependencyInjection.csproj @@ -1,9 +1,9 @@  Lamar Adapter for HostBuilder Integration - 15.1.0 + 16.0.0 Jeremy D. Miller - net8.0;net9.0;net10.0 + net9.0;net10.0 portable Lamar.Microsoft.DependencyInjection Lamar.Microsoft.DependencyInjection diff --git a/src/Lamar.Testing/Lamar.Testing.csproj b/src/Lamar.Testing/Lamar.Testing.csproj index fbda2631..6a74868b 100644 --- a/src/Lamar.Testing/Lamar.Testing.csproj +++ b/src/Lamar.Testing/Lamar.Testing.csproj @@ -1,6 +1,6 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 10.0 diff --git a/src/Lamar/Lamar.csproj b/src/Lamar/Lamar.csproj index b2596a6f..fc05672b 100644 --- a/src/Lamar/Lamar.csproj +++ b/src/Lamar/Lamar.csproj @@ -1,9 +1,9 @@  Fast ASP.Net Core compatible IoC Tool, Successor to StructureMap - 15.1.0 + 16.0.0 Jeremy D. Miller - net8.0;net9.0;net10.0 + net9.0;net10.0 portable Lamar Lamar @@ -26,12 +26,6 @@ - - - - - - diff --git a/src/LamarDiagnosticsWithNetCore3Demonstrator/LamarDiagnosticsWithNetCore3Demonstrator.csproj b/src/LamarDiagnosticsWithNetCore3Demonstrator/LamarDiagnosticsWithNetCore3Demonstrator.csproj index 10b55f57..4b97a4f3 100644 --- a/src/LamarDiagnosticsWithNetCore3Demonstrator/LamarDiagnosticsWithNetCore3Demonstrator.csproj +++ b/src/LamarDiagnosticsWithNetCore3Demonstrator/LamarDiagnosticsWithNetCore3Demonstrator.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net9.0;net10.0 + net9.0;net10.0 diff --git a/src/LamarWithAspNetCore3/LamarWithAspNetCore3.csproj b/src/LamarWithAspNetCore3/LamarWithAspNetCore3.csproj index 327ead14..3db48614 100644 --- a/src/LamarWithAspNetCore3/LamarWithAspNetCore3.csproj +++ b/src/LamarWithAspNetCore3/LamarWithAspNetCore3.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 false diff --git a/src/LamarWithAspNetCoreMvc3/LamarWithAspNetCoreMvc3.csproj b/src/LamarWithAspNetCoreMvc3/LamarWithAspNetCoreMvc3.csproj index 474e06a9..c3931ccb 100644 --- a/src/LamarWithAspNetCoreMvc3/LamarWithAspNetCoreMvc3.csproj +++ b/src/LamarWithAspNetCoreMvc3/LamarWithAspNetCoreMvc3.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 diff --git a/src/LamarWithIdentityOnNet5/LamarWithIdentityOnNet5.csproj b/src/LamarWithIdentityOnNet5/LamarWithIdentityOnNet5.csproj index 8de311a6..90fef694 100644 --- a/src/LamarWithIdentityOnNet5/LamarWithIdentityOnNet5.csproj +++ b/src/LamarWithIdentityOnNet5/LamarWithIdentityOnNet5.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + net9.0;net10.0 diff --git a/src/LamarWithMinimalApiOnNet6/LamarWithMinimalApiOnNet6.csproj b/src/LamarWithMinimalApiOnNet6/LamarWithMinimalApiOnNet6.csproj index 187a27e5..7f467f56 100644 --- a/src/LamarWithMinimalApiOnNet6/LamarWithMinimalApiOnNet6.csproj +++ b/src/LamarWithMinimalApiOnNet6/LamarWithMinimalApiOnNet6.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + net9.0;net10.0 enable enable diff --git a/src/MinimalApiTests/MinimalApiTests.csproj b/src/MinimalApiTests/MinimalApiTests.csproj index 30a7c270..931f3809 100644 --- a/src/MinimalApiTests/MinimalApiTests.csproj +++ b/src/MinimalApiTests/MinimalApiTests.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + net9.0;net10.0 enable false diff --git a/src/StructureMap.Testing.ExeWidget/StructureMap.Testing.ExeWidget.csproj b/src/StructureMap.Testing.ExeWidget/StructureMap.Testing.ExeWidget.csproj index 9ecc38b2..4dded8a4 100644 --- a/src/StructureMap.Testing.ExeWidget/StructureMap.Testing.ExeWidget.csproj +++ b/src/StructureMap.Testing.ExeWidget/StructureMap.Testing.ExeWidget.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 StructureMap.Testing.ExeWidget Exe StructureMap.Testing.ExeWidget diff --git a/src/StructureMap.Testing.GenericWidgets/StructureMap.Testing.GenericWidgets.csproj b/src/StructureMap.Testing.GenericWidgets/StructureMap.Testing.GenericWidgets.csproj index 905f4dc8..df6e54e0 100644 --- a/src/StructureMap.Testing.GenericWidgets/StructureMap.Testing.GenericWidgets.csproj +++ b/src/StructureMap.Testing.GenericWidgets/StructureMap.Testing.GenericWidgets.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 StructureMap.Testing.GenericWidgets StructureMap.Testing.GenericWidgets false diff --git a/src/StructureMap.Testing.Widget/StructureMap.Testing.Widget.csproj b/src/StructureMap.Testing.Widget/StructureMap.Testing.Widget.csproj index c0a98986..c140c546 100644 --- a/src/StructureMap.Testing.Widget/StructureMap.Testing.Widget.csproj +++ b/src/StructureMap.Testing.Widget/StructureMap.Testing.Widget.csproj @@ -1,6 +1,6 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 StructureMap.Testing.Widget StructureMap.Testing.Widget false diff --git a/src/StructureMap.Testing.Widget2/StructureMap.Testing.Widget2.csproj b/src/StructureMap.Testing.Widget2/StructureMap.Testing.Widget2.csproj index 1c30dd78..0ea941ab 100644 --- a/src/StructureMap.Testing.Widget2/StructureMap.Testing.Widget2.csproj +++ b/src/StructureMap.Testing.Widget2/StructureMap.Testing.Widget2.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 StructureMap.Testing.Widget2 StructureMap.Testing.Widget2 false diff --git a/src/StructureMap.Testing.Widget3/StructureMap.Testing.Widget3.csproj b/src/StructureMap.Testing.Widget3/StructureMap.Testing.Widget3.csproj index f985b705..3fd9d0a5 100644 --- a/src/StructureMap.Testing.Widget3/StructureMap.Testing.Widget3.csproj +++ b/src/StructureMap.Testing.Widget3/StructureMap.Testing.Widget3.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 StructureMap.Testing.Widget3 StructureMap.Testing.Widget3 false diff --git a/src/StructureMap.Testing.Widget4/StructureMap.Testing.Widget4.csproj b/src/StructureMap.Testing.Widget4/StructureMap.Testing.Widget4.csproj index 4f9a8633..045e816e 100644 --- a/src/StructureMap.Testing.Widget4/StructureMap.Testing.Widget4.csproj +++ b/src/StructureMap.Testing.Widget4/StructureMap.Testing.Widget4.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 StructureMap.Testing.Widget4 StructureMap.Testing.Widget4 1.6.0 diff --git a/src/StructureMap.Testing.Widget5/StructureMap.Testing.Widget5.csproj b/src/StructureMap.Testing.Widget5/StructureMap.Testing.Widget5.csproj index b907fae7..bcb4724e 100644 --- a/src/StructureMap.Testing.Widget5/StructureMap.Testing.Widget5.csproj +++ b/src/StructureMap.Testing.Widget5/StructureMap.Testing.Widget5.csproj @@ -1,6 +1,6 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 StructureMap.Testing.Widget5 StructureMap.Testing.Widget5 false diff --git a/src/UserApp/UserApp.csproj b/src/UserApp/UserApp.csproj index ba18a0c6..655fa746 100644 --- a/src/UserApp/UserApp.csproj +++ b/src/UserApp/UserApp.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 InProcess diff --git a/src/Widget.Core/Widget.Core.csproj b/src/Widget.Core/Widget.Core.csproj index 47829288..65b6be67 100644 --- a/src/Widget.Core/Widget.Core.csproj +++ b/src/Widget.Core/Widget.Core.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + net9.0;net10.0 diff --git a/src/Widget.Instance/Widget.Instance.csproj b/src/Widget.Instance/Widget.Instance.csproj index 65f37b76..6543587e 100644 --- a/src/Widget.Instance/Widget.Instance.csproj +++ b/src/Widget.Instance/Widget.Instance.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + net9.0;net10.0 diff --git a/src/Widget.Registration/Widget.Registration.csproj b/src/Widget.Registration/Widget.Registration.csproj index 9f633f96..4074ecd5 100644 --- a/src/Widget.Registration/Widget.Registration.csproj +++ b/src/Widget.Registration/Widget.Registration.csproj @@ -1,7 +1,7 @@ - net8.0;net9.0;net10.0 + net9.0;net10.0