Skip to content

ConfigurationBinder Source Generator doesn't support IEnumerable types with same functionality as reflection based binder #96652

Closed
@eerhardt

Description

@eerhardt

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:

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>:

// 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);
}

Metadata

Metadata

Assignees

Labels

Priority:1Work that is critical for the release, but we could probably ship withoutarea-Extensions-Configurationin-prThere is an active PR which will close this issue when it is mergedsource-generatorIndicates an issue with a source generator feature

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions