In one of my projects I needed a simple dependency incjection activator so I've built one. It performs only type/inheritance matching because I'm not creating a second Autofac and I think I won't need anything more complex any time soon (aka YAGNI).
To find the dependencies I use a Join
with a custom comparer. It runs over constructor parameters and tries to match a dependency and maintain the order of the parameters. If the parameters and dependencies counts don't match I use Except
to find the missing ones and put their names in the exception message.
I don't use a DI container because I use it for creating commands based on the command line.
public interface IDependencyInjectionActivator
{
object CreateInstance(Type type, params object[] candidates);
}
public class DependencyInjectionActivator : IDependencyInjectionActivator
{
private static readonly IEqualityComparer<Type> TypeInheritanceComparer =
AdHocEqualityComparer<Type>
.CreateWithoutHashCode((candidate, parameter) => parameter.IsAssignableFrom(candidate));
public object CreateInstance(Type type, params object[] candidates)
{
var constructor = type.GetConstructors().Single();
var parameters =
constructor
.GetParameters()
.Join(
candidates,
parameter => parameter.ParameterType,
candidate => candidate.GetType(),
(parameter, candidate) => candidate,
TypeInheritanceComparer
).ToArray();
var dependenciesResolved = (parameters.Length == constructor.GetParameters().Length);
if (dependenciesResolved)
{
return Activator.CreateInstance(type, parameters);
}
var missingDependencies =
constructor
.GetParameters()
.Select(p => p.ParameterType)
.Except(parameters.Select(p => p.GetType()), TypeInheritanceComparer)
.ToList();
var message =
$"Some dependecies for {type.ToPrettyString().QuoteWith("'")} could not be resolved: " +
$"{missingDependencies.Select(t => t.ToPrettyString()).Join(", ").EncloseWith("[]")}";
throw DynamicException.Factory.CreateDynamicException($"MissingDependency{nameof(Exception)}", message, null);
}
}
The compare I use here takes two Func
s for each interface method. Factory methods make the creation easier.
public class AdHocEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _equals;
private readonly Func<T, int> _getHashCode;
private AdHocEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
{
_equals = equals;
_getHashCode = getHashCode;
}
public static IEqualityComparer<T> CreateWithoutHashCode([NotNull] Func<T, T, bool> equals)
{
if (equals == null) throw new ArgumentNullException(nameof(@equals));
return Create(equals, _ => 0);
}
public static IEqualityComparer<T> Create([NotNull] Func<T, T, bool> equals, [NotNull] Func<T, int> getHashCode)
{
if (equals == null) throw new ArgumentNullException(nameof(equals));
if (getHashCode == null) throw new ArgumentNullException(nameof(getHashCode));
return new AdHocEqualityComparer<T>(equals, getHashCode);
}
public bool Equals(T x, T y)
{
if (ReferenceEquals(null, x)) return false;
if (ReferenceEquals(null, y)) return false;
if (ReferenceEquals(x, y)) return true;
return _equals(x, y);
}
public int GetHashCode(T obj) => _getHashCode(obj);
}
This time without examples because I think the usage is strightforward. You create and instance of the activator, specify the type you want to create and pass an array of dependency caditates.
Do you think this DI is smart enough or does it need any improvements?