ILDASMDC—an ILDASM-based DependencyChecker

V.1.12 © Harald M. Müller
Used Software
Examples of defining rules up-front—the preferred scenario
An example of a dependency detection session: Understanding the dependencies in Microsoft's Enterprise Library
Design considerations
Open Items
Release Notes
This project is hosted on Logo.
The project homepage is at
The development homepage ("sourceforge foundry") is at
My email address is


ILDASMDC is a tool that helps you to keep the (static) architecture of a .Net software clean. You specify allowed dependencies between namespaces, classes, or even methods, and ILDASMDC will check whether the dependencies are violated somewhere. You will use this in Nightly Builds or Continuous Integration Builds to prevent the introduction of unwanted dependencies.

Ideally, the software architect will specify the intended dependencies (or rather, dependency rules) for some module before code is written. However, by creating a diagram of the dependencies, it is also possible to explore and document the dependencies of an existing piece of software. Both ways of proceeding are especially useful to prevent the dreaded cyclic dependencies, which usually result in a tangled monolith of interdependencies, making a software unmaintainable in a quite short period of time.

ILDASMDC has proven its usefulness (and stability) in a project of 25 developers with now more than 2 million LOC.

Used Software

ILDASMDC is based on Microsoft's .NET Framework 2.0.
Internally, ILDASMDC uses ANTLR for .NET (version 2.7.5) from
For graph drawing, you need DOT from AT&T's GraphViz tool library at


ILDASMDC is licensed under the GPL license (see


Installation of executable and documentation


Installation of sources


Dependency rules and builds

ILDASMDC reads one or more .Net assemblies or IL files (produced by Microsoft .Net's disassembler ILDASM) and extracts all the uses of classes, structs, enums, delegates, inner classes, and methods from that file. It also needs a definition of dependency rules—here are two simple examples:
    ILDASMDependencyChecker.** ---> antlr.**
    ILDASMDependencyChecker.** ---! System.Windows.Forms.**
The two asterisks ** are the abbreviation for "a class name, including a namespace." The two rules thus mean:
All classes (and enums, methods, ...) in and below the namespace ILDASMDependencyChecker may use all classes (structs, delegates, ...) in and below the antlr namespace.
All classes (etc.) in and below the namespace ILDASMDependencyChecker must not use any class (etc.) from the System.Windows.Forms namespace tree.
The idea is that rules like these are defined by the software architect(s) and checked every time a build is run. When rules are violated, ILDASMDC will write something like the following and return with exit code 3:
    **** Illegal dependency ILDASMDependencyChecker.DependencyGrapher/Node::.ctor ---> ILDASMDependencyCheckerTests.TestClass/InnerTestClass
        (probably at src\ILDASMDependencyChecker\DependencyGrapher.cs:80)
A violation of the rules should be like a compile time error: It breaks the build. Hence, a defined (static) software architecture can no longer degrade to some tangled web of dependencies, which will become unmaintainable over time.

Showing dependencies graphically

ILDASMDC can also produce a DOT graph showing dependencies. This is not as useful as it sounds to most people: Defining and checking dependency rules should be the predominant goal. However, there are two applications for this feature:


Manual use

From version 1.10 onwards, it is suggested that you call ILDASMDependencyChecker directly with the corresponding options (there is still the old BAT wrapper called ildasmdc.bat, which is explained below). A typical call to ILDASMDependencyChecker looks as follows:
     ILDASMDependencyChecker /f=myrules.dep myproj*.dll myproj*.exe
This will output all the dependencies in all myproj* .Net assemblies in the current directory. It is good practice to have a naming convention for the assemblies of a project; by this, the project's own assemblies can easily be distinguished from other assemblies (usually copied ones) in the same directory. The complete usage of ILDASMDependencyChecker is explained here:
   ILDASMDependencyChecker [options] [<filename> | @<file with filenames in it>] ...
where <filename> can be the name of
  a .Net-DLL-Assembly
  a .Net-EXE-Assembly
  a UTF8 .il-file with quoted names

Typical uses:

* Check dependencies in My.DLL:
      ILDASMDependencyChecker /f=MyDependencies.dep /r My.dll
      ildasm.exe /UTF8 /QUOTEALLNAMES /LINENUM / My.DLL 
      ILDASMDependencyChecker /f=MyDependencies.dep

* Produce graph of dependencies in My.DLL:
      ILDASMDependencyChecker /f=MyDependencies.dep My.DLL /
      dot -Tgif -oMy.gif

All messages of ILDASMDependencyChecker are written to Console.Out.

   /v    Verbose. Shows regular expressions used for checking and 
         all checked dependencies. Attention: Place /v BEFORE any
         /f or /d option to see the regular expressions.

   /r    Remember last checking time for each input file somewhere in temp
         directory; do dependecy checking for an assembly or .il-file
         only if it has changed since last checking time (or no time has
         been remembered for this file).

   /f=<rule file>  A file of dependency specifications - one specification
         per line. Multiple dependency files can be specified.
         Currently, the following lines are supported:

           empty line            ... ignored
           // comment            ... ignored
           # comment             ... ignored

           NAME := pattern       ... define abbreviation which is replaced
                                     in patterns before processing. NAME
                                     must be uppercase only (but it can
                                     contain digits, underscores etc.).
                                     Longer names are preferred to shorter
                                     ones during replacement. The pattern
                                     on the right side can in turn use 
                                     abbreviations. Abbreviation processing
                                     is done before all reg.exp. replacements
                                     described below.

           pattern ---> pattern  ... allowed dependency. The second
                                     pattern may contain back-references
                                     of the form \1, \2 etc. that are
                                     matched against corresponding (...)
                                     groups in the first pattern.

           pattern ---! pattern  ... forbidden dependency. This can be used
                                     to exclude certain possibilities for
                                     specific cases instead of writing many
                                     "allowed" rules.

           pattern ---? pattern  ... questionable dependency. If a dependency
                                     matches such a rule, a warning will be
                                     emitted. This is useful for rules that
                                     should be removed, but have to remain
                                     in place for pragmatic reasons (only
                                     for some time, it is hoped).

           NAME :=
               <arbitrary lines except =:>
           =:                    ... definition of a rule macro. The
                                     arbitrary lines can contain the strings
                                     \L and \R, which are replaced with the
                                     corresponding patterns from the macro 
                                     use. NAME need not consist of letters
                                     only; also names like ===>, :::>, +++>
                                     etc. are allowed and quite useful.

           pattern NAME pattern  ... Use of a defined macro.

           % pattern (with at least one group) 
                                 ... Define output in DAG graph (substring
                                     matching first group is used as label).
                                     If the group is empty, the dependency
                                     is not shown in the graph.
                                     Useful only with /d option.

         For an example of a dependency file, see near end of this help text.

         A pattern can be specified in three ways:

           ^regexp$              ... matched against a declaration
                                     ("declarations" see below)

           ^regexp               ... the regexp is expanded to up to four
                                     different forms, all of which are
                                     matched against declarations:
               ^regexp$                   - for matching a class name
               ^regexp(/<ident>)*$  - for matching nested classes
                                            (if regexp contains no / )
                                            <ident> is the pattern
                                            matching an identifier.
               ^regexp::<ident>$          - for matching methods
                                            (if regexp contains no ::)
               ^regexp(/<ident>)*::ident$ - for methods of nested classes
                                            (if regexp contains no / and no ::)

           wildcardpath          ... first, the following replacements are done:

               .       is replaced with the reg.exp. [.] (matches single period)

               *       is replaced with the reg.exp. for an <ident> (a "name")

               **      is usually replaced with <ident>(?:.<ident>)* (a 
                            (?: in a reg.exp.means that the parentheses do not 
                            count as numbered group when matching \1, \2, etc.)
                       However, if there is a slash (/) somewhere to the left 
                       of the **, it is replaced with <ident>(?:/<ident>)*, 
                       i.e., the idents are separated by /. This can be used
                       to match inner class hierarchies.

               After the wildcard replacemants, suffixes are added as for 

         Patterns are matched against declarations. A declaration is
         retrieved from the .il file as a string of one of the forms


         Inside namespace, elements are separated by a period (.).
         That means that the LAST period (before an optional ::)
         separates the namespace from the classname. If there is 
         no namespace, there is no leading . before the classname.
         Inside nestednames, sub-nestings are separated by a slash (/).

   /d=<dot file>   Create output of dependencies in AT&T DOT format.
         By default, ILDASMDependencyChecker tries to remove transitive
         edges - i.e., if a uses b, b uses c, but also a uses c, then
         the last edge is not shown. The algorithm for this will
         sometimes choose funny edges for removal ...

   /t    Show also transitive edges in DOT graph.

   /i[=<N>]        For each illegal edge (i.e., edge not allowed by 
         the dependency file), show an example of a concrete illegal 
         dependency in the DOT graph. N is the maximum width of strings 
         used; the default is 80. Graphs can become quite cluttered 
         with this option.

Example of a dependency file with some important dependencies (all
using the wildcardpath syntax):

   // Every class may use all classes from its own namespace.
        (**).* ---> \1.*

   // Special dependency for class names without namespace
   // (the pattern above will not work, because it contains a
   // period): A class from the global namespace may use
   // all classes from that namespace.
        * ---> *

   // Every class may use all classes from child namespaces
   // of its own namespace.
        (**).* ---> \1.**.*

   // Every class may use all of System.
        ** ---> System.**

   // Use ALL as abbreviation for ILDASMDependencyChecker.**
        ALL := ILDASMDependencyChecker.**

   // All DependencyChecker classes must not use Windows Forms
   // (even though in principle, all classes may use all of 
   // System according to the previous ---> rule).
        ALL ---! System.Windows.Forms.**

   // All DependencyChecker classes may use classes from antlr.
        ALL ---> antlr.**

   // In DAG output, identify each object by its path (i.e.
   // namespace).
        % (**).*

   // Classes without namespace are identified by their class name:
        % (*)

   // Classes in System.* are identified by the empty group, i.e.,
   // they (and arrows reaching them) are not shown at all.
        % ()System.**

Exit codes:
   0    All dependencies ok (including questionable rules).
   1    Usage error.
   2    Cannot load dependency file (syntax error or file not found).
   3    Dependencies not ok.
   4    IL file specified as argument not found.
   5    Other exception (e.g. parse error in IL file).

Using the ildasmdc.bat wrapper

ildasmdc.bat provides a convenience wrapper around the disassembler ILDASM.exe, the actual checker ILDASMDependencyChecker.exe, and AT&T's DOT.exe (if you want to see a graph). Since version 1.10, ILDASMDependencyChecker internally calls ILDASM if passed a .DLL or .EXE, so the usefulness of this wrapper has declined sharply.

ildasmdc.bat is used as follows:

    ILDASMDC [/v] [/f=rule file] ... [/n] [/g=GIF file] [/i] [/t] directory_with_.Net_DLLs_and_EXEs ...
/v Verbose mode—this is useful to check which regular expressions are used for dependency matching. The output will be quite long if you specify this.
/f=rule file specifies a rule file to be used. For formats of rules, see "Usage page" below and the following example sections.
/n Do not read StandardDependencies.dep.
/g=GIF file Create a GIF image showing the dependency graph. Units shown (classes, namespaces, methods, ...) are selected on the basis of regular expressions—see "Usage page" below and the following example sections. By an algorithm known as "transitive reduction," the number of shown edges is reduced as follows: If A uses B, and B uses C, and A also uses C, the last edge is not shown in the graph (because it is implied by A -> B -> C).
/i If /g is specified, print one illegal dependency (according to the rule file) near a dependency edge.
/t If /g is specified, print also transitive edges. This will usually produce a huge number of lines in your picture and make it totally useless for presentation purposes. On the other hand, this is necessary if you want to extract dependency rules from a dependency graph—then you need all dependencies.
directory_with_.Net_DLLs_and_EXEs- One or more directories which are traversed for DLLs and EXEs to be analyzed. The traversal is done by dir /s/b, hence subdirectories are also traversed. If this is not what you want, edit ildasmdc.bat accordingly.

ildasmdc.bat writes its messages to standard output (file handle 1). If you expect many messages, you will want to redirect the output to a file.

Use in builds

In a build, you will call ILDASMDependencyChecker.EXE, and possibly DOT separately.

Please note that ILDASMDependencyChecker.EXE's options are a little different from ildasmdc.bat's:

For uses in builds, ILDASMDependencyChecker.EXE has various exit codes. Essentially, you will test for exit code = 0—this means that everything is ok; exit code = 3 is also interesting, meaning "some dependencies did not follow the rules." For the other exit codes, see the above copy of the help output of ILDASMDependencyChecker.EXE.

Examples of defining rules up-front—the preferred scenario

Namespace dependency rules

Here is an example scenario for dependency checking. Assume you are responsible for the design of a new GUI library which uses the MVC pattern. Let's call that new library "Yet Another GUI Library" (or "Your Advanced GUI Library"), which is abbreviated as "YAGL." The architect (you) has a vision of an architecture which decouples the implementations of the three pattern roles as cleanly as possible—preferably by namespaces. To be concrete, you start with the design and implementation of buttons. You decide that you want the following namespaces:

and, moreover Your next step is to decide on the dependencies that you want to allow. This will lead to the intended decoupled architecture, where e.g. the Models namespace can be replaced (or even removed) without disturbing the rest of the design (except that, at runtime, there must of course be some concrete models around—but they could be provided by other modules, e.g. an application or a data binding layer). Here are the rules that might be necessary with the namespace division above:
    // MODELS:
      // Abstract and standard models use the model interfaces
    Yagl.Buttons.Models.**          ---> Yagl.Buttons.IModels.**
      // Controllers call View changing methods - on a general level ...
    Yagl.Buttons.Controllers.*      ---> Yagl.Buttons.Views.*
      // ... and for each GUI system (but not crossing
      // GUI systems!)
    Yagl.Buttons.Controllers.(*).** ---> Yagl.Buttons.Views.\1.**
      // GUI-system dependent Controllers use common Controller code
    Yagl.Buttons.Controllers.*.**   ---> Yagl.Buttons.Controllers.*
      // Controllers for one GUI system use methods and events from the 
      // corresponding native library.
    Yagl.Buttons.Controllers.(*).** ---> System.GUISupport.\1.**
      // Controllers influence Models; and hook themselves as listeners to Models
    Yagl.Buttons.Controllers.*      ---> Yagl.Buttons.IModels.**
    // VIEWS:
      // Views hook themselves as listeners to Models
    Yagl.Buttons.Views.*            ---> Yagl.Buttons.IModels.**
      // GUI-system dependent Views use common View code
    Yagl.Buttons.Views.*.**         ---> Yagl.Buttons.Views.*
      // Views for one GUI system use methods and events from the 
      // corresponding native library.
    Yagl.Buttons.View.(*).**        ---> System.GUISupport.\1.**

The last rule (but not only that one) shows how the right side of a dependency rule can reference the left side by using "back references" (this is due to an idea of Ralf Kretzschmar, a colleague on a project I'm currently working on). As in standard regular expressions, \1 references the first parenthesized group on the left side, \2 the second etc.

Your team will now start designing the classes and algorithms and then code and test along. As you have ILDASMDC in your continuous build, there is no chance that someone inadvertently introduces unwanted dependencies. At some point (which might be quite early if you practice TDD) you want to assemble a small button with a controller, a view, and a model. At this time, you will probably notice that you can build this aggregate component with your defined dependencies in any of the existing namespaces, as you need access to the class constructors of controllers and views as well as models. You should now not weaken the dependencies: This will, if done a few more times, lead to a tangled architecture, where almost everything depends on everything.

One possibility of a correct design which keeps the dependencies clean is that you define an additional namespace

which provides typical facade classes connecting elements from all namespaces above. The dependencies of this namespace would (or could) be like this:

      // needed to create standard model via constructor
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.Models.**
      // needed to define instance vars to models
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.IModels.**
      // needed to create GUI-dependent controller via constructor
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.Controllers.*.**
      // needed to create GUI-dependent view via constructor
    Yagl.Buttons.Standard.* ---> Yagl.Buttons.Views.*.**

—and now you are happy: Facade classes RadioButton, ToggleButton, StandardButton, MenuButton and more can be defined in the Standard namespace by instantiating objects from the Models, Views.*, and Controllers.* namespaces.

A different concept is that instead of providing a "connecting namespace," you use dependency injection: The creation is externalized into a container (e.g. PicoContainer), which is suitably configured. In some sense, that configuration takes the role of the connecting namespace.

In later steps of designing YAGL, you might generalize the patterns above for all sorts of GUI controls, so that you end up with dependencies like

    Yagl.(*).Models.**          ---> Yagl.\1.IModels.**
    Yagl.(*).Controllers.*      ---> Yagl.\1.Views.*
    Yagl.(*).Controllers.(*).** ---> Yagl.\1.Views.\2.**
    Yagl.(*).Controllers.*.**   ---> Yagl.\1.Controllers.*


In the previous example, you might have defined namespaces Yagl.Buttons, Yagl.Labels, Yagl.Grids etc. with the corresponding sub-namespaces (Models, IModels, Controllers, Views). Between such "modules", you might also have dependencies: However, the dependencies between these module should follow a consistent pattern: In larger systems, such compound rules introduce an additional level of abstraction, which helps to define the constraints more concisely. Here is an example of definitions for Yagl, which uses a macro called ===>:
    ===> :=
        Yagl.\L.(*).**             ---> Yagl.\R.\1.**
        Yagl.\L.Models.**          ---> Yagl.\R.IModels.**
        Yagl.\L.Controllers.*      ---> Yagl.\R.Views.*
        Yagl.\L.Controllers.(*).** ---> Yagl.\R.Views.\1.**
        Yagl.\L.Controllers.*.**   ---> Yagl.\R.Controllers.*
    Buttons ===> Labels
    Grids   ===> Buttons
    Grids   ===> Labels
This feature has still not been tested extensively. I still hope to get to module checking in our large application till the end of 2007...

Method dependency rules

Here is a short explanation of a quite different dependency scenario, this time on the level of methods: An object-relational mapping I wrote does its work for searches in multiple phases (this is a sort of pipeline architecture):

  1. First, search conditions are converted to an internal tree representation (c2t—"condition to tree")
  2. Then, the trees are enhanced with SQL statements, depending on lazy/eager load and other factors (t2s—"tree to SQL")
  3. Then, the SQL statements are executed (s2r—"SQL to result sets")
  4. Finally, the result sets are converted to objects (r2o—"result sets to objects")
Many internal classes contribute to more than one of these phases: There is state, and there are methods for each phase. For a clean architecture, methods of one phase should only call methods and access fields provided by the same phase; this is important so that methods of earlier phases (e.g. a t2s method) do not call methods of later phases (e.g. an r2o method), when the state necessary for the later phase has not yet been computed by an earlier phase! In addition, there are some common methods which may be called in each phase (e.g. property getters)—these are to be suffixed with _com. To ascertain these constraints, the methods' names are to be suffixed with the phase, e.g. ComputeSQL_t2s() or CreateObject_r2o(...). Here are possible dependency rules that help to maintain the calling architecture (when using such specific rules, the StandardDependencies.dep file cannot be used, because it allows arbitrary calls inside each class. Specify /n to suppress those rules):
    ORMapping.**::*_c2t  --->   ORMapping.**::*_c2t 
    ORMapping.**::*_t2s  --->   ORMapping.**::*_t2s
    ORMapping.**::*_s2r  --->   ORMapping.**::*_s2r 
    ORMapping.**::*_r2o  --->   ORMapping.**::*_r2o
    ORMapping.**::*      --->   ORMapping.**::*_com
      // Methods may access private state (prefixed with _).
      // A separate getter is provided for each phase that
      // may legitimally access some field.
    ORMapping.**::*      --->   ORMapping.**::_*
      // ORMapping may use System except Windows.Forms
    ORMapping.**         --->   System.**
    ORMapping.**         ---!   System.Windows.Forms.**
      // Every class may use <PrivateImplementationDetails>
    ORMapping.** ---> <PrivateImplementationDetails>**
By the way, a shorter way of defining the first 4 rules would be:
    ORMapping.**::*_(c2t|t2s|s2r|r2o)  --->   ORMapping.**::*_\1

However, this is probably more difficult to understand than the expanded version above.

Also, in practice, the rules need to include methods for writing; and they will be more elaborate, as there will also be dependency rules between namespaces inside the ORMapping. E.g., some classes responsible for writing to the database—INSERT, UPDATE, DELETE—probably must not call, and not be called by, classes for querying the database.


The two scenarios just described are typical and intended uses for dependency checking: You (as the architect) define the dependencies up-front and have them checked during the life-time of the project. Of course, new developments and experiences might make it necessary to add new dependencies (as new modules with new namespaces or new method types are designed), and, sometimes, also the addition and modification of dependencies for existing namespaces. However, the latter can be a quite disruptive act as it might fundamentally shake the architectural foundations of the software system. When you become (or if you already are) a seasoned software architect, you will create more and more stable architectures up-front—or (more important) learn when to delay architectural decisions so that they can be arrived at when the necessary knowledge and understanding is available.

An example of a dependency detection session: Understanding the dependencies in Microsoft's Enterprise Library (January 2006)

In real life, you will at times be confronted with existing software where the architecture is not clear. ILDASMDC gives you the possibility to draw diagrams of dependencies with arbitrary granularity, which can help to understand the dependencies and architecture. The goal of such understanding should always be to define the rules to be followed from that point on—probably after some clean-up (re-architecting) of the software under consideration. As a concrete example, let us find out the main dependencies in Microsoft's Enterprise Library of January 2006.

We start with graphing the main modules. Because we practice whar might be called "explorative architecturing," we allow all dependencies. We store the following in a file, say msel.dep:

    ** ---> **
      // Show all leaf namespaces in graph
    % (Microsoft.Practices.*).**
      // For System and rest of Microsoft, show only one box
    % (System).**
    % (Microsoft).**
We call ildasmdc.bat as follows:
    ildasmdc.bat /f=msel.dep /g=msel.gif "c:\Programme\Microsoft Enterprise Library January 2006\bin"   

Here is the resulting diagram in msel.gif ...

A small disappointment: We were too high-level! In the next try, we go one level deeper:

    ** ---> **
      // Show two levels below Microsoft.Practices - especially,
      // below Microsoft.Practices.EnterpriseLibrary.
    % (Microsoft.Practices.*.*).**
    % (Microsoft.Practices.*).*
      // For System and rest of Microsoft, show only one box
    % (System).**
    % (Microsoft).**

The same call as above now yields the following diagram:

Now we see for the first time the library's internal structure. The only obvious architectural flaw is the cyclic dependency between Caching and Security; we will explore this further in the next steps. Please remember that this diagram excludes as many transitive edges as possible—hence it might e.g. be that classes from Logging use classes from Common etc. To show the full dependencies for demonstrating the difference, let's call ILDASMDC with the /t option (/t=transitive edges):

    ildasmdc.bat /f=msel.dep /g=msel.gif /t "c:\Programme\Microsoft Enterprise Library January 2006\bin"   

The result is typical for a weakly layered architecture and demonstrates why the removal of transitive edges helps to understand the actual dependencies. However, we will have to return to this type of diagram later. For now, let us continue with the exploration of the cycle between Caching and Security. We only show Caching and Security, but now down to the lowest namespaces. To distinguish visually between the two, we have Caching namespaces shown in a "long" format including "Practices.EnterpriseLibrary," whereas namespaces from Security are shown in a "short" format:

    ** ---> **
      // Show Caching and Security in detail.
    % Microsoft.(Practices.EnterpriseLibrary.Caching.**).*
    % Microsoft.Practices.EnterpriseLibrary.(Security.**).*
      // For all the rest, show nothing
    % ()**

This diagram certainly needs some detailed inspection. However, after a short time it becomes clear that Caching.Cryptography and its sub-namespaces —and only those—use modules from Security. Hence, we now reformulate the graph patterns for the complete Enterprise Library:

    ** ---> **
      // Show two levels below Microsoft.Practices - especially,
      // below Microsoft.Practices.EnterpriseLibrary.
      // Caching.Cryptography is singled out, because it depends on Security.
    % (Microsoft.Practices.*.*).**
    % (Microsoft.Practices.*).*
    % (Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography).**
      // For System and rest of Microsoft, show only one box
    % (System).**
    % (Microsoft).**

Here is the resulting picture:

Now that we have found a dependency architecture which is ok, we want to codify the dependency rules so that subsequent development cannot violate these rules. To this end, we must first show also all the transitive edges. We are no longer interested in System, because this may be used by all according to the standard rules. Therefore, we use the following graph patterns and call ILDASMDC with the /t option:

    ** ---> **
      // Show two levels below Microsoft.Practices - especially,
      // below Microsoft.Practices.EnterpriseLibrary.
      // Caching.Cryptography is singled out, because it depends on Security.
    % (Microsoft.Practices.*.*).**
    % (Microsoft.Practices.*).*
    % (Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography).**
      // Do not show System; for rest of Microsoft, show only one box
    % ()System.**
    % (Microsoft).**

The resulting graph shows us, to no-one's surprise, that Common and Configuration are used by everyone. Moreover, ObjectBuilder is also used by everyone (which is stated in the design documentation of the Enterprise Library somewhere). For the rest, we have a not too large set of specific dependencies which are easily written down. Here is the resulting list:

      // Everyone in EL may use ObjectBuilder, EL.Common, and EL.Configuration
    Microsoft.Practices.EnterpriseLibrary.**                      ---> Microsoft.Practices.ObjectBuilder.**
    Microsoft.Practices.EnterpriseLibrary.**                      ---> Microsoft.Practices.EnterpriseLibrary.Common.**
    Microsoft.Practices.EnterpriseLibrary.**                      ---> Microsoft.Practices.EnterpriseLibrary.Configuration.**
      // EL.Logging and EL.Caching may use EL.Data
    Microsoft.Practices.EnterpriseLibrary.Logging.**              ---> Microsoft.Practices.EnterpriseLibrary.Data.**
    Microsoft.Practices.EnterpriseLibrary.Caching.**              ---> Microsoft.Practices.EnterpriseLibrary.Data.**
      // EL.Security may use EL.Caching, but not EL.Caching.Cryptography
    Microsoft.Practices.EnterpriseLibrary.Security.**             ---> Microsoft.Practices.EnterpriseLibrary.Caching.**
    Microsoft.Practices.EnterpriseLibrary.Security.**             ---! Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography.**
      // EL.Caching.Cryptography may use EL.Security (the "backwards dependency"
      // which created the loop we saw a few steps ago).
    Microsoft.Practices.EnterpriseLibrary.Caching.Cryptography.** ---> Microsoft.Practices.EnterpriseLibrary.Security.**
      // EL.ExceptionHandling may use EL.Logging
    Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.**    ---> Microsoft.Practices.EnterpriseLibrary.Logging.**
As the string Microsoft.Practices.EnterpriseLibrary is occurring quite often here, it may make sense to replace it with a shorter abbreviation, e.g. as follows:
    _EL := Microsoft.Practices.EnterpriseLibrary

      // Everyone in EL may use ObjectBuilder, EL.Common, and EL.Configuration
    _EL.**                      ---> Microsoft.Practices.ObjectBuilder.**
    _EL.**                      ---> _EL.Common.**
    _EL.**                      ---> _EL.Configuration.**
      // EL.Logging and EL.Caching may use EL.Data
    _EL.Logging.**              ---> _EL.Data.**
    _EL.Caching.**              ---> _EL.Data.**
      // EL.Security may use EL.Caching, but not EL.Caching.Cryptography
    _EL.Security.**             ---> _EL.Caching.**
    _EL.Security.**             ---! _EL.Caching.Cryptography.**
      // EL.Caching.Cryptography may use EL.Security (the "backwards dependency"
      // which created the loop we saw a few steps ago).
    _EL.Caching.Cryptography.** ---> _EL.Security.**
      // EL.ExceptionHandling may use EL.Logging
    _EL.ExceptionHandling.**    ---> _EL.Logging.**

Running ILDASMDC gives us ... what is that? Zillions of "Illegal dependencies!" What's wrong here?

Here is the first "illegal" dependency:

  **** Illegal dependency Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.Instrumentation.DistributorEventLogger::LogServiceStarted ---> Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.Properties.Resources::get_Culture

What does Logging use here? Here is a more concise view of the dependency (you might want to highlight this with a marker on a printout of the "illegal" dependencies):

  ...Logging.MsmqDistributor.Instrumentation... ---> ...Logging.MsmqDistributor.Properties...
It is clear now: We forgot to specify that we are not concerned about arbitrary dependencies inside toplevel namespaces of the Enterprise Library. Hence, we need the following additional rule:
      // Everyone in or below a namespace EL.* may use all other namespaces in or below the same top-level EL namespace 
    Microsoft.Practices.EnterpriseLibrary.(*).**   ---> Microsoft.Practices.EnterpriseLibrary.\1.**
But we would now also allow that Caching uses all of Caching.Cryptography! Although this is not the case today, we want to prevent it in the future (so that someone will not introduce a cyclic dependency with Security!). Let us disect Caching to find out about the concrete rules we want to allow. Here are the graph patterns for this job:
    ** ---> **
      // Show only Caching (top level and one level below) so
      // that we can find out the dependencies needed in there.
    % (_EL.Caching).*
    % (_EL.Caching.*).**
    % ()Microsoft.**
    % ()System.**

The result—with transitive edges—is as follows:

Thus, we now know which other namespaces are immediately below Caching. For all these—except Cryptography—, and also for Caching itself, we suppress the use Caching.Cryptography:

    _EL.Caching.*                ---! _EL.Caching.Cryptography.**
    _EL.Caching.BackingStoreImplementations.** ---! _EL.Caching.Cryptography.**
    _EL.Caching.Common.**        ---! _EL.Caching.Cryptography.**
    _EL.Caching.Configuration.** ---! _EL.Caching.Cryptography.**
    _EL.Caching.Database.**      ---! _EL.Caching.Cryptography.**

However, this is not at all a fool-proof set of of rules: If a new sub-namespace of Caching is introduced at some future time, that future namespace could use Cryptography. An alternative idea is to decree that all namespaces not starting with C cannot use Cryptography:

    _EL.Caching.*                ---! _EL.Caching.Cryptography.**
    _EL.Caching.[^C]*.**         ---! _EL.Caching.Cryptography.**
However, this would still allow Caching.Common and Caching.Configuration to use Caching.Cryptography. So, we add another rule:
    _EL.Caching.C[^r]*.**        ---! _EL.Caching.Cryptography.**

—and we could add more rules to suppress use of Cryptography by longer and longer prefixes.

In my opinion, a better idea would be that Microsoft changed the name of Caching.Cryptography to e.g. SecureCaching in the Enterprise Library: Then there would be a clear top-level acyclic dependency graph.

Together with the other rules, we get the following overall dependency rule list:

      // Everyone in or below a namespace EL.* may use all other namespaces in or below the same namespace
    _EL.(*).**                   ---> _EL.\1.**

      // However, top-level Caching and Caching sub-namespaces not starting with Cry must not use Caching.Cryptography
    _EL.Caching.*                ---! _EL.Caching.Cryptography.**
    _EL.Caching.[^C]*.**         ---! _EL.Caching.Cryptography.**
    _EL.Caching.C[^r]*.**        ---! _EL.Caching.Cryptography.**
    _EL.Caching.Cr[^y]*.**       ---! _EL.Caching.Cryptography.**

      // Everyone in EL may use ObjectBuilder, EL.Common, and EL.Configuration
    _EL.**                       ---> Microsoft.Practices.ObjectBuilder.**
    _EL.**                       ---> _EL.Common.**
    _EL.**                       ---> _EL.Configuration.**
      // EL.Logging and EL.Caching may use EL.Data
    _EL.Logging.**               ---> _EL.Data.**
    _EL.Caching.**               ---> _EL.Data.**
      // EL.Security may use EL.Caching, but not EL.Caching.Cryptography
    _EL.Security.**              ---> _EL.Caching.**
    _EL.Security.**              ---! _EL.Caching.Cryptography.**
      // EL.Caching.Cryptography may use EL.Security (the "backwards dependency")
    _EL.Caching.Cryptography.**  ---> _EL.Security.**
      // EL.ExceptionHandling may use EL.Logging
    _EL.ExceptionHandling.**     ---> _EL.Logging.**

A final run of ILDASMDC over the Enterprise Library now yields no messages—hence, our rules encompass all current dependencies. On the other hand, they are sufficiently strong to prevent future corruption of the static architecture during maintenance and redesign.

For teaching developers about the allowed dependencies, I would start with the clean picture to show the principial layering of the library:

For detailed dependencies between modules, the architect could then

or, preferably,

Design considerations

A. Couldn't there be an option showing all cyclic dependencies?— —The idea is that architecture is not arrived at by "reacting to symptoms" (e.g. to cycles), but on the contrary by "acting," i.e., by wanting that certain dependencies may occur, whereas others do not occur. Thus, the goal is not that a tool emits warnings, but that an architect prescribes allowed dependency patterns. These might also include cycles at certain or even many places. The object-relational mapping mentioned above, for example, had—intentionally—almost no restrictions on class dependencies, but very restrictive rules for method dependencies.

B. Couldn't the dependency rule language allow for "except rules," e.g., "All sub-namespaces of A except A.B may use C"?— —

Open items

1. Parsing of .IL files is very heuristic. I suspect that with method dependencies, a dependency on a return type might be reported incorrectly (namely with the previous method instead of the one having the return type). This needs to be checked and corrected, if actually wrong.

2. Provide an MSBuild task to run ILDASMDC in automated builds.

3. Upgrade to ANTLR 3 (for performance reasons).

4. Make ILDASMDC multi-threaded to increase performance on multi-core and multi-processor machines.

Release Notes

1.12 (2008-01-12)

1.11 (2007-11-27)

1.10 (2007-11-24)

1.00 (2007-04-15)

0.99 (2007-03-26)

0.98 (2007-01-20)

0.97 (2006-09-06)

0.96 (2006-08-29)

0.95 (2006-08-28)

0.94 (2006-08-05)

0.93 (2006-05-13)

0.92 (2006-05-06)

0.91 (2006-02-21)

0.9 (2006-02)

© 2006-2007 HMMüller