Skip to content

Commit 42440b5

Browse files
github-actions[bot]Dmitry Kandiner
andauthored
[release/10.0] Fixed codegen for IEnumerable<T> binding (#123422) (#123720)
Backport of #123663 to release/10.0 /cc @tarekgh @dmitry-kandiner ## Customer Impact - [x] Customer reported - [ ] Found internally Using configuration source generator, when a user binds configuration to a class or record with a primary constructor, and one of the constructor parameters is exact of type `IEnumerable<T>`, the binding throws a `System.InvalidOperationException`, even though the binding is expected to succeed. The issue #123422 shows the customer report and more details. ## Regression - [x] Yes - [ ] No This is a regression in .NET 10.0.1. A previous servicing fix addressed several crashes, but one case was missed. That omission surfaced as this regression. The change that introduced the regression is #121325. ## Testing Manually verified the failing scenario and confirmed the fix resolves the issue. Added a regression test covering the previously failing case and ran the full test suite successfully. Reviewed related code paths to ensure no additional cases were missed. ## Risk Low, the change is targeting the specific case with specific type. The fix is localized to the failing scenario. --------- Signed-off-by: Dmitry Kandiner <dkandiner@growings.com> Co-authored-by: Dmitry Kandiner <dkandiner@growings.com>
1 parent e08f350 commit 42440b5

3 files changed

Lines changed: 33 additions & 2 deletions

File tree

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Emitter/CoreBindingHelpers.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,9 @@ void EmitBindImplForMember(MemberSpec member)
413413
if (member is ParameterSpec parameter && parameter.ErrorOnFailedBinding)
414414
{
415415
// Add exception logic for parameter ctors; must be present in configuration object.
416-
// In case of Arrays, we emit extra block to handle empty arrays. The throw block will not be `else` case at that time.
417-
EmitThrowBlock(condition: _typeIndex.GetEffectiveTypeSpec(member.TypeRef) is ArraySpec ? $"if ({member.Name} is null)" : "else");
416+
// In case of Arrays and IEnumerable<T>, we emit extra block to handle collection. The throw block will not be `else` case at that time.
417+
TypeSpec typeSpec = _typeIndex.GetEffectiveTypeSpec(member.TypeRef);
418+
EmitThrowBlock(condition: typeSpec is ArraySpec || typeSpec.IsExactIEnumerableOfT ? $"if ({member.Name} is null)" : "else");
418419
}
419420

420421
_writer.WriteLine();

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,5 +1184,11 @@ public class MyOptionsWithNullableEnumerable
11841184
public IEnumerable<int>? IEnumerableProperty { get; set; }
11851185
public string[] StringArray { get; set; }
11861186
}
1187+
1188+
internal sealed record ContainingIEnumerable
1189+
{
1190+
public NestedWithIEnumerable? Source { get; set; }
1191+
}
1192+
internal sealed record NestedWithIEnumerable(string Name, IEnumerable<string> Addresses);
11871193
}
11881194
}

src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3060,5 +3060,29 @@ public void TestBindingEmptyArrayToNullIEnumerable()
30603060
Assert.Equal(Array.Empty<int>(), result.IEnumerableProperty);
30613061
Assert.Equal(Array.Empty<string>(), result.StringArray);
30623062
}
3063+
3064+
[Fact]
3065+
public void TestBindingNestedIEnumerable()
3066+
{
3067+
string jsonConfig1 = """
3068+
{
3069+
"source": {
3070+
"name": "DemoService",
3071+
"addresses": [ "127.0.0.1" ]
3072+
}
3073+
}
3074+
""";
3075+
3076+
var configuration = new ConfigurationBuilder()
3077+
.AddJsonStream(new MemoryStream(Encoding.UTF8.GetBytes(jsonConfig1)))
3078+
.Build();
3079+
3080+
ContainingIEnumerable? result = configuration.Get<ContainingIEnumerable>();
3081+
3082+
Assert.NotNull(result);
3083+
Assert.Equal("DemoService", result.Source.Name);
3084+
Assert.Equal(1, result.Source.Addresses.Count());
3085+
Assert.Equal("127.0.0.1", result.Source.Addresses.First());
3086+
}
30633087
}
30643088
}

0 commit comments

Comments
 (0)