1

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).

SilentSin
  • 1,026
  • 9
  • 18
  • Which .NET framework or NET Standard library are you targeting? – Gerardo Grignoli Aug 03 '16 at 04:49
  • For your first approach, you may speed up things if you filter which assemblies to iterate thru. You can process only assemblies with filename starting with a pattern, or list the assemblies on a config file. If possible do it at startup and build up a cache into a Dictionary or something. – Gerardo Grignoli Aug 03 '16 at 05:00
  • For your second approach, you should create a tree of referenced assemblies, using a recursive function, with `assembly.GetReferencedAssemblies`. To speed things up, keep track of the assemblies that you have already processed once. – Gerardo Grignoli Aug 03 '16 at 05:06
  • I want it to work in Unity's .NET 2.0 subset on any platform. The part I'm having trouble with is how to efficiently turn the arrays from `GetReferencedAssemblies` into a dictionary that maps each assembly to a list of everything that references it (directly, or indirectly). – SilentSin Aug 03 '16 at 06:37
  • Re: your last paragraph - if anything in A *depends* on a type in C via something in B then it will have needed to add a reference to C anyway. The only time that you can have the dependency chain you've described is if B has successfully hidden it's use of C in private implementation details. Your specific example (`A.something` derives from `B.somethingelse` which derives from `C.someotherthing`) would cause a compiler error until the `A->C` reference was added. So, do you still think you need this transitive search? – Damien_The_Unbeliever Aug 03 '16 at 07:33
  • @Damien_The_Unbeliever Nope, apparently I was over thinking it. I just tested it and you're right. You can repost that as an answer if you like. – SilentSin Aug 03 '16 at 08:06

2 Answers2

1

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.

Gerardo Grignoli
  • 14,058
  • 7
  • 57
  • 68
0

Dependency Walker will do that magic for you.

nikk
  • 2,627
  • 5
  • 30
  • 51
  • Dependency Walker finds everything that a specific assembly is dependant on. I need to find everything that is dependant on a specific assembly. Also, I need to do it in code at runtime so I can loop through the types in each assembly. – SilentSin Aug 03 '16 at 06:30