C# lambda内使用ref参数报错原因及泛型列表属性查找实现
异常抛出原因
这是C#的明确语法限制,核心原因有两点:
ref/out/in参数传递的是调用方变量的栈内存地址,而非值副本。- 你传入
FirstOrDefault的lambda表达式是延迟执行的,它的实际执行时机可能晚于FindList方法返回的时刻——此时FindList的栈帧已经被销毁,ref参数指向的内存地址已经失效,会引发非法内存访问问题,因此编译器直接禁止在lambda、匿名方法、查询表达式中捕获这类传引用的参数。
另外你在这里加ref本身就是多余的:你的逻辑只需要读取属性值做相等比较,不需要修改外部传入的propertyValue变量,完全没有传引用的必要。
实现目标功能的修改方案
核心修改思路
- 移除
FindList方法参数和调用处多余的ref关键字 - 替换相等判断逻辑:反射的
GetValue方法返回的是object类型,直接用==判断值类型(比如int、bool)时会因装箱产生引用不等的问题,改用Equals()方法做值相等判断即可 - 增加属性存在性校验,避免传入不存在的属性名时抛出无明确提示的空引用异常
- 提前缓存属性元数据,避免循环内重复执行反射产生不必要的性能开销
可直接运行的完整代码
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; public class User{ public string name {get; set;} public int id {get; set;} } public class HelloWorld { public static void Main(string[] args) { List<User> Users = new List<User>{ new User{name="John", id=1}, new User{name="Deer", id=2}, new User{name="Bruce", id=3} }; // 按int类型的id属性查找 var u1 = FindList<User, int>(Users, "id", 2); Console.WriteLine($"{u1.name} {u1.id}"); // 输出 Deer 2 // 按string类型的name属性查找 var u2 = FindList<User, string>(Users, "name", "Bruce"); Console.WriteLine($"{u2.name} {u2.id}"); // 输出 Bruce 3 } public static T FindList<T, K>(List<T> list, string propertyName, K propertyValue) { PropertyInfo targetProp = typeof(T).GetProperty(propertyName); if (targetProp == null) { throw new ArgumentException($"类型{typeof(T).Name}不存在名为{propertyName}的公开属性"); } return list.FirstOrDefault(item => { object currentPropValue = targetProp.GetValue(item, null); return propertyValue.Equals(currentPropValue); }); } }
补充说明
如果后续对性能有更高要求,可以用表达式树预先编译属性访问的委托,替代每次反射取值的逻辑,不过对于日常开发、数据量不大的场景,上面的实现已经完全够用,逻辑和你写的JS版本完全对齐。
内容的提问来源于stack exchange,提问作者ytan11
相关产品推荐
相关产品推荐

