SilentSin
SilentSin

Reputation: 1036

What's the most efficient way to find all other assemblies that reference a specific assembly?

I often find myself wanting to iterate over all types in the current environment for various reasons such as to find everything that inherits from a particular class or to find all types with a specific attribute.

I can do this using System.AppDomain.CurrentDomain.GetAssemblies() to get all the assemblies, then Assembly.GetTypes() on each of them and iterating through all the types. But this is needlessly inefficient; in the Unity editor in a project containing a single script, this method goes through 8590 types, most of which can't possibly meet the criteria I'm searching for anyway. For example, nothing in mscorlib.dll or UnityEngine.dll is ever going to have my custom attribute or inherit from one of my classes, so I should just skip those assemblies.

So now, I'm trying to find all assemblies that reference any given assembly, but I'm having trouble coming up with an efficient algorithm for finding them since I can only get an array of assemblies that the target references but not an array of assemblies that reference the target.

Also note that if assembly A references B and B references C, when searching for everything that references C I would need to get both A and B (in case something in B inherits from the class I'm looking for and something in A inherits from that class without A referencing C directly).

Upvotes: 1

Views: 1851

Answers (2)

Gerardo Grignoli
Gerardo Grignoli

Reputation: 15167

I have never used Unity 2.0, so I coded this for .NET framework 2.0, hope it works.

First you may wanto to create a tree representation of the assemblies:

class DependencyTree
{
    public string AssemblyName;
    public IDictionary<string,DependencyTree> ReferencedAssemblies;
}

Now lets create a class to walk and generate the tree

class DependencyWalker
{
    Dictionary<string, DependencyTree> _alreadyProcessed = new Dictionary<string, DependencyTree>();

    public DependencyTree GetDependencyTree(Assembly assembly)
    {
        // Avoid procesing twice same assembly.
        if (_alreadyProcessed.ContainsKey(assembly.FullName)) 
            return _alreadyProcessed[assembly.FullName]; 

        var item = new DependencyTree();
        item.AssemblyName = assembly.FullName;
        item.ReferencedAssemblies = new Dictionary<string, DependencyTree>();

        _alreadyProcessed.Add(item.AssemblyName, item);

        foreach (AssemblyName assemblyName in assembly.GetReferencedAssemblies())
        {
            item.ReferencedAssemblies.Add(assemblyName.FullName, GetDependencyTree(Assembly.Load(assemblyName)));
        }

        return item;
    }

    // To print the tree to the console:
    public void PrintTree(DependencyTree tree)
    {
        PrintTree(tree, 0, new Dictionary<string, bool>()); // Using Dictionary because HashSet is not available on .NET 2.0
    }

    private void PrintTree(DependencyTree tree, int indentationLevel, IDictionary<string,bool> alreadyPrinted)
    {
        Console.WriteLine(new string(' ', indentationLevel) + tree.AssemblyName);

        if (alreadyPrinted.ContainsKey(tree.AssemblyName))
            return;

        alreadyPrinted[tree.AssemblyName] = true;

        foreach (DependencyTree a in tree.ReferencedAssemblies.Values)
            PrintTree(a, indentationLevel + 3, alreadyPrinted);

    }
}

Now you can easyly use this class:

class Program
{
    static void Main(string[] args)
    {
        new System.Xml.XmlDocument().LoadXml("<xml/>"); // Do whatever to ensure System.Xml assembly is referenced.

        var startingAssembly = typeof(Program).Assembly;
        var walker = new DependencyWalker();
        var tree = walker.GetDependencyTree(startingAssembly);
        walker.PrintTree(tree);
    }
}

Which outputs;

ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
   mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
         mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
         System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
            mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
            System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
            System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
            System.Security, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
               mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
               System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
               System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
         System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
      System.Data.SqlXml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
         System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
         mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
         System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

Please note that the generator outputs a tree that has loops, so traversing it on a recursive function will be an infinite loop. In PrintTree I avoid infinite loops by using the alreadyPrinted list. (I only print the child referenced list only once to avoid loops.) YMMV, so change it based on your needs.

Upvotes: 1

nikk
nikk

Reputation: 2867

Dependency Walker will do that magic for you.

Upvotes: 0

Related Questions