Skip to content
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

Creating lazy type-safe factories for safe runtime service provision #152

Open
cn-ml opened this issue Nov 27, 2023 · 2 comments
Open

Creating lazy type-safe factories for safe runtime service provision #152

cn-ml opened this issue Nov 27, 2023 · 2 comments

Comments

@cn-ml
Copy link

cn-ml commented Nov 27, 2023

Problem

I have the scenario where I want to decide at runtime which service to inject.

public interface IExampleService;
public class ExampleServiceA : IExampleService;
public class ExampleServiceB : IExampleService;

I see different solutions for this right now:

Use injected IServiceProvider

[ServiceProvider]
[Singleton<IExampleService>(Factory = nameof(ChooseExampleService))]
[Singleton<ExampleServiceA>] // No service for type 'releaser.Lib.ExampleServiceA' has been registered.
[Singleton<ExampleServiceB>] // No service for type 'releaser.Lib.ExampleServiceB' has been registered.
public partial class ExampleProvider(bool useA) {
    public IExampleService ChooseExampleService(IServiceProvider provider) => useA
        ? provider.GetRequiredService<ExampleServiceA>()
        : provider.GetRequiredService<ExampleServiceB>();
}

⚠️ This works as expected, unless I do not register the example services using the attributes, which will result in runtime errors.

Use pre-generated instances

[ServiceProvider]
[Singleton<IExampleService>(Factory = nameof(ChooseExampleService))]
[Singleton<ExampleServiceA>]
[Singleton<ExampleServiceB>]
public partial class ExampleProvider(bool useA) {
    public IExampleService ChooseExampleService(ExampleServiceA a, ExampleServiceB b) => useA ? a : b;
}

⚠️ This also works, with the benefit of being type-safe, but eager evaluated. Missing the service registrations

Proposed solution

Using your IServiceProvider<T> interface or other wrappers to lazily create instances of the service. 1

Edit: maybe using something like Lazy<T> is better suited for this purpose.

[ServiceProvider]
[Singleton<IExampleService>(Factory = nameof(ChooseExampleService))]
[Singleton<ExampleServiceA>]
[Singleton<ExampleServiceB>]
public partial class ExampleProvider(bool useA) {
    internal IExampleService ChooseExampleService(IServiceProvider<ExampleServiceA> aProvider, IServiceProvider<ExampleServiceB> bProvider) => useA
        ? aProvider.GetService()
        : bProvider.GetService();
}

✅ This allows Jab to gather the necessary information on service dependencies beforehand (i.e. in this case IExampleService depends on ExampleServiceA and ExampleServiceB during runtime), which is lost when using the plain IServiceProvider interface.
This could be a minimally invasive approach to solve this problem.

Let me know what you think of this idea. If you think this is out of scope, feel free to close this issue, it shall just be my suggestion. I just thought it might be fitting with the whole "build-time checking" and source generation aspects of this project.

Footnotes

  1. Note that the factory currently has to be internal at most, because of the accessibility level of the IServiceProvider<T>.

@pakrym
Copy link
Owner

pakrym commented Nov 28, 2023

This does make sense, I think injecting the Func<ExampleServiceA> is the way most of DI containers solve this.

@cn-ml
Copy link
Author

cn-ml commented Nov 29, 2023

This does make sense, I think injecting the Func is the way most of DI containers solve this.

Ah yes, you are right, somehow I forgot about the existence of Func<T>. That is definitely the better way to solve this regarding declared intent of the factory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants