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逻辑完全可控,是这类场景的最优选择:
- 保留前序IL流程:依次压入输入参数、调用
object.GetType()、调用Type.GetProperties()获取PropertyInfo[]数组 - 定义两个局部变量:一个存储当前遍历的数组索引,初始值为0;一个存储当前遍历到的
PropertyInfo实例 - 构建循环跳转:
- 每次循环先判断当前索引值是否大于等于数组长度,是则抛出
InvalidOperationException,和原LINQFirst找不到元素的行为保持一致 - 按当前索引从数组中取出对应
PropertyInfo实例存入局部变量 - 调用
CustomAttributeExtensions.GetCustomAttribute<KeyAttribute>()方法,判断当前属性是否携带KeyAttribute - 判断为真时跳出循环,将当前
PropertyInfo实例压入栈顶
- 每次循环先判断当前索引值是否大于等于数组长度,是则抛出
- 后续逻辑保持不变:压入原始输入参数引用,调用
PropertyInfo.GetValue()拿到最终属性值,将结果作为object类型返回
兼容方案:保留Enumerable.First调用流程的修正方式
如果必须沿用LINQ方法的实现路径,按以下步骤调整即可跑通:
- 单独构建独立的
DynamicMethod作为谓词实现:方法签名严格匹配bool Predicate(PropertyInfo property),IL逻辑为接收PropertyInfo参数,调用GetCustomAttribute<KeyAttribute>()方法,判断返回值是否不为null,最终返回布尔判断结果 - 将上述动态方法直接转换为
Func<PropertyInfo, bool>类型的委托实例,存入类的静态只读字段 - 正确定位泛型
First方法:先获取包含两个入参的Enumerable.First<TSource>泛型方法定义,用typeof(PropertyInfo)替换泛型参数,得到可直接调用的具体方法实例 - 修正IL压栈顺序:栈上依次放入
PropertyInfo[]数组引用、静态字段中存储的谓词委托引用,两个参数刚好匹配First方法的入参要求,调用方法后拿到带KeyAttribute的PropertyInfo实例,后续接入GetValue的调用逻辑即可。
内容的提问来源于stack exchange,提问作者Meydan Ozeri
相关产品推荐
相关产品推荐

