and we forgot to register B, we get the error on class A (which is, per se, OK).
The solution (proposed) is to instantiate recursively all the services, and to build for each (failed) instantiation a histogram: the more points we get for a service, the "more" needed it is, the probability to
have there the "injection-problem" will be bigger. For the previous example A will get 1 point and B 2 points.
And here is the source:
public class Memoize
private readonly Func
public Memoize(Func
_func = func;
}
private IDictionary
public R Call(T value) {
R result;
if (_cache.ContainsKey(value)) {
result = _cache[value];
} else {
result = _func(value);
_cache[value] = result;
}
return result;
}
}
class InjectionExplorer {
private Func<IEnumerable<Type>, Type> _greediest_ctor_params;
public InjectionExplorer() {
_greediest_ctor_params = new Memoize<IEnumerable<Type>, Type>(GreediestConstructorParametersWorker).Call;
}
public IEnumerable<Type> RequiredTypes(Type t) {
return _greediest_ctor_params(t);
}
private ConstructorInfo Greediest(Type t) {
ConstructorInfo greediest = null;
int greediest_param_length = -1;
foreach (ConstructorInfo ctor in t.GetConstructors()) {
if (ctor.GetParameters().Length > greediest_param_length) {
greediest = ctor;
greediest_param_length = ctor.GetParameters().Length;
}
}
if (greediest == null) { throw new Exception("No ctor was found"); }
return greediest;
}
private IEnumerable<Type> GreediestConstructorParametersWorker(Type t) {
ConstructorInfo ctor = Greediest(t);
return Enumerables.Do.Map<Type, ParameterInfo>(ctor.GetParameters(), delegate(ParameterInfo info) { return info.ParameterType; });
}
}
class InjectionDoctor {
private readonly IEnumerable<Type> _types;
private readonly Func<object, Type> _instantiate;
private readonly InjectionExplorer _explorer;
private readonly IDictionary<Type, int> _failed_instantiations;
public InjectionDoctor(IEnumerable<Type> types, Func<object, Type> instantiate) {
_types = types;
_instantiate = instantiate;
_explorer = new InjectionExplorer();
_failed_instantiations = new Dictionary<Type, int>();
}
public void Examine() {
Enumerables.Do.ForEach(_types, TryInstantiate);
}
public void PrettyPrint() {
string print_format = "{0} -> {1}";
Console.WriteLine(print_format, "Type", "Failed Instatiation Count");
foreach (KeyValuePair<Type, int> failed_instantiation in SortFailedInstatiationsByFailedCount()) {
Console.WriteLine(print_format, failed_instantiation.Key, failed_instantiation.Value);
}
}
private IEnumerable<KeyValuePair<Type,int>> SortFailedInstatiationsByFailedCount() {
List<KeyValuePair<Type, int>> pairs = new List<KeyValuePair<Type, int>>();
foreach (KeyValuePair<Type, int> failed_instantiation in _failed_instantiations) {
pairs.Add(failed_instantiation);
}
pairs.Sort(delegate(KeyValuePair<Type, int> x, KeyValuePair<Type, int> y) { return y.Value.CompareTo(x.Value); });
return pairs;
}
private Type Instantiable(Type candidate) {
if (candidate.IsArray) {
return Instantiable(candidate.GetElementType());
}
return candidate;
}
private void TryInstantiate(Type candidate) {
Type instantiable = Instantiable(candidate);
if (HasFailedInstatiations(instantiable)) {
CountFailedInstantiation(instantiable);
return;
}
try {
_instantiate(instantiable);
} catch (Exception) {
CountFailedInstantiation(instantiable);
TryInstatiateRequiredTypes(instantiable);
}
}
private void TryInstatiateRequiredTypes(Type candidate) {
Enumerables.Do.ForEach(_explorer.RequiredTypes(candidate), TryInstantiate);
}
private bool HasFailedInstatiations(Type candidate) {
return _failed_instantiations.ContainsKey(candidate);
}
private void CountFailedInstantiation(Type candidate) {
if (! HasFailedInstatiations(candidate)) { _failed_instantiations[candidate] = 0; }
_failed_instantiations[candidate] += 1;
}
public int FailedInstatiations
if (! HasFailedInstatiations(typeof(T))) { return 0; }
return _failed_instantiations[typeof (T)];
}
}
Unit-Test sample:
class B {
private readonly C _c;
public B(C c) {
_c = c;
throw new Exception("poof");
}
}
class C {
public C() {
throw new Exception("poof");
}
}
class D {
}
class E {
private readonly C[] _cs;
public E(C[] cs) {
_cs = cs;
}
}
class A {
private readonly B _b;
private readonly D _d;
public A(B b, D d) {
_b = b;
_d = d;
}
}
[TestClass]
public class InjectionDoctorSpecifications {
private DefaultPicoContainer _container;
private InjectionDoctor _doctor;
[TestInitialize]
public void BeforeEachTest() {
_container = new DefaultPicoContainer();
}
private void Register(IEnumerable<Type> types) {
foreach (Type t in types) {
_container.RegisterComponent(new CachingComponentAdapter(new ConstructorInjectionComponentAdapter(t)));
}
}
[TestMethod]
public void should_detect_instantiation_failures() {
IEnumerable<Type> types = new Type[] {typeof (A), typeof (B), typeof (C), typeof (D), typeof(E)};
Register(types);
_doctor = new InjectionDoctor(types, _container.GetComponentInstanceOfType);
_doctor.Examine();
//_doctor.PrettyPrint();
Assert.AreEqual(1, _doctor.FailedInstatiations<A>());
Assert.AreEqual(2, _doctor.FailedInstatiations<B>());
Assert.AreEqual(3, _doctor.FailedInstatiations<C>());
Assert.AreEqual(0, _doctor.FailedInstatiations<D>());
Assert.AreEqual(1, _doctor.FailedInstatiations<E>());
}
}
The output of the PrettyPrint looks like:
Type -> Failed Instatiation Count
Tests.C -> 3
Tests.B -> 2
Tests.E -> 1
Tests.A -> 1
Additionally we could add a method bool InjectionDoctor.HaveAllInstantionsSucceded()
ps. The Blogger Code Formatter is not playing nice with the generics: the InjectionDoctor takes as parameter in constructor a function f(Type) -> object. In this way we make the InjectionDoctor container-agnostic.
pps. source-code on request.
No comments:
Post a Comment