However, [...] I don't want to have to register my helper classes in the DI container, because I know the public class will always be using those specific implementations.
That is a very strong assumption you're making here. Are you sure you'll never want to inject a decorator that will, say, report execution time to the debug output? Or one that catches and logs (with email notification if you want it) whatever uncaught exceptions that this other code could throw?
This is what proper DI allows you to do, when you inject dependencies as abstractions - the class has no clue of the actual implementing type, and doesn't care about it, because as long as the implementing type in question implements the specified interface, how it's implemented is not a concern of that class. It's actually none of its business.
/// Public constructor that passes dependencies to concrete implementations of helper classes.
public TranslationCompiler(IResourceService resourceService, ITranslationSerializer serializer)
if (resourceService == null) throw new ArgumentNullException("resourceService");
if (serializer == null) throw new ArgumentNullException("serializer");
_translationCatalogTransformer = new TranslationCatalogTransformer(resourceService);
_compiledCatalogTransformer = new CompiledCatalogTransformer(serializer);
The key to a successful dependency-injection implementation, is going all the way through.
You haven't listed your class, but I'm expecting to see this somewhere like right above these constructors you've listed:
private readonly ITranslationCatalogTransformer _translationCatalogTransformer;
private readonly ICompiledCatalogTransformer _compiledCatalogTransformer;
These are your class' dependencies. Inversion of Control specifically implies giving away that control you're keeping all for yourself - the control over the specific types that implement the abstractions you're depending on.
Just let go, embrace DI in its full glory.
If there aren't any other dependencies (are you
newing up anything else anywhere else?), then at the end this should be the only constructor you need:
public TranslationCompiler(ITranslationCatalogTransformer translationCatalogTransformer,
if (compiledCatalogTransformer == null)
throw new ArgumentNullException("compiledCatalogTransformer");
if (translationCatalogTransformer == null)
throw new ArgumentNullException("translationCatalogTransformer");
_translationCatalogTransformer = translationCatalogTransformer;
_compiledCatalogTransformer = compiledCatalogTransformer;
Even if you actually end up using a specific implementation forever, that's not a reason to introduce tight coupling in your architecture.
What you have here isn't Poor Man's DI. It's not DI.
You depend on two classes that each have their own dependencies, that you're able to provide via an IoC container: there's no reason to
new them up yourself, unless there's lack of context in your post or, more likely, unless there's something I missed.
The public constructor you have, doesn't take the constructed type's dependencies, and this only uselessly blurs things up. Poor Man's DI is a replacement for an IoC container, not a reason to introduce tight coupling.
You're injecting your dependencies' dependencies: this means your client code must
new up things that the object being created doesn't even need. Doesn't it smell?
Even if you are going to always be using those specific implementations, by
newing them up yourself you have ruled out all the advantages of doing DI in the first place, and you have made your constructors much more ambiguous and complex than they need to be, at least from a DI standpoint, where a type's constructor signature is expected to tell us what that type's dependencies are.
...which brings us to tell, don't ask.