确定 Mono.Cecil.MethodDefinition 是否引用与给定 EnvDTE.CodeFunction 相同的函数

Determining if a Mono.Cecil.MethodDefinition is refering to the same function as a given EnvDTE.CodeFunction

上下文

我已经试用 jbEvain 强大的 Mono.Cecil 库大约两周了。我创建了以下函数:

/// <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;
}

目标

目标是确定 pCodeFunctionpMethodDefinition 是否引用相同的函数定义。到目前为止,我能够比较函数的名称和它们拥有的参数数量。我很清楚,仅仅证明它们确实指的是同一个函数是不够的。我需要帮助改进我的比较。例如,我认为应该始终比较参数类型,以便将潜在的函数覆盖考虑在内。

之前的尝试

我尝试比较参数类型,但 none 我的尝试占了上风。请允许我演示。

我想我可以将类型作为字符串进行比较,所以我添加了一些代码,如下所示:

/// <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;
}

鉴于 pCodeFunction 在运行时引用了以下 VB.Net 函数

Public Function SomeFunction(ByVal arg As List(Of String)) As Object
    Return New Object()
End Function

我得到了以下输出

System.Collections.Generic.List`1<System.String>
System.Collections.Generic.List(Of System.String)

我不想弄乱这两个输出值并尝试解析它们以使它们匹配,因为这似乎不是比较类型的非常“可靠”的方法。比较参数类型最可靠的方法是什么?

奖金

只要源代码是 VB 或 C#,此函数就必须能够查找函数的定义。

我目前使用的是最新的 Mono.Cecil 版本 (3.12.1),您可以下载 here

如果您想使用我的函数并将其插入到您所做的测试 class 中,您将需要以下导入:

using EnvDTE;
using Mono.Cecil;

我相信,在多次尝试适当地比较它们之后,没有任何“适当”的方法可以用来比较这两种类型的对象。

但是,我发现了一个不同的解决方案,该解决方案意味着计算相对于 class 中定义的每个其他函数的函数索引。当我们开始考虑 IL 代码中定义的构造函数时,它可能会变得有点复杂,但我仍然认为 post 这个答案在这里是合适的,因为它是我迄今为止的最终解决方案。坦率地说,整个解决方案非常简单。

请允许我列出一个简单的 class 用于演示目的:

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
    }
}

函数索引

我的函数索引应该是多少?使用我的 Class1 示例,简单地说,函数的相应索引将是:

  • 0
  • 1
  • 2

当然,所说的函数索引并不是EnvDTE自带的属性。我将不得不自己计算。为了实现它,我创建了一个 class,其中包含一个 EnvDTE.CodeFunction 属性 以及一个 int 属性(用于函数索引)。

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

至于我们Mono.Cecil.MethodDefinition的函数索引,因为我们在循环它们(见main post),我们可以很容易地计算出它们的索引。

它并没有就此结束!如果您想使用相同的方法,我需要提及几件事。

我对 Mono.Cecil 便捷库背后发生的事情的了解有限,我们正在循环的 MethodDefinition 列表包含在我们的 dll 被调用后在 IL 代码中生成的所有函数编译。但是,我们的 EnvDTE.CodeFunctions 所在的 class 没有被编译。

Mono.Cecil.Type(又名class)是否包含与EnvDTE.ProjectItem(指class)一样多的功能?

没有! 这是我们必须考虑的:构造函数。 class 可能有也可能没有明确定义的构造函数。但是,Mono.Cecil.Type(也就是 Mono.Cecil 的 class 对象) 必须包含至少一个构造函数。相信我,如果您不显式定义自己的构造函数,Mono.Cecil.Type!

中会有一个

找出构造函数是否在我们的 EnvDTE.ProjectItem(指的是 class)中明确定义并不是一项艰巨的任务。嗯...除非你认为下面的代码很复杂。

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;
}

下面是我调用此函数以查明是否有任何显式定义的构造函数的方式:

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

但是,如果里面没有定义任何构造函数,我们Mono.Cecil.MethodDefinition的函数索引如何匹配我们EnvDTE.CodeFunction的函数索引?

这是我的解决方案的主要想法(已测试):

  1. 在VB.Net中,如果class中没有明确定义的构造函数,IL代码中的构造函数将位于[=95=的开头](函数索引 0)。

  2. 在C#.Net中,如果class中没有显式定义构造函数,IL代码中的构造函数将位于端[=88] =] 的 class(最后一个函数索引)。

这是我在第一个 post 中提议的函数 CompareMethodDefinitionWithCodeFunction 今天的样子(是的,它已重命名......对此我深表歉意):

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;
}

此代码是从一个工作项目中提取的。

assemblies 变量是类型 List<ModuleDefinition> 的 class 属性。调用此函数时,它将包含可以在其中找到我们要查找的函数的程序集。

请耐心等待,我只能澄清这么多。这个项目相当大,无论如何在我看来,它可以执行许多我需要从这个 post 中省略的操作,因为它首先与问题没有直接关系。

希望这至少能有所帮助。我为文字墙道歉。