Repository navigation
Keyed decorator inner binding consumes an explicitly keyed constructor dependency #109
Description
Priority: P2
Problem
Contextual keyed decorator activation binds the supplied inner instance to the first assignable parameter, excluding only ServiceKey. An earlier same-service-type parameter marked FromKeyedServices therefore consumes the inner instance instead of resolving its specified key. Reordering the otherwise equivalent constructor to put inner first makes resolution succeed.
Follow-up to closed #51: ServiceKey parameters are excluded from inner binding, but explicitly keyed dependencies are still eligible.
Source: ConstructorActivator.cs:49.
Reproduction
Create artifacts/review-decorator-binding/repro.csproj and Program.cs in a checkout of the pinned commit. The project uses the same DI 10.0.0 references as the library.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net472;net8.0;net9.0;net10.0</TargetFrameworks>
<LangVersion>14</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/Mammoth.Extensions.DependencyInjection/Mammoth.Extensions.DependencyInjection.csproj" />
</ItemGroup>
</Project>using Mammoth.Extensions.DependencyInjection;
using Mammoth.Extensions.DependencyInjection.Configuration;
using Microsoft.Extensions.DependencyInjection;
foreach (var otherFirst in new[] { true, false })
Report(otherFirst ? "other first" : "inner first", () => {
var s = new ServiceCollection();
s.AddKeyedSingleton<IWork, Work>("other");
s.AddKeyedTransient<IWork, Work>("blue");
if (otherFirst) s.Decorate<IWork, OtherFirst>();
else s.Decorate<IWork, InnerFirst>();
using var p = s.BuildServiceProvider();
Console.WriteLine(p.GetRequiredKeyedService<IWork>("blue").GetType().Name);
});
static void Report(string path, Action action)
{
try { action(); Console.WriteLine(path + ": OK"); }
catch (Exception e) { Console.WriteLine(path + ": " + e.GetType().Name + ": " + e.Message); }
}
public interface IWork { }
public sealed class Work : IWork { }
public sealed class OtherFirst([FromKeyedServices("other")] IWork other,
IWork inner, [ServiceKey] string key) : IWork { }
public sealed class InnerFirst(IWork inner,
[FromKeyedServices("other")] IWork other, [ServiceKey] string key) : IWork { }Run dotnet run --project artifacts/review-decorator-binding/repro.csproj -c Release -f net472; repeat with net8.0, net9.0 and net10.0.
Observed versus expected
With OtherFirst, blue resolution throws No satisfiable public constructor on OtherFirst. With InnerFirst, blue resolution succeeds. Both blue and other services are registered. The failure is also present in published 0.8.0.
Do not bind the decoration inner instance to a parameter requiring a different explicit keyed dependency. Preserve explicit keyed lookup and actual ServiceKey injection independently of parameter order. Test reference identities, repeated layers, all lifetimes, inherited/explicit keys and disposal ownership.
Verification
Reviewed source: ead376e. Executed matching library targets on net472 (Windows .NET Framework 4.8.9345.0), .NET 8.0.31, .NET 9.0.20 and .NET 10.0.12. Also compared the original probes against published Mammoth 0.8.0 on net472 and .NET 10. These are executable comparison probes, not new committed regression tests. No library source changes were made.
Prepared with OpenAI Codex.
Activity
AGiorgetti commentedon Oct 8, 2026
Implemented #109 in draft PR #116, final commit
e5a2ca47bd0554a5b6da000216651b0bc144c1da.The decorator activator previously bound the supplied inner service to the first assignable parameter, even when that parameter had
FromKeyedServices. An earlier same-interface or object-typed keyed dependency could therefore consume the inner instance, causing constructor rejection or silently swapped identities. The fix excludes those attributed parameters from inner binding, retaining the existingServiceKeyexclusion. The ordinary parameter receives the inner; explicit, inherited and null-key dependencies retain their own lookup, and optional defaults remain available.The production fix is one additional cached-metadata check with an explanatory code comment; the changelog is updated under vNext Bug Fixes.
Verification:
508bbe6,ec100d8): 93 failed / 84 passed per target on unchanged production code.Awaiting your review and explicit instruction before starting another issue.
test: reproduce keyed decorator dependency binding (#109)
test: keep blue as the decoration target in rejection cases (#109)
fix: reserve keyed decorator dependencies from inner binding (#109)