Resolving services
Once a container is declared, you ask it for services. This page covers the resolve surface. Most of the time you inject dependencies through constructors and never call resolve yourself, but the entry points matter at the edges of your app.
Resolve
Resolve<T>() returns the service or throws if it is not registered.
using CoffeeShop.Root shop = new();
Cup cup = shop.Resolve<Cup>();
When the receiver is the generated Root or Scope, Awaiten knows at compile time which container answers, so it warns about a call that would throw: a service the container does not provide (AWT218), or one it does not hand out through that call, such as an async-initialized service on a synchronous Resolve (AWT219). This covers the typeof(...) and keyed overloads below too. A call through IAwaitenRoot, with a type parameter or with a key only known at run time is checked at run time only.
There is a Type-based overload for when you do not know the type at compile time.
object cup = shop.Resolve(typeof(Cup));
TryResolve
TryResolve<T>(out T?) returns false instead of throwing when the service is not available.
if (shop.TryResolve<Cup>(out Cup? cup))
{
// use cup
}
For an async-tainted service in the strict default, TryResolve reports false, because that service has no synchronous path. See Async initialization.
ResolveAsync
ResolveAsync<T>(CancellationToken) constructs the service, awaits its initialization, and returns it ready to use. This is the entry point for async services.
EspressoMachine machine = await shop.ResolveAsync<EspressoMachine>(cancellationToken);
Keyed resolution
Each of the three methods has an overload that takes the Key a registration was declared with. This is the imperative counterpart to [FromKey] at an injection site, and it selects the same registration.
IMilk oat = shop.Resolve<IMilk>("Oat");
if (shop.TryResolve<IMilk>(MilkKind.Whole, out IMilk? whole))
{
// use whole
}
IMilk soy = await shop.ResolveAsync<IMilk>("Soy", cancellationToken);
The key is an object, so a string, an enum constant, or a typeof(...) all work, matching the key the registration carries. A null key resolves the unkeyed registration, exactly like the keyless overload. An unknown key follows the same miss rules as above: Resolve throws, TryResolve reports false. See Keyed services.
Prefer constructor injection
You rarely call these methods deep in your code. You call them once at the composition root to pull out the top-level service, and everything below is wired by its constructors. A Barista that needs a Grinder just declares it:
public sealed class Barista(Grinder grinder, EspressoMachine machine);
For the shapes a dependency can take, like Func<T> or collections, read on.
Where to go next
- Relationships for
Func,Lazy, andTask. - Collections for many implementations at once.
- Property injection for filling properties instead of constructor parameters.