确定 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;
}
目标
目标是确定 pCodeFunction
和 pMethodDefinition
是否引用相同的函数定义。到目前为止,我能够比较函数的名称和它们拥有的参数数量。我很清楚,仅仅证明它们确实指的是同一个函数是不够的。我需要帮助改进我的比较。例如,我认为应该始终比较参数类型,以便将潜在的函数覆盖考虑在内。
之前的尝试
我尝试比较参数类型,但 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
的函数索引?
这是我的解决方案的主要想法(已测试):
在VB.Net中,如果class中没有明确定义的构造函数,IL代码中的构造函数将位于[=95=的开头](函数索引 0)。
在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 中省略的操作,因为它首先与问题没有直接关系。
希望这至少能有所帮助。我为文字墙道歉。
上下文
我已经试用 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;
}
目标
目标是确定 pCodeFunction
和 pMethodDefinition
是否引用相同的函数定义。到目前为止,我能够比较函数的名称和它们拥有的参数数量。我很清楚,仅仅证明它们确实指的是同一个函数是不够的。我需要帮助改进我的比较。例如,我认为应该始终比较参数类型,以便将潜在的函数覆盖考虑在内。
之前的尝试
我尝试比较参数类型,但 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
的函数索引?
这是我的解决方案的主要想法(已测试):
在VB.Net中,如果class中没有明确定义的构造函数,IL代码中的构造函数将位于[=95=的开头](函数索引 0)。
在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 中省略的操作,因为它首先与问题没有直接关系。
希望这至少能有所帮助。我为文字墙道歉。