Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

FindRefs - Find Method, Property and Field References Originating From Your Assembly

0.00/5 (No votes)
3 Apr 2007 1  
A command line utility that analyzes your assembly and generates an XML file containing the methods, fields and properties referenced, grouped by assembly and type

Introduction

This article originated from the need to find out the methods, fields and properties that an assembly accesses through its references. While Visual Studio shows the list of assemblies that an assembly references, it does not show the actual members being referenced. FindRefs is a simple tool that uses Cecil [^] as the back-end to analyze all methods and properties in an assembly and list the method calls, field and property references made by it on types residing in referenced assemblies.

Background

The motivation to write the tool was to figure out the logical dependencies of an assembly, so that refactoring or redesigning its referenced assemblies would not break it. Just the list of referenced assemblies wasn't obviously enough and I couldn't find any tool, except Reflector, that gave me the list of methods, properties and fields that are actually referenced. However, I couldn't find a way to export the output from Reflector and so I decided to roll my own. Besides, I had just discovered Cecil and I thought this would be a nice opportunity to play around with it.

Using FindRefs

Using FindRefs is very simple. FindRefs is a command line application that takes the assembly to be analyzed as the argument. You could optionally filter the types to be considered (for output) using the -include and -exclude flags. The command line syntax is:

 findrefs <assembly_path> [-include filterlist] [-exclude filterlist]</assembly_path>

filterlist is a list of fully qualified type names. It can also include namespaces with an asterisk (*) to indicate all types within the namespace, like we do with the using C# statement. It also supports using * as a wildcard to match type names. Example usage:

// excludes all types in all namespaces under System
findrefs blah.exe -exclude System.*  
// excludes all types in all namespaces/types under System except for System.Console
findrefs blah.exe -include System.Console -exclude System.* 
// excludes everything except System.Console
findrefs blah.exe -include System.Console -exclude * 
// excludes all types except those whose fully qualified names start with Sys
findrefs blah.exe -include Sys* -exclude * 

The output will be written to blah.xml (the assembly name without the extension + .xml) and it gets generated in the current directory. Not specifying -exclude does not exclude anything and therefore -include makes sense only when used in conjunction with -exclude.

Sample Output

For a simple program like:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello");
    }
}

Running findrefs on the compiled assembly generates the following output:

<?xml version="1.0" encoding="utf-8"?>
<References>
  <Assembly name="mscorlib, Version=2.0.0.0, Culture=neutral, 
        PublicKeyToken=b77a5c561934e089">
    <Type name="System.Console">
      <Reference name="System.Void System.Console::WriteLine(System.String)" />
    </Type>
    <Type name="System.Object">
      <Reference name="System.Void System.Object::.ctor()" />
    </Type>
  </Assembly>
</References>

How It Works

The Cecil library makes it really simple to walk through all methods in an assembly. You load the assembly and get a reference to AssemblyDefinition using the AssemblyFactory.GetAssembly(assemblyPath) and then use the AssemblyDefinition object to recursively walk through modules, types and methods. The following code snippet shows how to walk through all methods in an assembly:

public static void FindReferences(string assemblyPath, TypeFilter typeFilter)
{
    AssemblyDefinition assemblyDefinition = AssemblyFactory.GetAssembly(assemblyPath);
    foreach (ModuleDefinition moduleDefinition in assemblyDefinition.Modules)
    {
        Console.WriteLine("Processing module [" + moduleDefinition.Name + "]");
        foreach (TypeDefinition typeDefinition in moduleDefinition.Types)
        {
            ProcessType(typeDefinition, typeFilter);
        }
    }
}

static void ProcessType(TypeDefinition typeDefinition, TypeFilter typeFilter)
{
    Console.WriteLine("Processing type [" + typeDefinition.FullName + "]");

    foreach (MethodDefinition methodDefinition in typeDefinition.Methods)
    {
        ProcessMethod(methodDefinition, typeFilter);
    }

    foreach (MethodDefinition methodDefintion in typeDefinition.Constructors)
    {
        ProcessMethod(methodDefintion, typeFilter);
    }

    foreach (PropertyDefinition propertyDefintion in typeDefinition.Properties)
    {
        ProcessMethod(propertyDefintion.GetMethod, typeFilter);
        ProcessMethod(propertyDefintion.SetMethod, typeFilter);
    }
}

ProcessMethod goes through the IL instructions in the body of the method, looking for an operand of type MethodReference or FieldReference. However, MethodDefinition and FieldDefinition derive from MethodReference and FieldReference and an operand of type XXXDefinition means that the particular method/field resides in this assembly. We are looking for references outside the current assembly, so ProcessMethod explicitly checks that they are of type XXXReference but not of type XXXDefinition.

The rest of the code is just plumbing to group the results by assembly and type. There is also code that groups the references by the calling method, but that data is not used currently. TypeFilter is a nifty little class that takes care of the filtering based on include and exclude lists, if provided and it uses simple string matching to do its job.

Conclusion

This was my first brush with the Mono project and I must say I was really impressed by Cecil [^]. It was incredibly easy figuring out how to use it and it seems to handle .NET 2.0 stuff like generics pretty well. Anyway, I had great fun writing this tool and I hope it proves to be a useful addition to CP's repository of free tools. Comments, suggestions (and bug reports:)) welcome.

History

  • Initial version - 11:23 AM 4/4/2007
  • Updated - 8:52 AM 4/5/2007
    • Fixed bugs that prevented inner classes and generic types from being filtered correctly
    • Added wildcard matching for type names

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here