Determining whether Mono.Cecil.MethodDefinition refers to the same function as the specified EnvDTE.CodeFunction

Context

I tried the powerful Mono.Cecil jbEvain library for about two weeks now. I 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; } 

goal

The goal is to determine if pCodeFunction and pMethodDefinition to the same function definition or not. So far, I can compare the names of the functions and the number of parameters that they have. I know well that this is not enough to confirm that they really refer to the same function. I need help improving my comparison. For example, I believe that parameter types should always be compared to take into account potential function overrides.

Previous attempts

I tried to compare parameter types, but none of my attempts prevailed. Let me show you.

I thought I could compare types as strings, so I added abit code as follows:

 /// <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 referencing 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 interfere with these two output values ​​and try to analyze them so that they match, because this does not seem like a very “reliable” way to compare types. What is the most reliable way to compare parameter types?

Bonus Notes

This function should be able to search for a function definition if the source code is either VB or C #.

I am currently using the latest version of Mono.Cecil (3.12.1), which you can download here

If you want to use my function and paste it into the test class that you created, you will need the following imports:

 using EnvDTE; using Mono.Cecil; 
+5
source share
1 answer

I believe that after several additional attempts to match them appropriately, there are no “right” methods available for comparing these two types of objects.

But I found another solution that involves calculating the index of a function relative to any other function defined inside the class. This can be a bit more complicated when we start accepting the constructors defined in the IL code, but I still find it appropriate to post this answer here, as that was my final decision. And, to be honest, the solution as a whole is quite simple.

Let me lay out a simple class for demo 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 } } 

Function index

What should be my function index? Using my Class1 example, just add the appropriate function indexes:

  • 0
  • 1
  • 2

Of course, the specified function index is not some property that comes with EnvDTE. I have to figure it out myself. To implement it, I created a class that contains the EnvDTE.CodeFunction property, as well as the int property (intended for the index of the function).

 public class CodeFunctionWithIndex { public CodeFunction CodeFunction { get; set; } public int Index { get; set; } } 

As for our Mono.Cecil.MethodDefinition function Mono.Cecil.MethodDefinition , since we are fixated on them (see the main post), we can easily calculate their indexes.

This is not just the end! There are several things that I should mention if you want to use the same approach.

To my limited understanding of what happens behind the convenient Mono.Cecil library, the MethodDefinition list that we MethodDefinition over contains all the functions that were generated in the IL code after our dll was compiled. But the class from which our EnvDTE.CodeFunctions does not compile.

Does the Mono.Cecil.Type class (AKA class) have as many functions as EnvDTE.ProjectItem (referring to the class)?

Not! Here's what we will need to consider: constructor (s). A class may or may not have explicitly defined continua. But (AKA - an object of the Mono.Cecil class) must contain at least one constructor . And believe me, if you do not explicitly define your own constructor, then in Mono.Cecil.Type !

Finding if the constructor is explicitly defined in our EnvDTE.ProjectItem (referring to the class) is not such a difficult task. Well ... if you don't think the following code is 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 kind equals the requested kind else if (element.Kind == pRequestedCodeElementKind) { nbCodeFunction++; //if it 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 this is how I call this function to find out if I have any explicitly defined constructors:

 GetExplicitlyDefinedConstructors( vsCMElement.vsCMElementFunction, DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElements) .Any(); 

But, if it doesn't have any constructors, how Mono.Cecil.MethodDefinition our Mono.Cecil.MethodDefinition index match our EnvDTE.CodeFunction ? Function index.

Here is the big idea of ​​my solution (tested):

  • In VB.Net, if there is no explicitly defined constructor in the class, the constructor in the IL code will be located at the beginning of the class (function index 0).

  • In C # .Net, if there is no explicitly defined constructor in the class, the constructor in the IL code will be located at the end of the class (the last function index).

Here is what my CompareMethodDefinitionWithCodeFunction function CompareMethodDefinitionWithCodeFunction in my first post today (yes, it was renamed ... I apologize for this):

 public MethodDefinition FindMethodDefinition(CodeFunctionWithIndex pCodeFunction, bool pHasAnExplicitlyDefinedCtor) { //Get the assembly that should contain the function we seek //Note : this is done by comparing pCodeFunction assembly name to every assembly 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 the working draft.

The assemblies variable is a property of a class of type List<ModuleDefinition> . By the time this function is called, it will contain an assembly in which you can find the desired function.

Bear with me as I can only make this clear. The project is quite large, in my opinion, in any case, and it can perform many operations that I need to omit from this position, since this is not directly related to the issue.

Hope this helps at least a little. I apologize for the text wall.

0
source

All Articles