-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for enumerating keyed services with KeyedService.AnyKey #95582
Add support for enumerating keyed services with KeyedService.AnyKey #95582
Conversation
Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection Issue DetailsMy take on how we should fix #95308 Registering "keyed" service with a
|
FWIW, that still doesn't make sense to me, given how it works today. You can call GetKeyedServices with a null key and it returns all services registered without a key and with a null key. We made null a valid key. It's logical then that AnyKey includes null keys and therefore no keys. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than the null thing, lgtm. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some test questions, but otherwise LGTM
...ns.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs
Show resolved
Hide resolved
...ns.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs
Show resolved
Hide resolved
...ns.DependencyInjection.Specification.Tests/src/KeyedDependencyInjectionSpecificationTests.cs
Show resolved
Hide resolved
It still doesn't make sense to me that: sp.GetKeyedService<T>(null) can return something that: sp.GetKeyedServices<T>() will not include. |
Regardless, thank you for fixing this; will you handle backporting it as well? |
Yes this will be backported. |
/backport to release/8.0-staging |
Started backporting to release/8.0-staging: https://github.com/dotnet/runtime/actions/runs/7167493983 |
Maybe we shouldn't have permitted |
Maybe... but we did. |
@benjaminpetit can you update the original issue to make sure other DI container authors see this change |
I agree with @benjaminpetit that a null key is equivalent to no key. It's no different from calling Are there any tests for the non-plural I'm fine with the singular var sc1 = new ServiceCollection();
sc1.AddKeyedSingleton<IService>("first-service", new Service("first-service"));
var sp1 = sc1.BuildServiceProvider();
Console.WriteLine($"sp1 Name: {sp1.GetKeyedService<IService>(KeyedService.AnyKey)?.Name ?? "null"}");
Console.WriteLine($"sp1 Names: {string.Join(", ", sp1.GetKeyedServices<IService>(KeyedService.AnyKey).Select(s => s.Name))}");
Console.WriteLine();
var sp2 = sc1.BuildServiceProvider();
Console.WriteLine($"sp2 Names: {string.Join(", ", sp2.GetKeyedServices<IService>(KeyedService.AnyKey).Select(s => s.Name))}");
Console.WriteLine($"sp2 Name: {sp2.GetKeyedService<IService>(KeyedService.AnyKey)?.Name ?? "null"}");
class Service(string name) : IService
{
public string Name => name;
}
interface IService
{
string Name { get; }
} Output:
And then things get even weirder with an var sc3 = new ServiceCollection();
sc3.AddKeyedSingleton<IService>(KeyedService.AnyKey, (sp, key) => new Service($"Any '{key}'"));
sc3.AddKeyedSingleton<IService>("first-service", new Service("first-service"));
var sp3 = sc3.BuildServiceProvider();
Console.WriteLine($"sp3 Name: {sp3.GetKeyedService<IService>(KeyedService.AnyKey)?.Name ?? "null"}");
Console.WriteLine($"sp3 Names: {string.Join(", ", sp3.GetKeyedServices<IService>(KeyedService.AnyKey).Select(s => s.Name))}");
Console.WriteLine();
var sp4 = sc3.BuildServiceProvider();
Console.WriteLine($"sp4 Names: {string.Join(", ", sp4.GetKeyedServices<IService>(KeyedService.AnyKey).Select(s => s.Name))}");
Console.WriteLine($"sp4 Name: {sp4.GetKeyedService<IService>(KeyedService.AnyKey)?.Name ?? "null"}"); Output:
Another thing I dislike about this change is that plural resolution for singleton and scoped services registered using the That's why I think it's better not to include the I'm also not sure it makes sense to return multiple services registered using the same specific key when doing plural In any case where applications are resolving multiple services for the same key, the application/library/framework author should know what keys are relevant and resolve them directly. If the application needs to be able to handle an arbitrary number of keys that are unknown even at resolution time, it's not clear why you couldn't give each service a unique key. @davidfowl @stephentoub Do you think we should discuss this with the DI council? |
After discussing this a bit more with @stephentoub, I can see the reason in including the non/null-keyed service (if any) in the Then there's the question of what happens for
|
@halter73 yeah it seems I missed this when implementing this PR. But that's why it wasn't implemented in the first place. I will open a PR fixing the first issue. About not including the registration with I think it's simpler and easier to explain to not return non-keyed services in |
My take on how we should fix #95308
Registering "keyed" service with a
null
key was supposed to be possible for convenience, however it will be considered as a non-keyed service. That's why I think we should not return them when enumerating services.