I have a wrapper class as below:
// this is not used for IoC purposes, but by code to cast object to known T
internal interface ITaskCompletionSourceWrapper
{
void SetResult(object result);
/* snip */
}
internal class TaskCompletionSourceGenericWrapper<TResult> : ITaskCompletionSourceWrapper
{
readonly TaskCompletionSource<TResult> tcs;
public TaskCompletionSourceGenericWrapper() =>
this.tcs = new TaskCompletionSource<TResult>(TaskCreationOptions.RunContinuationsAsynchronously);
public void SetResult(object result) => tcs.SetResult((TResult)result);
/* snip */
}
Where i currently instantiate using Activator:
var responseType = request
.GetType()
.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>))
GetGenericArguments()[0];
var wrapperType = typeof(TaskCompletionSourceGenericWrapper<>).MakeGenericType(responseType);
var tcsWrapper = this.container.GetInstance(wrapperType) as ITaskCompletionSourceWrapper;
I switched this to SimpleInjector (as may use logging and other IOC goodness) and thought all was well until I found an issue (which was due to lifestyle being shared):
/* snip responseType code from above */
var wrapperType = typeof(TaskCompletionSourceGenericWrapper<>).MakeGenericType(responseType);
var tcsWrapper = this.container.GetInstance(wrapperType) as ITaskCompletionSourceWrapper;
// i can see the type has been registered as scoped, but we need it to be unique
[30] = ServiceType = TaskCompletionSourceGenericWrapper<Object>, Lifestyle = Async Scoped
The above is being called within a AsyncScopedLifestyle.BeginScope
block.
I would like to instantiate this wrapper at runtime using SimpleInjector (but ensuring Lifestyle.Transient
):
The types for the wrapper are pretty much arbitrary from int
, string
, bool
etc but could also be simple POCO classes.
This is similar to a case of open generic registration but I would rather avoid having other developers specify assemblies for the simple types (as for sure they will forget, and their classes are simple anyways - command/query POCOs and the like).
How can I achieve the above please or am I forced to register collection?
Or is this deeper with lifestyle mismatches - I hope not, since the wrapper has no dependencies (and will not other than ILogger perhaps) is only doing something instantiation of a simple class (which I want to replace Activator rather than decouple components IoC style).
Based on their docs Batch-Registration / Auto-Registration and experience, you need to register the implementation of the interface when using dependency injection.
var container = new Container();
// If related classes exist in a different assembly then add them here
var assemblies = new[] { typeof(TaskCompletionSourceGenericWrapper<>).Assembly };
container.Collection.Register(typeof(TaskCompletionSourceGenericWrapper<>), assemblies, Lifestyle.Transient);
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
One hack that I can think of but haven't tested is to create a wrapper class that returns a deep copy a.k.a clone of your wrapper. Returning a clone of an object is similar to transient as it returns a new instance every time you request for it but the downside on this approach is that you can only do it for serializable classes so you may need to add [Serializable()]
. Refer to Deep cloning objects for more info.
public interface IUglyDuckling
{
ITaskCompletionSourceWrapper GetITaskCompletionSourceWrapper(Type type);
}
public class UglyDuckling : IUglyDuckling
{
public ITaskCompletionSourceWrapper GetITaskCompletionSourceWrapper(Type type)
{
var wrapperType = typeof(TaskCompletionSourceGenericWrapper<>).MakeGenericType(type);
var tcsWrapper = this.container.GetInstance(wrapperType) as ITaskCompletionSourceWrapper;
return ObjectCopier.Clone<ITaskCompletionSourceWrapper>(tcsWrapper);
}
}