Over the years of using C#/.NET for a bunch of in-house projects, we've had one library grow organically into one huge wad of stuff. It's called "Util", and I'm sure many of you have seen one of these beasts in your careers.

Many parts of this library are very much standalone, and could be split up into separate projects (which we'd like to open-source). But there is one major problem that needs to be solved before these can be released as separate libraries. Basically, there are lots and lots of cases of what I might call "optional dependencies" between these libraries.

To explain this better, consider some of the modules that are good candidates to become stand-alone libraries. CommandLineParser is for parsing command lines. XmlClassify is for serializing classes to XML. PostBuildCheck performs checks on the compiled assembly and reports a compilation error if they fail. ConsoleColoredString is a library for colored string literals. Lingo is for translating user interfaces.

Each of those libraries can be used completely stand-alone, but if they are used together then there are useful extra features to be had. For example, both CommandLineParser and XmlClassify expose post-build checking functionality, which requires PostBuildCheck. Similarly, the CommandLineParser allows option documentation to be provided using the colored string literals, requiring ConsoleColoredString, and it supports translatable documentation via Lingo.

So the key distinction is that these are optional features. One can use a command line parser with plain, uncolored strings, without translating the documentation or performing any post-build checks. Or one could make the documentation translatable but still uncolored. Or both colored and translatable. Etc.

Looking through this "Util" library, I see that almost all potentially separable libraries have such optional features that tie them to other libraries. If I were to actually require those libraries as dependencies then this wad of stuff isn't really untangled at all: you'd still basically require all the libraries if you want to use just one.

Are there any established approaches to managing such optional dependencies in .NET?

Even if the libraries are dependent on each other, there still might be some benefit in separating them into coherent but separate libraries, each containing a broad category of functionality.
–
Robert HarveyDec 16 '11 at 22:14

6 Answers
6

Refactor Slowly.

Expect this to take some time to complete, and may occur over several iterations before you can completely remove your Utils assembly.

Overall Approach:

First take some time and think of how you want these utility assemblies to look when you're done. Don't worry too much about your existing code, think of the end goal. For example, you may wish to have:

Handling Optional Interfaces

Either an assembly references another assembly, or it doesn't. The only other way to use functionality in a non-linked assembly is through an interface loaded through reflection from a common class. The downside to this is that your core assembly will need to contain interfaces for all of the shared features, but the upside is that you can deploy your utilities as needed without the "wad" of DLL files depending on each deployment scenario. Here's how I would handle this case, using the colored string as an example:

Create a PluginFinder (or maybe InterfaceFinder is a better name in this case) class that can find interfaces from DLL files in the current folder. Here is a simplistic example. Per @EdWoodcock's advice (and I agree), when your projects grows I would suggest using one of the available Dependency Injection frameworks (Common Serivce Locator with Unity and Spring.NET come to mind) for a more robust implementation with more advanced "find me that feature" capabilities, otherwise known as the Service Locator Pattern. You can modify it to suit your needs.

That PluginFinder class looks suspiciously like a roll-your-own automagical DI handler (using a ServiceLocator pattern), but this is otherwise sound advice. Maybe you would be better to just point the OP at something like Unity, since that would not have issues with multiple implementations of a particular interface within the libraries (StringColourer vs StringColourerWithHtmlWrapper, or whatever).
–
Ed WoodcockJan 10 '12 at 10:57

@EdWoodcock Good point Ed, and I can't believe I didn't think of the Service Locator pattern while writing this. The PluginFinder is definitely an immature implementation and a DI framework would certainly work here.
–
Kevin McCormickJan 10 '12 at 15:30

I've awarded you the bounty for the effort, but we're not going to go this route. Sharing a core assembly of interfaces means that we only succeeded in moving away the implementations, but there's still a library that contains a wad of little-related interfaces (related through optional dependencies, as before). The set-up is much more complicated now with little benefit for libraries as small as this. The extra complexity might be worth it for humongous projects, but not these.
–
romkynsJan 16 '12 at 15:29

Try to resolve a contract (class via interface) using a dependency injection (MEF, Unity etc). If not found, set it to return a null instance.
Then check if the instance is null, in which case you don't do the extra functionalities.

This is especially easy to do with MEF, since it's the textbook use for it.

It would allow you to compile the libraries, at the cost of splitting them into n + 1 dlls.

This sounds almost right - if only it wasn't for that one extra DLL, which is basically like a bunch of skeletons of the original wad of stuff. The implementations are all split up, but there's still a "wad of skeletons" left. I suppose that has some advantages, but I'm not convinced that the advantages outweigh all the costs for this particular set of libraries...
–
romkynsJan 9 '12 at 19:45

Additionally, including an entire framework is totally a step back; this library as-is is about the size of one of those frameworks, totally negating the benefit. If anything, I'd just use a bit of reflection to see if an implementation is available, since there can only be between zero and one, and external configuration is not required.
–
romkynsJan 16 '12 at 17:21

I thought I'd post the most viable option we've come up with so far, to see what the thoughts are.

Basically, we'd separate each component into a library with zero references; all the code that requires a reference will be placed into an #if/#endif block with the appropriate name. For example, the code in CommandLineParser that handles ConsoleColoredStrings would be placed into #if HAS_CONSOLE_COLORED_STRING.

Any solution that wishes to include just the CommandLineParser can easily do so, since there are no further dependencies. However, if the solution also includes the ConsoleColoredString project, the programmer now has the option to:

add a reference in CommandLineParser to ConsoleColoredString

add the HAS_CONSOLE_COLORED_STRING define to the CommandLineParser project file.

This would make the relevant functionality available.

There are several issues with this:

This is a source-only solution; every consumer of the library must include it as a source code; they can't just include a binary (but this isn't an absolute requirement for us).

The library project file of the library gets a couple of solution-specific edits, and it's not exactly obvious how this change is committed to SCM.

I'm going to recommend the book Brownfield Application Development in .Net. Two directly relevant chapters are 8 & 9. Chapter 8 talks about relayering your application, while chapter 9 talks about taming dependencies, inversion of control, and the impact this has on testing.

Full disclosure, I'm a Java guy. So I understand you're probably not looking for the technologies I'll mention here. But the problems are the same, so perhaps it'll point you in the right direction.

In Java, there are a number of build systems which support the idea of a centralized artifact repository that houses built "artifacts" - to my knowledge this is somewhat analogous to the GAC in .NET (please execuse my ignorance if it's a strained anaology) but more than that because it is used to produce independent repeatable builds at any point in time.

Anyway, another feature that is supported (in Maven, for instance) is the idea of an OPTIONAL dependency, then depending on specific versions or ranges and potentially excluding transitive dependencies. This sounds to me like what you're looking for, but I could be wrong. Take a look at this intro page on dependency management from Maven with a friend that knows Java and see if the problems sound familiar. This will allow you to build your application and construct it with or without having these dependencies available.

There are also constructs if you need truly dynamic, pluggable architecture; one technology that tries to address this form of runtime dependency resolution is OSGI. This is the engine behind Eclipse's plugin system. You'll see it can support optional dependencies and a minimum/maximum version range. This level of runtime modularity imposes a good number of constraints on you and how you develop. Most people can get by with the degree of modularity Maven provides.

One other possible idea that you could look into that might be orders of magnitude simpler to implement for you is to use a Pipes and Filters style of architecture. This is largely what has made UNIX such a long standing, successful ecosystem that has survived and evolved for half a century. Take a look at this article on Pipes and Filters in .NET for some ideas about how to implement this kind of pattern in your framework.