5
\$\begingroup\$

Basically, what I mean by illumination is having a method create an object where the caller supplies all the dependencies. This allows us to hide our new keyword behind a dependency on an Illuminator, allowing us to much more easily test our code.

Yes, a little bit of reflection is involved, but not too much (IMO).

Please tell me what you think of this type (is the intent behind it clear? et alia):

namespace xofz.Illumination
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    public class Illuminator
    {
        public virtual T Illumine<T>(params object[] dependencies) where T : class
        {
            var constructors = new LinkedList<ConstructorInfo>(
                typeof(T).GetConstructors().OrderByDescending(ci => ci.GetParameters().Length));
            var list = new List<object>(dependencies);
            foreach (var ctor in constructors)
            {
                var parameters = ctor.GetParameters();
                var valuesNeeded = new List<object>(list.Count);
                foreach (var parameter in parameters)
                {
                    for (var i = 0; i < list.Count; ++i)
                    {
                        if (!parameter.ParameterType.IsInstanceOfType(list[i]))
                        {
                            continue;
                        }

                        valuesNeeded.Add(list[i]);
                        list.Remove(list[i]);
                        break;
                    }
                }

                if (valuesNeeded.Count == parameters.Length)
                {
                    list = valuesNeeded;
                    break;
                }

                // if the objects don't line up with this constructor, start over with the next one
                list = new List<object>(dependencies);
                valuesNeeded.Clear();
            }

            return (T)Activator.CreateInstance(typeof(T), list.ToArray());
        }
    }
}

Edit 2: added some ordering to GetConstructors(). So far, it only orders by the number of parameters.

Edit: here is an example of what you can do with an Illuminator. Say you have a class that has a lot of dependencies. We can new up all the dependencies of the dependencies, and then actually assign the fields of the class through illuminator.Illumine() calls:

public class MegaBigClass
{
    public MegaBigClass(Illuminator illuminator)
    {
        var a = new A(); // new in a constructor?! Blasphemy!
        var b = new B();
        var c = new C();
        var x = new X();
        var y = new Y();
        var z = new Z();

        // then actually set the fields with the illuminator
        this.abc = illuminator.Illumine<AbcComposite>(a, b, c);
        this.xyz = illuminator.Illumine<XyzComposite>(x, y, z);
    }

    private readonly AbcComposite abc;
    private readonly XyzComposite xyz;
}

Contemplate how testable this code is. Because Illumine is virtual, it is easy to create a fake or alternate Illuminator for test code to enable us to exactly specify what gets put into the fields. Look at how we can use new all we want in our constructors, but we need to make sure to assign fields only through illuminator.Illumine() calls.

Could I wrap the entire implementation into a single Activator.CreateInstance() call? Perhaps, but I feel like doing it this way instead allows for imperfect calls to illuminator.Illumine<T> that still supplies all the necessary dependencies.

\$\endgroup\$
  • 8
    \$\begingroup\$ Could you please include an example of how to use this or possibly a test or two that shows its limitations? \$\endgroup\$ – Robert Snyder Jul 7 '16 at 0:35
  • 1
    \$\begingroup\$ Activator.CreateInstance already uses the constructor that is appropriated for the passed arguments. So, could you please explain what you are trying to archive with the code before return (T)Activator.CreateInstance(typeof(T), list.ToArray());? \$\endgroup\$ – JanDotNet Jul 7 '16 at 5:21
  • \$\begingroup\$ Not to mention that to pick the right constructor also involves some conversions (for example int may be promoted to long if it's the only applicable one) and here you're not doing anything like that. Also note that GetConstructor() already has an overload to match a given parameter list (optionally with a binder...) You may show us which scenario this code is supposed to solve \$\endgroup\$ – Adriano Repetti Jul 7 '16 at 8:06
  • \$\begingroup\$ Default parameters and param[]. Are these handled appropriately? \$\endgroup\$ – radarbob Jul 7 '16 at 11:56
  • \$\begingroup\$ I think the only advantage of this method is that you don't have to care about the order of the parameters and the code before the activator tries to sort the parameters to match one of the constructors. \$\endgroup\$ – t3chb0t Jul 7 '16 at 14:52
2
\$\begingroup\$

I suppose we are talking about DI.

Based on your example, I can not see the advantages of the Illuminator class. If MegaBigClass depends on AbcComposite and XyzComposite, the right way would be to pass that objects to the constructor. In your case, you have to pass the dependencies of AbcComposite and XyzComposite to MegaBigClass to be able to create the composite objects within that class. However, MegaBigClass should not be responsible for creating that objects.

If MegaBigClass must be able to create the composit objects during runtime, Illuminator serves as something like a "generic factory" (Btw: In that case, I would call it factory so that other developers know what it is). For that case, the Illuminator provides a way to create objects without using new where the concrete creation logic can be changed for testing.

The logic of the Illuminator is (correct me if I am wrong): Search the first constructor from an unordered list that's signature matches to any sub set of the passed arguments.

Well, lets assume we will use the Illuminator that way:

  • I am not able to define which constructor should be used. If the class to create has a parameterless constructor that is returned first in .GetConstructors(), the class will be created always by the parameterless constructor.

MSDN - GetConstructors()

The M:System.Type.GetConstructors method does not return constructors in a particular order, such as declaration order. Your code must not depend on the order in which constructors are returned, because that order varies.

  • because that order varies.! Therfore, it is possible that running the code twice, the class creates objects on different ways.

  • Even if the order of the returned constructors will not change (or will be ordered first), what is if the class to be created becomes a new constructor somedays... That means that all the places where the Illuminator is used, objects may be created using another constructor! That may result in funny bugs ;)

Conclusion

I would really suggest NOT to use that class! There are excellent frameworks that adress the problems you are tring to solve (e.g. NInject factory extensions for creating factories, Moq for mocking objecs to enable testability ...).

\$\endgroup\$
  • \$\begingroup\$ Upvoted! I didn't know I couldn't depend on GetConstructors() to start with the most complex constructor first (one with the most and most derived dependencies). With that in mind, my code is definitely flawed. Let me consider your comments on how I might be reinventing the wheel a bit and should use a DI framework instead. \$\endgroup\$ – xofz Jul 8 '16 at 16:43
  • \$\begingroup\$ Also, I'm surprised GetConstructors doesn't return the same array each time it's called. If the order of the constructors is not guaranteed, GetConstructors should really return an IEnumerable<ConstructorInfo> and not an array. \$\endgroup\$ – xofz Jul 8 '16 at 16:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.