- Required Service Type
Required service type identifies the registered service type when resolving or injecting things, when the type is different from the resolution service type.
Better illustrated with examples.
namespace DryIoc.Docs;
using DryIoc;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
public class Required_service_type_is_implemented_by_resolution_type
{
interface IFoo { }
class Foo : IFoo { }
[Test]
public void Example()
{
var container = new Container();
container.Register<Foo>();
var f = container.Resolve<IFoo>(requiredServiceType: typeof(Foo));
Assert.IsInstanceOf<Foo>(f);
}
}
Where Foo
is required (registered) service type and IFoo
is a resolved type.
Resolve will throw an exception if Foo
does not implement IFoo
.
Note: Required service type always refers to service type and not to implementation type.
Required service type identifies a wrapped service type when resolving a Wrapper
public class Service_type_for_a_wrapper
{
class Foo { }
[Test]
public void Example()
{
var container = new Container();
container.Register<Foo>();
var fooObjects = container.Resolve<IEnumerable<object>>(typeof(Foo));
Assert.IsInstanceOf<Foo>(fooObjects.Single());
}
}
We are resolving collection of objects IEnumerable<object>
where an object required to ba a Foo
.
Works with nested wrappers as well, e.g. IEnumerable<Func<object>>
public class Select_to_use_and_open_generic_type
{
interface IFoo<T> { }
class FooInt : IFoo<int> { }
class Foo<T> : IFoo<T> { }
[Test] public void Example()
{
var container = new Container();
container.Register<IFoo<int>, FooInt>();
container.Register(typeof(IFoo<>), typeof(Foo<>));
var f = container.Resolve<IFoo<int>>();
Assert.IsInstanceOf<FooInt>(f);
// using required service type to resolve Foo<int>
var f2 = container.Resolve<IFoo<int>>(typeof(IFoo<>));
Assert.IsInstanceOf<Foo<int>>(f2);
// AGAIN, Important that required service type should specify
// a service type and not the implementation type.
// The below Resolve won't work cause the `Foo<>` is implementation type.
var f3 = container.Resolve<IFoo<int>>(typeof(Foo<>), IfUnresolved.ReturnDefault);
Assert.IsNull(f3);
}
}
To specify required service type for injected dependency you may use Made.Of
expression:
container.Register<Bar>(Made.Of(() => new Bar(Arg.Of<IDisposable, IFoo>()));
Where Bar
expects IDisposable
and we are injecting IFoo
required service type.
Given the service registration: container.Register<IFoo, Foo>()
and GenericHandler
class in external library that expects an object
dependency:
class GenericHandler
{
public readonly object Target;
public GenericHandler(object target)
{
Target = target;
}
}
How to configure GenericHandler
to use IFoo
for the object
dependency?
First, let's use a RegisterDelegate
as a generally available technique in many IoC Containers:
public class Using_register_delegate_to_adapt_service_type
{
interface IFoo { }
class Foo : IFoo { }
[Test]
public void Example()
{
var container = new Container();
container.Register<IFoo, Foo>();
container.RegisterDelegate(r => new GenericHandler(r.Resolve<IFoo>()));
var handler = container.Resolve<GenericHandler>();
Assert.IsInstanceOf<Foo>(handler.Target);
}
}
Seems fine, but what if GenericHandler
has many more dependencies which are also available from container,
then we need to specify Resolve for all of them.
But the main point is the delegate registration (though powerful) is the "black box" for the container, and may lead to problems when used wrong:
- Memory leaks by capturing variable into delegate closure and keeping them for container lifetime.
- Container is unable to see what's inside delegate, which makes it hard to find a lifestyle mismatch or diagnose other problems.
Let's use required service type:
public class Required_service_type_to_adapt_the_object_dependency
{
interface IFoo { }
class Foo : IFoo { }
[Test]
public void Example()
{
var container = new Container();
container.Register(Made.Of(() => new GenericHandler(Arg.Of<IFoo>())));
container.Register<IFoo, Foo>();
var handler = container.Resolve<GenericHandler>();
Assert.IsInstanceOf<IFoo>(handler.Target);
}
}
Here, we are using Made.Of
specification expression saying to use required service type IFoo
as argument.
Made.Of
looks a very similar to RegisterDelegate but actually its argument is not a delegate, but an
ExpressionTree parsed by Container to get a
registration information. The Made.Of<T>(...)
is similar to Register<T>(...)
where
DryIoc retrieves the information about T
and do its "magic", but with the Made.Of<T>
it is
possible to provide statically checked expression, which won't compile if provided wrong.
Another interesting case is using required service type with Wrappers.
Basically, required service type will be propagated inside wrapper the same way as service key. That's allow us to specify a target service type inside a wrapper.
Imaging, the GenericHandler
expects a Lazy<object>
instead of an object
in its constructor:
public class Required_service_type_with_wrapper
{
public class GenericHandler
{
public object Target => _target.Value;
public GenericHandler(Lazy<object> target)
{
_target = target;
}
private readonly Lazy<object> _target;
}
interface IFoo { }
class Foo : IFoo { }
[Test]
public void Example()
{
var container = new Container();
container.Register<IFoo, Foo>();
container.Register(Made.Of(() => new GenericHandler(Arg.Of<Lazy<object>, IFoo>())));
// Resolution remain the same
var handler = container.Resolve<GenericHandler>();
Assert.IsInstanceOf<IFoo>(handler.Target);
}
}
Here in Arg.Of<Lazy<object>, IFoo>()
DryIoc will look for required service type IFoo
instead of object
inside the Lazy<>
wrapper.
IEnumerable and the rest of supported collection types are also Wrappers, so you may expect required service type to work with them too:
public class Required_service_type_in_collection
{
interface IDigit { }
class One : IDigit { }
class Two : IDigit { }
[Test]
public void Example()
{
var container = new Container();
container.Register<IDigit, One>();
container.Register<IDigit, Two>();
// Get all digits as objects:
container.Resolve<IEnumerable<object>>(requiredServiceType: typeof(IDigit));
}
}
Note: Examples with Lazy and IEnumerable give the same vibe as Variance for open-generic types in .NET 4.0 and higher and DryIoc is supporting this functionality starting from .NET 3.5.