You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Configuration binder source generator silently binds null for a nested type whose sole member is a read-only collection constructor parameter #131399
If a bound type has a member whose own type's sole member is a read-only collection constructor parameter (IReadOnlyList<T>, IReadOnlyCollection<T>, IReadOnlySet<T>, or IEnumerable<T>, with no other bindable property), the source generator binds that nested member to null instead of the real value. No exception, no diagnostic. Top-level use of the same shape (the one #131358 fixes) works correctly; only the nested case, reached through BindCore/EmitObjectInit instead of GetCore, is affected.
Reproduction steps
usingMicrosoft.Extensions.Configuration;usingSystem.Collections.Generic;publicclassProgram{publicstaticvoidMain(){ConfigurationBuilderconfigurationBuilder=new();configurationBuilder.AddInMemoryCollection(newDictionary<string,string?>{["Nested:Values:0"]="a",["Nested:Values:1"]="b",});IConfigurationconfig=configurationBuilder.Build();Outerouter=config.Get<Outer>();// outer.Nested is non-null, but outer.Nested.Values is null.System.Console.WriteLine(outer.Nested.Valuesisnull?"Values is null":"Values is bound");}}publicrecordInner(IReadOnlyList<string>Values);publicrecordOuter(InnerNested);
Needs a project with the config binder source generator active (AOT/trimming context, or EnableConfigurationBindingGenerator=true).
Expected behavior
outer.Nested.Values contains ["a", "b"], matching what reflection-based binding (Microsoft.Extensions.Configuration.Binder without the generator) produces for the same shape.
Actual behavior
outer.Nested.Values is null, with no exception or generator diagnostic.
Root cause
In src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs, EmitBindImplForMember's early-return guard:
if(!_typeIndex.HasBindableMembers(complexType)&&!_typeIndex.GetEffectiveTypeSpec(complexType).IsValueType&&complexTypeis not CollectionSpec&&((ObjectSpec)complexType).InstantiationStrategy==ObjectInstantiationStrategy.ParameterizedConstructor){returnfalse;}
skips emitting the member assignment whenever HasBindableMembers is false, without accounting for types that are constructible via ParameterizedConstructor even without other bindable members (the same condition #131358 fixes for the top-level/GetCore path). Initialize<Inner> is generated correctly and CanInstantiate is true for Inner, but nothing ever calls it here, so the member silently stays at its default (null).
Confirmed via git diff against the commit before #131358 that this code path is untouched by that PR, so it's a pre-existing gap, not a regression.
Regression?
No, pre-existing.
Other information
Found while adding nested-usage test coverage during review of #131358, which fixes the equivalent top-level (GetCore) case for the same "sole read-only collection ctor param" shape. This issue tracks the BindCore/EmitObjectInit (nested) path separately, per @tarekgh's request on that PR.
Activity
Give each LoadAndInvokeMain compilation a unique assembly name
dotnet-policy-service commentedon Jul 26, 2026
Tagging subscribers to this area: @dotnet/area-extensions-configuration
See info in area-owners.md if you want to be subscribed.
Fix nested readonly collection ctor parameters (#131932)