Reflector.CodeMetrics dynamically loads code metrics tabs at runtime by using Reflection. This means that anybody can write his own metric whithout recompiling CodeMetrics.... an Addin API inside another AddIn api :)
Here's a quick review of the steps to get your new cool code metric started. In this example, we will display the number of arguments per method (simple example).
Setting up the project
Start a new assembly project, add references to Reflector.exe, Reflector.CodeMetrics.dll and Reflector.Helpers.dll
Creating a new CodeMetric
All code metrics implement the ICodeMetric interface (this what the CodeMetricManager looks for):
public interface ICodeMetric : IServiceComponent{ string Name { get;} string FullName { get;} ComputationState State { get;} DataTable Result { get;} Object FindItem(DataRow row); bool Enabled { get;set;} CodeMetricLevel Level { get;} event ComputationProgressEventHandler Progress; void Compute(); void Abort();}
public interface ICodeMetric : IServiceComponent
{
}
Of course, you don't have to implement it from scratch. You can use the abstract base class CodeMetricBase to get started:
public class ArgumentCountCodeMetric : CodeMetricBase { // setting up name, full name and level of metric public ArgumentCountCodeMetric() :base("AC","Argument Count",CodeMetricLevel.Method) {} // adding columns protected void AddColumns() { this.AddColumn("Argument Count"); } ... }
It is important to note that we specify that the metrics target Method (can also target Type, Module). The AddColumns method is used to all the columns you want to show in the grid.
Add the metric computation
Time to get to the real fun, computing the metric... First, we provide a method GetTestCount that returns the number of steps. This number is used to scale the progress bar:
private int GetStepCount() { int count = 0; foreach (IAssembly assembly in this.Services.AssemblyLoader.Assemblies) { if (this.Services.CodeMetricManager.IsIgnored(assembly)) continue; foreach (IModule module in assembly.Modules) count += module.Types.Count; } return count; }
It is important to note that we check for each IAssembly if it has to be ignored. This is directly linked to the checkbox control on the CodeMetrics page. Note that we coun the types only, if you show progress on methods, almost all the CPU time is spent to update the controls. Next, we write the method that create a row in the grid from a IMethodDeclaration:
private void ComputeMethod(IMethodDeclaration method) { IMethodBody body = method as IMethodBody; if (body == null) return; this.AddRow( method, body.Parameters.Count ); }
The AddRow methods will add a row in the underlying DataTable, etc...
Implementing Compute
We have all the pieces, we just need to implement Compute:
public override void Compute() { base.Compute(); int stepCount = GetStepCount(); int count = 0; foreach (IAssembly assembly in this.Services.AssemblyLoader.Assemblies) { if (this.Services.CodeMetricManager.IsIgnored(assembly)) continue; foreach (IModule module in assembly.Modules) { foreach (ITypeDeclaration type in module.Types) { this.OnProgress( new ComputationProgressEventArgs( count++, stepCount, "Analysing {0}", type ) ); foreach (IMethodDeclaration method in type.Methods) { if (this.CheckForAbort()) return; ComputeMethod(method); } } } } this.Finished(); }
The metric is ready, we just need to register it to the manager when the Addin is loaded.
Setting up a package and registering CodeMetrics
Reflector looks for IPackage class when loading Addins. Reflector.Helpers contains a smart implementation of that addin that will automatically load the code metrics the assembly contains, so all you have to do is to inherit from PackagePage (well almost):
namespace Reflector.CodeMetrics { public sealed class CodeMetricPackage : PackageBase { [ReflectorWindow( Name = "Dummy control to make Reflector happy", Caption = "Dummy control to make Reflector happy" )] [ReflectorCommandBar(CommandBarTarget.Tools)] private UserControl dummy = new UserControl(); } }
If you do not actually load a new window, Reflector gets pissed and unload the addin. Therefore, just add a dummy window. At this point, recompile (makes sure you target the right .Net framework), load and enjoy.
Debugging
To debug, just start Reflector as external process and break point on your code.
Page rendered at Friday, August 29, 2008 12:29:06 AM UTC
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.