dotnet / dotnet/runtime

`IServiceProviderIsService`'s behavior is unsound for constrained open generics

Open
#125,926 2 comments 0 reactions 0 assignees View on GitHub
area-Extensions-DependencyInjection
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

There is a circumstance in which `IServiceProviderIsService` returns `true` even though the container would NOT, in fact, resolve any services for the specified `Type`. This defeats the point of `IServiceProviderIsService` in such cases.

### Reproduction Steps

Consider the following scenario:
```cs
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
// services.AddScoped(typeof(IFoo<>), typeof(FooForEverything<>));
services.AddScoped(typeof(IFoo<>), typeof(FooForSpecials<>));

var sp = services.BuildServiceProvider();

var isService = sp.GetRequiredService();
Console.WriteLine($"IFoo has services? {isService.IsService(typeof(IFoo))}");
Console.WriteLine($"IFoo has services? {isService.IsService(typeof(IFoo))}");

using var scope = sp.CreateScope();

var one = scope.ServiceProvider.GetServices>();
Console.WriteLine($"For IFoo: ({one.Count()}) {string.Join(", ", one)}");

var two = scope.ServiceProvider.GetServices>();
Console.WriteLine($"For IFoo: ({two.Count()}) {string.Join(", ", two)}");

public interface IFoo;

public sealed class FooForEverything : IFoo;
public sealed class FooForSpecials : IFoo
where T : ISpecial;

public interface ISpecial;
public sealed class Special : ISpecial;
```

### Expected behavior

One would expect that since the line adding `FooForEverything<>` to the DI container has been commented out, and thus no services will be returned from `GetServices>`, that `isService.IsService(typeof(IFoo))` would return `false`. But it returns `true`, which is wrong.

And notably, this is how it works in AutoFac.

### Actual behavior

`isService.IsService(typeof(IFoo))` returns `true`, but it's lying.

This will be the console output if you ran the program above:
```
IFoo has services? True
IFoo has services? True
For IFoo: (1) FooForSpecials`1[Special]
For IFoo: (0)
```
Notice how `IFoo has services? True` and `For IFoo: (0)` are contradictory.

### Regression?

_No response_

### Known Workarounds

_No response_

### Configuration

_No response_

### Other information

cc @davidfowl

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.