Skip to content

ConfigurationBinder fails to bind empty arrays to nullable IEnumerable properties without default values #121193

@digaomatias

Description

@digaomatias

Description

In .NET 10 RC2, the ConfigurationBinder throws an exception when attempting to bind an empty array [] from JSON configuration to a nullable IEnumerable<T>? property that has no default value. Non-empty arrays bind successfully to the same property type.

This appears to be a regression introduced by the .NET 10 fix for empty array handling (see dotnet/runtime#58930 and the breaking change documented at https://learn.microsoft.com/en-us/dotnet/core/compatibility/extensions/10.0/configuration-null-values-preserved). That change was made in Support Null configuration (#116677).

Reproduction Steps

1. Create a configuration class:

public class MyServiceOptions
{
    public const string Key = "MyService";

    // This property causes the crash when binding empty array
    public IEnumerable<int>? ExcludedIds { get; set; }
}

2. Add configuration in appsettings.json:

{
  "MyService": {
    "ExcludedIds": []
  }
}

3. Register the options in Startup.cs or Program.cs:

services.Configure<MyServiceOptions>(
    context.Configuration.GetSection(MyServiceOptions.Key)
);

4. Inject and use the options in a service:

public class MyService
{
    private readonly MyServiceOptions _options;

    public MyService(IOptions<MyServiceOptions> options)
    {
        _options = options.Value; // CRASH happens here
    }
}

5. Run the application

Result: Application crashes when attempting to resolve IOptions<MyServiceOptions>.Value with:

System.ArgumentNullException: Value cannot be null. (Parameter 'elementType')
   at System.Array.CreateInstance(Type elementType, Int32 length)
   at Microsoft.Extensions.Configuration.ConfigurationBinder.BindInstance(...)

Expected behavior

Empty arrays in configuration should bind successfully to nullable IEnumerable<T>? properties, either as:

  • An empty collection (e.g., Array.Empty<int>())
  • null

This is consistent with how non-empty arrays are handled and how other configuration values work.

Comparison of Scenarios

Configuration Value Property Type Has Default Value Result
[] IEnumerable<int>? No CRASH
[1] IEnumerable<int>? No Works
[] IEnumerable<int>? = null Works
[] IEnumerable<int> = [] Works
[] List<int>? No Works
null IEnumerable<int>? No Works

Actual behavior

The ConfigurationBinder attempts to instantiate IEnumerable<T> directly when encountering an empty array, which fails because IEnumerable<T> is an interface and cannot be instantiated.

Regression?

Works fine in .NET 8, breaks in .NET 10, very likely to be related to this fix: https://learn.microsoft.com/en-us/dotnet/core/compatibility/extensions/10.0/configuration-null-values-preserved).

Known Workarounds

Option 1: Add a default value

public IEnumerable<int>? ExcludedIds { get; set; } = null;
// or
public IEnumerable<int> ExcludedIds { get; set; } = [];

Option 2: Use concrete type

public List<int>? ExcludedIds { get; set; }

Option 3: Change configuration to null

{
  "ExcludedIds": null
}

Configuration

  • .NET Version: 10.0.100-rc.2.25502.107
  • OS: macOS (Darwin 25.0.0), but reproducible across platforms
  • Configuration Package: Microsoft.Extensions.Configuration 10.0.0-rc.2.25502.107
  • Binder Package: Microsoft.Extensions.Configuration.Binder (via Microsoft.Extensions.Hosting)

Other information

Root Cause Analysis

When the ConfigurationBinder encounters:

  • Non-empty array (e.g., [1]): Creates a concrete int[] and assigns it to the IEnumerable<int>? property (Works)
  • Empty array ([]): Attempts to instantiate the property type (IEnumerable<int>) directly, which fails (Crashes)

The binder appears to take different code paths for empty vs. non-empty arrays, with the empty array path incorrectly attempting to instantiate the abstract interface type.

Additional Context

This issue is particularly problematic because:

  1. Silent failure pattern: Works fine in .NET 8, breaks in .NET 10
  2. Inconsistent behavior: Non-empty arrays work, empty arrays don't
  3. Common pattern: Nullable IEnumerable<T>? without defaults is a common C# pattern for optional collections
  4. Migration blocker: Requires code changes to properties that previously worked fine

Related Issues

Suggested Fix

The ConfigurationBinder should handle empty arrays consistently with non-empty arrays by creating a concrete collection type (e.g., Array.Empty<T>()) rather than attempting to instantiate the property's interface type.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions