Reputation: 1036
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
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