【问题标题】:Predicate member referencing谓词成员引用
【发布时间】:2021-05-15 20:23:25
【问题描述】:

编译器将 LINQ 函数谓词转换为名称为“Predicate`1”的 FieldInfo。我想分析这些 FieldInfo 以查看引用了哪些成员(如果有)。例如下面的谓词使用用户定义的方法“ArchName”:

list.FindAll(m => !m.ArchName().Contains("<>c"))

我不知道 FieldInfo 有任何相关的 MethodInfo 成员。可以做什么?

提前致谢!

【问题讨论】:

  • 会不会是扩展方法?
  • @HansKesting 我不喜欢使用第三方资源。你有什么想法?
  • 扩展方法不需要是第三方,可以在项目的其他地方定义。在 Visual Studio 中,将光标放在该方法上并按 F12
  • ArchName() 是一个扩展方法,是的。
  • 您必须解析方法体,然后解析找到的令牌。看看这里:stackoverflow.com/a/33034906/4035472

标签: c# reflection metadata cil


【解决方案1】:

这与 LINQ 无关。它是纯粹的 C# lambda 表达式 + List。

在这种情况下,Predicate 1is not a *field name*, but instead, a type, more preciselySystem.Predicate`。

在我看来,您想分析方法体以查找对该类型的引用。尽管这可以通过反射来实现,但使用 Mono.Cecil 会更容易;在这种情况下,您可以使用类似的东西(请注意,这段代码并不完整……您需要考虑错误、静态字段等):

using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;

class Bar
{
    void Foo(List<int> items)
    {
        // A non sense expression :)
        var r = items.FindAll(i => i % 2 == 0 || i.ToString() == "A");
    }

    static void Main()
    {
        var a = AssemblyDefinition.ReadAssembly(typeof(Bar).Assembly.Location);

        var allMethods = a.MainModule.GetTypes().SelectMany(t => t.Methods);

        // find instructions referencing System.Predicate<T>
        var allMethodsReferencingSystemPredicate = allMethods.Where(m => m.Body.Instructions.Any(i => i.OpCode == OpCodes.Newobj && i.Operand.ToString().Contains("System.Predicate`1")));
        foreach(var m in allMethodsReferencingSystemPredicate)
        {
            System.Console.WriteLine($"Analyzing {m}");
            
            var instReferencingSystemPredicate = m.Body.Instructions.Where(i => i.OpCode == OpCodes.Newobj && i.Operand.ToString().Contains("System.Predicate`1"));            
            foreach(var inst in instReferencingSystemPredicate)
            {
                System.Console.WriteLine($"Checking {inst} / {inst.Previous}");
                if (inst.Previous.OpCode != OpCodes.Ldftn)
                {
                    System.Console.WriteLine($"Something went wrong. Expected LdFnt, got {inst.Previous.OpCode} in instruction {inst.Previous}");
                    continue;
                }
                // get the method being referenced.
                var method = ((MethodReference) inst.Previous.Operand).Resolve();
                
                // Analyze the method body looking for calls to `ToString()` (you will replace this with your own checks...)
                foreach(var methodInst in method.Body.Instructions)
                {
                    //System.Console.WriteLine($"Checking: {methodInst}");
                    if ( (methodInst.OpCode == OpCodes.Callvirt || methodInst.OpCode == OpCodes.Call) && methodInst.Operand.ToString().Contains("ToString()"))
                    {
                        System.Console.BackgroundColor = System.ConsoleColor.DarkCyan;
                        System.Console.WriteLine($"{method} (used in {m}) references ToString(): {methodInst}");
                    } 
                }
            }
        }

    }
}

【讨论】:

  • 感谢 Vagaus 澄清 lambda 表达式。实际上,我的 lambda 表达式调用了另一个方法——这是我想看到的行为。我使用 ` FieldInfo.FieldType.GetMembers() ` 从这个谓词中检索方法。但即使在这些方法中,也没有按照示例使用我的成员“ArchName”的名称的调用指令。
  • 另外,我不想使用 Cecil;而我正在构建自己的工具。
  • [实际上,我的 lambda 表达式调用了另一个方法 - 这是我想看到的行为。] 这就是在我的示例中我的 lambda 调用Int32.ToString() 的原因。 [不想使用塞西尔;而我正在构建自己的工具] 除非您的 tool 是操纵 IL 的库,否则我不明白您为什么不能使用 Mono.Cecil :) 。您要么需要使用反射(我认为这需要大量的低级 byte code -> il 翻译)或更多 high level lib(如 Cecil)。你当然也可以使用“System.Reflection.Metadata”,但这也并不容易。
  • 跟进时间过长。 @Vagaus,你说得对,彻底研究 CIL 是一种皇家痛苦。
  • 确实,学习 CIL 并不是一件容易的事,但至少恕我直言,这很有趣 :) sharplab.io 是了解 C# 如何转换为 IL 的好资源。
猜你喜欢
  • 1970-01-01
  • 2018-04-02
  • 1970-01-01
  • 2018-04-24
  • 1970-01-01
  • 1970-01-01
  • 2022-10-13
  • 1970-01-01
  • 2011-04-16
相关资源
最近更新 更多