You need to enable JavaScript to run this app. C# 使用Reflection.Emit如何调用带谓词的LINQ First方法
优惠活动
大模型
产品
解决方案
定价
更多

C# 使用Reflection.Emit如何调用带谓词的LINQ First方法

Reflection.Emit实现表主键取值功能的卡点与落地指导

待复现的原生C#实现

public static object GetTableKeyValue(object tableValue) => tableValue.GetType().GetProperties().First(property => property.GetCustomAttribute<KeyAttribute>() is not null).GetValue(tableValue);

当前已编写的Emit代码

public static object GetTableKeyValueReflectionEmit(object val)
{
    var getTableKeyValue = new DynamicMethod("GetTableKeyValueReflectionEmit", typeof(object), new Type[] { typeof(object) }, typeof(object).Module);
    getTableKeyValue.DefineParameter(1, ParameterAttributes.In, "tableValue");
    var il = getTableKeyValue.GetILGenerator(256);

    il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Call, typeof(object).GetMethod(nameof(object.GetType)), null);
    il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetProperties), new Type[] { }), null);
    Func<PropertyInfo, bool> predicate = property => (!property.GetCustomAttribute<KeyAttribute>().Equals(null));
    // 实现卡点位置
    il.EmitCall(OpCodes.Call, typeof(Enumerable).GetMember(nameof(Enumerable.First)).OfType<MethodInfo>().First(method => method.GetParameters().Length == 1), new Type[] { typeof(Func<PropertyInfo, bool>) });
    il.EmitCall(OpCodes.Call, typeof(PropertyInfo).GetMethod(nameof(PropertyInfo.GetValue), new Type[] { typeof(object) }), new Type[] { typeof(object) });

    il.Emit(OpCodes.Ret);

    var getTableKeyValueDelegate = (GetTableKeyValueDelegate)getTableKeyValue.CreateDelegate(typeof(GetTableKeyValueDelegate));
    return getTableKeyValueDelegate(val);
}

现存核心问题

  • IL栈状态不平衡:执行完Type.GetProperties()后,栈顶仅存储PropertyInfo[]的数组引用,未将谓词委托压入栈,调用First方法时参数数量不匹配,直接触发非法程序格式异常
  • 目标方法定位错误:现有代码筛选的是单参数、无谓词的Enumerable.First重载,真正带谓词的First方法需要两个入参(源序列、谓词委托),方法筛选逻辑完全错位
  • 谓词构造逻辑缺失:没有明确的IL路径构造Func<PropertyInfo, bool>类型的判断逻辑,无法将其作为合法参数传递给First方法

额外修正:原谓词代码存在空引用风险
原写法!property.GetCustomAttribute<KeyAttribute>().Equals(null)在属性无KeyAttribute时,会因GetCustomAttribute返回null触发空引用异常,和目标方法的is not null判断逻辑不一致,实现时需要替换为正确的null判断逻辑。

实现方案

推荐方案:跳过LINQ扩展方法,手写IL遍历属性数组

这种方案无额外委托调用、泛型方法实例化的开销,IL逻辑完全可控,是这类场景的最优选择:

  1. 保留前序IL流程:依次压入输入参数、调用object.GetType()、调用Type.GetProperties()获取PropertyInfo[]数组
  2. 定义两个局部变量:一个存储当前遍历的数组索引,初始值为0;一个存储当前遍历到的PropertyInfo实例
  3. 构建循环跳转:
    • 每次循环先判断当前索引值是否大于等于数组长度,是则抛出InvalidOperationException,和原LINQ First找不到元素的行为保持一致
    • 按当前索引从数组中取出对应PropertyInfo实例存入局部变量
    • 调用CustomAttributeExtensions.GetCustomAttribute<KeyAttribute>()方法,判断当前属性是否携带KeyAttribute
    • 判断为真时跳出循环,将当前PropertyInfo实例压入栈顶
  4. 后续逻辑保持不变:压入原始输入参数引用,调用PropertyInfo.GetValue()拿到最终属性值,将结果作为object类型返回

兼容方案:保留Enumerable.First调用流程的修正方式

如果必须沿用LINQ方法的实现路径,按以下步骤调整即可跑通:

  1. 单独构建独立的DynamicMethod作为谓词实现:方法签名严格匹配bool Predicate(PropertyInfo property),IL逻辑为接收PropertyInfo参数,调用GetCustomAttribute<KeyAttribute>()方法,判断返回值是否不为null,最终返回布尔判断结果
  2. 将上述动态方法直接转换为Func<PropertyInfo, bool>类型的委托实例,存入类的静态只读字段
  3. 正确定位泛型First方法:先获取包含两个入参的Enumerable.First<TSource>泛型方法定义,用typeof(PropertyInfo)替换泛型参数,得到可直接调用的具体方法实例
  4. 修正IL压栈顺序:栈上依次放入PropertyInfo[]数组引用、静态字段中存储的谓词委托引用,两个参数刚好匹配First方法的入参要求,调用方法后拿到带KeyAttribute的PropertyInfo实例,后续接入GetValue的调用逻辑即可。

内容的提问来源于stack exchange,提问作者Meydan Ozeri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.14 23:36:06