There are several different ways to implement Dependency Injection (DI), and Martin Fowler describes four of them in his excellent article on IoC/DI. In this article, the first three approaches (Constructor, Property, and Interface Injection) are mainly described as a background for introducing the Service Locator pattern. In Fowler's example, a generic Service Locator is a registry (basically an in-memory table), but these days you most commonly see it implemented as a factory.

Consider this simple interface:

public interface IMyInterface
{
    void DoStuff();
}

Here's a class that uses a Service Locator to get an instance of IMyInterface:

public class ImplicitConsumer
{
    public void PerformOperation()
    {
        IMyInterface mi = ServiceFactory.Create<IMyInterface>();
        mi.DoStuff();
    }
}

The ServiceFactory class has a static method that returns an instance of the requested interface. Although not explicitly shown, the ImplicitConsumer class has a default constructor, since no constructor is defined and the C# compiler then automatically creates one.

Now, imagine yourself in a situation where you need to consume an instance of the ImplicitConsumer class and call its PerformOperation method. Also imagine that you have just been handed the class in a binary form, with documentation, but without source code. In this scenario, you would probably write code like this:

ImplicitConsumer ic = new ImplicitConsumer();
ic.PerformOperation();

Writing the first line is very straightforward, since there's only one way to create a new instance of the class. Next, with the ic instance, IntelliSense will quickly help you to find and invoke the PerformOperation method, and that's it: The code compiles and you are happy.

At run-time, however, this code is going to fail, since the Service Locator has not been configured. At this point, you may resort to the documentation, and if you are lucky, the documentation will tell you that the PerformOperation method expects the Service Locator to be configured to return an instance of IMyInterface. If you aren't so lucky, you will have to fire up Reflector to figure out what to do.

Depending on the Service Locator's implementation, this configuration may be done in the configuration file or in code. Here's how it might look in code:

ServiceFactory.Preset<IMyInterface>(new StubMyInterface());
 
ImplicitConsumer ic = new ImplicitConsumer();
ic.PerformOperation();

Here we have what looks like two pieces of totally unrelated code, yet they are very closely related at run-time. If you came by this code without prior knowledge, you'd probably mistake the first line of code for a piece of lava flow and delete it. If you were the author of those three lines, you might attempt to protect yourself from this risk by applying a comment to the first line, but that would be an apology.

In my opinion, an API should always strive to steer developers in the right direction. With respect to DI, the API should clearly state its intent to consume a particular dependency. Constructor Injection does this very explicitly:

public class ExplicitConsumer
{
    private IMyInterface mi_;
 
    public ExplicitConsumer(IMyInterface mi)
    {
        if (mi == null)
        {
            throw new ArgumentNullException("mi");
        }
        this.mi_ = mi;
    }
 
    public void PerformOperation()
    {
        this.mi_.DoStuff();
    }
}

With this implementation, any developer is forced to do the right thing. There's only a single constructor, and IntelliSense will show that it expects an instance of IMyInterface. While a developer could pass null as a parameter, he or she would do so in spite of the knowledge that was just communicated by IntelliSense (and there would still be a run-time error). There would be no need to read the documentation or fire up Reflector, because the class makes it very clear that it needs a working instance of IMyInterface to perform its work, and as a developer, you must supply it:

ExplicitConsumer ec = 
    new ExplicitConsumer(new StubMyInterface());
ec.PerformOperation();

The only drawback of Constructor Injection that I have ever been able to identify is the need to initialize all dependencies at once if you have a complex hierarchy of dependencies, as described in my former post. If lazy initialization is a necessity, you can use Provider Injection, which is a variation of Constructor Injection. Although this is currently my favorite DI strategy, it's less well-known and more difficult to explain.

In any case, the main point is that if a component expects to consume a dependency which will be supplied at run-time, it should clearly state that intent through its API, instead of relying on out-of-band discovery mechanisms. Although the API may end up looking more complex, it's ensuring that mistakes are much harder to make. Even if there's a slightly more pronounced learning curve to get started with an API that uses Constructor Injection, it's easier to use in the long run. A developer with no prior knowledge of the component will sooner be able to produce code that compiles with a component relying on a Service Locator, but he or she will sooner be able to produce code that works with Constructor Injection.