如何分析LINQ谓词生成的Predicate`1类型FieldInfo引用的成员
实现分析逻辑的两种方案
方案1:调整代码改用表达式树(最简便,适合可修改业务代码的场景)
如果你可以修改传入FindAll的参数类型,不需要直接用Predicate<T>委托,而是改用Expression<Predicate<T>>,可以直接遍历表达式树节点拿到所有引用的成员,不需要处理底层IL。
示例代码逻辑:
Expression<Predicate<YourType>> expr = m => !m.ArchName().Contains("<>c"); // 自定义表达式访问器遍历所有成员引用 class MemberAccessVisitor : ExpressionVisitor { public List<MemberInfo> ReferencedMembers { get; } = new(); protected override Expression VisitMethodCall(MethodCallExpression node) { ReferencedMembers.Add(node.Method); return base.VisitMethodCall(node); } protected override Expression VisitMember(MemberExpression node) { ReferencedMembers.Add(node.Member); return base.VisitMember(node); } } // 使用访问器 var visitor = new MemberAccessVisitor(); visitor.Visit(expr); // 此时visitor.ReferencedMembers里就包含了ArchName、String.Contains这些引用的方法 // 要转成Predicate委托直接调用Compile()即可 var predicate = expr.Compile(); var result = list.FindAll(predicate);
这种方案不需要处理编译器生成的闭包类、FieldInfo相关的底层逻辑,可靠性最高。
方案2:从FieldInfo对应的委托实例分析IL(适合无法修改业务代码、只能拿到编译后字段的场景)
你拿到的Predicate1类型的FieldInfo,实际存储的是委托实例,你需要先拿到这个委托的MethodInfo`,再解析方法体的IL指令,提取所有引用的成员:
具体步骤
- 第一步:从
FieldInfo拿到委托实例,再取目标方法
// 假设fieldInfo是你拿到的Predicate`1类型的字段,instance是闭包类实例(静态字段的话instance传null) Delegate predicateDel = (Delegate)fieldInfo.GetValue(instance); MethodInfo lambdaMethod = predicateDel.Method;
- 第二步:解析
lambdaMethod的方法体IL,遍历所有成员引用指令
IL中引用成员的指令包括call、callvirt、ldfld、ldsfld、stfld、stsfld、ldtoken等,这些指令的操作数就是对应的MemberInfo。
你可以用现成的IL解析库(比如System.Reflection.Metadata)来做解析,也可以自己实现基础的IL读取逻辑,示例逻辑如下:
byte[] ilBytes = lambdaMethod.GetMethodBody().GetILAsByteArray(); Module module = lambdaMethod.Module; List<MemberInfo> referencedMembers = new List<MemberInfo>(); using (var reader = new BinaryReader(new MemoryStream(ilBytes))) { while (reader.BaseStream.Position < reader.BaseStream.Length) { OpCode opCode; byte op = reader.ReadByte(); if (op == 0xFE) { opCode = (OpCode)BitConverter.ToInt16(new byte[] { reader.ReadByte(), op }, 0); } else { opCode = (OpCode)op; } // 处理调用方法、访问字段的相关指令 if (opCode.OperandType is OperandType.InlineMethod or OperandType.InlineField or OperandType.InlineTok) { int token = reader.ReadInt32(); MemberInfo member = module.ResolveMember(token); referencedMembers.Add(member); } else { // 跳过其他指令的操作数 switch (opCode.OperandType) { case OperandType.InlineNone: break; case OperandType.InlineI: reader.ReadInt32(); break; case OperandType.InlineI8: reader.ReadInt64(); break; case OperandType.InlineR: reader.ReadDouble(); break; case OperandType.ShortInlineI: reader.ReadSByte(); break; case OperandType.ShortInlineR: reader.ReadSingle(); break; // 其余操作数类型可按需补充跳过逻辑 } } } }
解析后你只要过滤掉编译器生成的闭包类成员、系统基础类成员,剩下的就是你要找的用户自定义成员,比如示例里的ArchName方法。
注意点
- 如果你拿到的
FieldInfo是编译器生成的闭包类的字段,要注意闭包类的实例获取逻辑,静态lambda和捕获了外部变量的实例lambda的闭包存储逻辑不一样。 - IL解析要注意处理不同指令的操作数长度,避免读取偏移错误。
内容的提问来源于stack exchange,提问作者Minho17
相关产品推荐
相关产品推荐

