Linq Where子句中使用变量失败的原因排查
嘿,这个问题我之前也碰到过!咱们来一步步拆解清楚:
你看到的错误Attempt by method 'DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure, Project)' to access type 'Program+<>c__DisplayClass0' failed,本质是闭包类型的访问权限冲突。当你在LINQ表达式里引用外部变量i时,C#编译器会自动生成一个嵌套的闭包类(就是报错里的Program+<>c__DisplayClass0)来持有这个变量,但某些LINQ提供者(或者dotnetfiddle这种特殊的在线执行环境)在动态生成代码访问这个闭包类时,因为类的默认访问级别是private,导致了权限不足的问题。
而用常量的时候为啥没问题?因为编译器会直接把常量值硬编码到LINQ表达式里,根本不需要生成闭包类来存变量,自然就不会触发这个权限问题。
下面给你几个可行的解决方案:
方案1:把变量复制到局部临时变量
最简单的办法就是把要捕获的变量复制到一个局部临时变量里,这样编译器生成的闭包结构会更简单,也能绕开权限限制:
int i = 3; var localI = i; // 复制到局部变量 List<Project> proj = Repo.Projects.Where(p => p.ID < localI).ToList();
如果变量值是固定的,直接用const定义常量也能解决:
const int i = 3; List<Project> proj = Repo.Projects.Where(p => p.ID < i).ToList();
方案2:修正类型不匹配的小问题
另外注意你代码里的一个小bug:FirstOrDefault()返回的是单个Project对象,不是List<Project>,所以赋值的时候会报类型错误,得改成这样:
int i = 1; Project proj = Repo.Projects.FirstOrDefault(p => p.ID == i); // 或者保留Where的写法也可以,但没必要,直接用FirstOrDefault更简洁
方案3:手动构建表达式树(适合ORM场景)
如果你用的是Entity Framework这类支持表达式树的ORM,还可以手动构建表达式来避免闭包捕获:
int i = 3; // 手动构建表达式树 var param = Expression.Parameter(typeof(Project), "p"); var compareExpr = Expression.LessThan( Expression.Property(param, nameof(Project.ID)), Expression.Constant(i) ); var lambda = Expression.Lambda<Func<Project, bool>>(compareExpr, param); List<Project> proj = Repo.Projects.Where(lambda).ToList();
方案4:切换.NET版本
dotnetfiddle的旧版本.NET框架对闭包的处理可能有局限,尝试切换到.NET Core 3.1及以上版本,新版本的编译器和运行时已经修复了很多这类闭包权限的问题。
内容的提问来源于stack exchange,提问作者grochocz

