- 
                Notifications
    You must be signed in to change notification settings 
- Fork 5.2k
Description
Description
When a class implements IEnumerable, but not ICollection<T>, the ConfigurationBinder source generator is emitting code with different behavior than the reflection based binder uses.
Reproduction Steps
csproj:
<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
    <PackageReference Include="Confluent.Kafka" Version="2.3.0" />
  </ItemGroup>
</Project>appsettings.json:
{
  "Config": {
    "DeliveryReportFields": "one,two,three",
    "EnableDeliveryReports": false
  }
}Program.cs:
using Confluent.Kafka;
var builder = Host.CreateApplicationBuilder(args);
var producerConfig = builder.Configuration.GetSection("Config").Get<ProducerConfig>()!;
Console.WriteLine($"DeliveryReportFields: '{producerConfig.DeliveryReportFields}', should be 'one,two,three'");
Console.WriteLine($"EnableDeliveryReports: '{producerConfig.EnableDeliveryReports}', should be 'False'");Toggle the EnableConfigurationBindingGenerator setting between true and false.
Expected behavior
The behavior of the app should be the same whether you are setting EnableConfigurationBindingGenerator to false or true.
Actual behavior
When the Source Generator is not used:
DeliveryReportFields: 'one,two,three', should be 'one,two,three'
EnableDeliveryReports: 'False', should be 'False'
When the Source Generator is used:
Unhandled exception. System.NotSupportedException: Unable to bind to type 'Confluent.Kafka.ProducerConfig': generator did not detect the type as input.
   at Microsoft.Extensions.Configuration.Binder.SourceGeneration.<BindingExtensions_g>F40008862DA0CE1CF1C594A476014B455F2B40704BA5739597A906A2663453198__BindingExtensions.GetCore(IConfiguration configuration, Type type, Action`1 configureOptions) in C:\DotNetTest\ConfigBinderTest\Microsoft.Extensions.Configuration.Binder.SourceGeneration\Microsoft.Extensions.Configuration.Binder.SourceGeneration.ConfigurationBindingGenerator\BindingExtensions.g.cs:line 57
   at Microsoft.Extensions.Configuration.Binder.SourceGeneration.<BindingExtensions_g>F40008862DA0CE1CF1C594A476014B455F2B40704BA5739597A906A2663453198__BindingExtensions.Get[T](IConfiguration configuration) in C:\DotNetTest\ConfigBinderTest\Microsoft.Extensions.Configuration.Binder.SourceGeneration\Microsoft.Extensions.Configuration.Binder.SourceGeneration.ConfigurationBindingGenerator\BindingExtensions.g.cs:line 35
   at Program.<Main>$(String[] args) in C:\DotNetTest\ConfigBinderTest\Program.cs:line 4
Regression?
No response
Known Workarounds
No response
Configuration
No response
Other information
It appears the difference is this code:
Lines 781 to 782 in f21dc6c
| private bool IsCollection(ITypeSymbol type) => | |
| type is INamedTypeSymbol namedType && GetInterface(namedType, _typeSymbols.IEnumerable) is not null; | 
The source generator considers anything that implements IEnumerable to be a "collection".
vs the reflection-based binder, which only considers IDictionary and ICollection<T>:
runtime/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs
Lines 408 to 426 in f21dc6c
| // At this point we know that we have a non-null bindingPoint.Value, we just have to populate the items | |
| // using the IDictionary<> or ICollection<> interfaces, or properties using reflection. | |
| Type? dictionaryInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type); | |
| if (dictionaryInterface != null) | |
| { | |
| BindDictionary(bindingPoint.Value, dictionaryInterface, config, options); | |
| } | |
| else | |
| { | |
| Type? collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type); | |
| if (collectionInterface != null) | |
| { | |
| BindCollection(bindingPoint.Value, collectionInterface, config, options); | |
| } | |
| else | |
| { | |
| BindProperties(bindingPoint.Value, config, options); | |
| } |