I've been trying out jbEvain's powerful Mono.Cecil library for just about two weeks now. I've created the following function :
/// <summary>
/// Returns true only if they match.
/// </summary>
private bool CompareMethodDefinitionWithCodeFunction(
EnvDTE.CodeFunction pCodeFunction,
Mono.Cecil.MethodDefinition pMethodDefintion)
{
return pMethodDefintion.Name.Equals(pCodeFunction.Name)
&& pMethodDefintion.Parameters.Count == pCodeFunction.Parameters.Count;
}
The goal is to determine whether pCodeFunction
and pMethodDefinition
are refering to the same function definition or not. So far, I am able to compare the functions' names and the number of parameters they have. I am well aware that it's not enough to certify that they really are refering to the same function. I need help on improving my comparison. For instance, I believe one should always compare the parameter types in order to take potential function overrides into account.
I have tried comparing the parameter types but none of my attempts prevailed. Allow me to demonstrate.
I thought I could compare the types as strings so I added abit of code like so :
/// <summary>
/// Returns true only if they match.
/// </summary>
private bool CompareMethodDefinitionWithCodeFunction(
EnvDTE.CodeFunction pCodeFunction,
Mono.Cecil.MethodDefinition pMethodDefintion)
{
foreach (ParameterDefinition paramDef in pMethodDefintion.Parameters)
{
Debug.WriteLine(paramDef.ParameterType.FullName);
}
foreach (CodeElement ce in pCodeFunction.Parameters)
{
CodeParameter codeParameter = ce as CodeParameter;
Debug.WriteLine(codeParameter.Type.AsFullName);
}
return pMethodDefintion.Name.Equals(pCodeFunction.Name)
&& pMethodDefintion.Parameters.Count == pCodeFunction.Parameters.Count;
}
Given that pCodeFunction
was refering to the following VB.Net function at runtime
Public Function SomeFunction(ByVal arg As List(Of String)) As Object
Return New Object()
End Function
I got the following output
System.Collections.Generic.List`1<System.String>
System.Collections.Generic.List(Of System.String)
I would prefer not to mess around with these two output values and try to parse them so that they match because this doesn't seem like a very "reliable" way to compare types. What is be the most reliable way to compare the parameter types?
This function must be able to seek a function's definition so long as the source code is either VB or C#.
I am currently using the latest Mono.Cecil build (3.12.1) which you can download here
If you want to use my function and insert it in a test class that you've made, you will need the following imports :
using EnvDTE;
using Mono.Cecil;
I believe, after a few more attemps at comparing them appropriately, that there aren't any "proper" ways available out there to compare these two types of objects.
But, I have found a different solution that implies calculating a function index relative to every other function defined within the class. It can get a tiny bit complicated when we start taking the constructors defined in the IL code in consideration but I still think it's appropriate to post this answer out here since it has been my final solution so far. And to be quite frank, the solution as a whole is quite simple.
Allow me to lay out a simple class for demonstrative purposes :
class Class1
{
public static void Function1(string arg1, string arg2)
{
//business logic
}
public static Object Function2(Object arg1)
{
//business logic
}
public static void Function2(List<string> arg1)
{
//business logic
}
}
What is my function index supposed to be? Using my Class1 example, simply put, the function's corresponding indexes would be :
Of course, the said function index isn't some property that comes with EnvDTE. I will have to calculate it myself. To implement it, I created a class that contains a EnvDTE.CodeFunction
property as well as an int
property (intended for the function index).
public class CodeFunctionWithIndex
{
public CodeFunction CodeFunction { get; set; }
public int Index { get; set; }
}
As for our Mono.Cecil.MethodDefinition
's function indexes, since we are looping on them (see main post), we can easily calculate their indexes.
It doesn't simply end here though! There are a few things that I need to mention if you want to use the same approach.
To my limited understanding of what goes on behind Mono.Cecil's convenient library, the list of MethodDefinition
we are looping through contains all the functions that were generated in IL code after our dll was compiled. But, the class from which our EnvDTE.CodeFunctions
reside isn't compiled.
Mono.Cecil.Type
(AKA class) contain as many functions as a EnvDTE.ProjectItem
(refering to the class)?No!
Here is what we will have to consider : the constructor(s). A class may or may not have explicitly defined constuctors. But, a Mono.Cecil.Type
(AKA the class object of Mono.Cecil
) must contain at least one constructor. And believe me, if you don't explicitly define your own constructor, there will be one in the Mono.Cecil.Type
!
Finding out if a constructor is explicitly defined within our EnvDTE.ProjectItem
(refering to the class) isn't such a hard task. Well... unless you consider the following code complicated.
private List<CodeFunctionWithIndex> GetExplicitlyDefinedConstructors(vsCMElement pRequestedCodeElementKind, CodeElements pCodeElements)
{
int nbCodeFunction = 0; //calculated function index
List<CodeFunctionWithIndex> constructorList = new List<CodeFunctionWithIndex>();
if (pCodeElements != null)
{
foreach (CodeElement element in pCodeElements)
{
//if current element is a namespace
if (element.Kind == vsCMElement.vsCMElementNamespace)
{
constructorList = GetExplicitlyDefinedConstructors(pRequestedCodeElementKind, ((EnvDTE.CodeNamespace)element).Members);
if (!constructorList.Any())
continue;
return constructorList;
}
//if current element is a class
else if (element.Kind == vsCMElement.vsCMElementClass)
{
nbCodeFunction = 0;
constructorList = GetExplicitlyDefinedConstructors(pRequestedCodeElementKind, ((EnvDTE.CodeClass)element).Members);
if (!constructorList.Any()) //because there might be more than one class defined within the active file
continue;
return constructorList;
}
//if current element's kind equals the requested kind
else if (element.Kind == pRequestedCodeElementKind)
{
nbCodeFunction++;
//if it's a constructor, add its index to the list of constructor indexes
if (((CodeFunction)element).FunctionKind.ToString().Contains(vsCMFunction.vsCMFunctionConstructor.ToString()))
{
constructorList.Add(
new CodeFunctionWithIndex()
{
CodeFunction = ((CodeFunction)element),
Index = nbCodeFunction
});
}
}
}
}
return constructorList;
}
And here is how I am calling this function to find out if I have any explicitly defined constructors :
GetExplicitlyDefinedConstructors(
vsCMElement.vsCMElementFunction,
DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElements)
.Any();
But, if there aren't any constructors defined in it, how can our Mono.Cecil.MethodDefinition
's function index match with our EnvDTE.CodeFunction
's function index?.
Here is the big idea of my solution (tested) :
In VB.Net, if there is no explicitly defined constructor within the class, the constructor in the IL code will be situated at the beguining of the class (function index 0).
In C#.Net, if there is no explicitly defined constructor within the class, the constructor in the IL code will be situated at the end of the class (last function index).
Here is what my function CompareMethodDefinitionWithCodeFunction
proposed in my first post looks like today (yes it's been renamed... I apologize for that):
public MethodDefinition FindMethodDefinition(CodeFunctionWithIndex pCodeFunction, bool pHasAnExplicitlyDefinedCtor)
{
//Get the assembly that should contain the function we seek
//Note : this is done by comparing pCodeFunction's assembly name to every assembly's name (without the extension)
ModuleDefinition assemblyContainingMethod = assemblies
.Where(assem =>
assem.Name.Split(new char[] { '.' }).FirstOrDefault()
.Equals(pCodeFunction.CodeFunction.ProjectItem.ContainingProject.Properties.Item("AssemblyName").Value, StringComparison.CurrentCultureIgnoreCase))
.FirstOrDefault();
//Get the class that should contain the function we seek
//Note : pCodeFunction.Parent.Name is the class name of our pCodeFunction
TypeDefinition classContainingMethod =
assemblyContainingMethod.Types
.Where(cl => cl.Name.Equals(((CodeClass)pCodeFunction.CodeFunction.Parent).Name))
.FirstOrDefault();
//below is what you want to see
bool isCtorAtIndexZero = DTE.ActiveDocument.ProjectItem.Name.EndsWith(".vb");
int functionIndex = 0;
for (int i = 0; i < classContainingMethod.Methods.Count; i++)
{
if (!pHasAnExplicitlyDefinedCtor && isCtorAtIndexZero && i == 0)
continue;
if (functionIndex == pCodeFunction.Index)
return classContainingMethod.Methods[i];
functionIndex++;
}
return null;
}
This code is extracted from a working project.
The assemblies
variable is a class property of type List<ModuleDefinition>
. By the time this function is called, it will contain the assembly in which the function we seek can be found.
Bear with me as I can only clarify so much. The project is rather big, in my opinion anyways, and it can perform many operations that I need to omit from this post as it is not directly related to the question in the first place.
Hope this helps at least a tiny bit. I apologize for the wall of text.