C# LINQ延迟执行与嵌套方法:更新值未生效原因及解决办法
为什么LINQ延迟执行没跟着参数更新生效?附解决方法
问题出在两个关键点:
- 值类型参数的传递逻辑:你创建
query时调用了GetRange(ints, start, end),这里的start和end是int值类型,传递时会复制一份副本给方法参数。后面在FirstMethod里修改的是原变量的值,和之前传给GetRange的副本毫不相干——值类型的参数传递是按值复制,不是引用,所以GetRange始终用的是初始的0和5来生成序列。 - 迭代器方法的参数绑定时机:
GetRange是带yield的迭代器方法,它的参数在**第一次被调用(也就是创建query的时候)**就固定下来了,后续参数的修改不会反过来改变这个已经绑定好的参数值。虽然Where里的x < limit会捕获外部变量limit,limit的修改确实能生效,但因为GetRange一直返回的是1-5,所以不管limit改成8还是16,结果都是1-5,自然输出重复。
实现预期效果的几种方法
方法一:每次执行时动态构建查询
不要提前创建好query,而是每次需要输出的时候,用最新的参数重新构建查询:
List<int> ints = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; int start = 0; int end = 5; int limit = 10; IEnumerable<int> GetRange(List<int> list, int startGR, int endGR) { for (int i = startGR; i < endGR; i++) { yield return list[i]; } } void FirstMethod(ref int start1, ref int end1, ref int limit1) { start1 = 4; end1 = 9; limit1 = 8; // 每次调用都用最新参数生成查询 SecondMethod(GetRange(ints, start1, end1).Where(x => x < limit1)); start1 = 9; end1 = 14; limit1 = 16; SecondMethod(GetRange(ints, start1, end1).Where(x => x < limit1)); } void SecondMethod(IEnumerable<int> query2) { Console.WriteLine(string.Join(", ", query2)); } FirstMethod(ref start, ref end, ref limit);
这样每次执行查询时都会用最新的参数,输出就是你要的5, 6, 7和10, 11, 12, 13, 14。
方法二:用引用类型包装参数
把start、end、limit放到一个类里,类是引用类型,迭代器方法捕获的是这个类的引用,就能拿到最新的属性值:
List<int> ints = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; // 用类包装参数,变成引用类型 class RangeParams { public int Start { get; set; } public int End { get; set; } public int Limit { get; set; } } RangeParams param = new RangeParams { Start = 0, End = 5, Limit = 10 }; IEnumerable<int> GetQuery() { // 每次迭代时读取param的最新值 for (int i = param.Start; i < param.End; i++) { if (ints[i] < param.Limit) yield return ints[i]; } } void FirstMethod(RangeParams param1) { param1.Start = 4; param1.End = 9; param1.Limit = 8; SecondMethod(GetQuery()); param1.Start = 9; param1.End = 14; param1.Limit = 16; SecondMethod(GetQuery()); } void SecondMethod(IEnumerable<int> query2) { Console.WriteLine(string.Join(", ", query2)); } FirstMethod(param);
方法三:用Func延迟查询构建
用Func<IEnumerable<int>>委托来包装查询逻辑,每次调用委托时才生成最新的查询:
List<int> ints = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; int start = 0; int end = 5; int limit = 10; IEnumerable<int> GetRange(List<int> list, int startGR, int endGR) { for (int i = startGR; i < endGR; i++) { yield return list[i]; } } // 用Func延迟查询的构建,每次调用才会读取最新参数 Func<IEnumerable<int>> queryFactory = () => GetRange(ints, start, end).Where(x => x < limit); void FirstMethod(Func<IEnumerable<int>> queryFactory1, ref int start1, ref int end1, ref int limit1) { start1 = 4; end1 = 9; limit1 = 8; // 调用委托生成最新查询 SecondMethod(queryFactory1()); start1 = 9; end1 = 14; limit1 = 16; SecondMethod(queryFactory1()); } void SecondMethod(IEnumerable<int> query2) { Console.WriteLine(string.Join(", ", query2)); } FirstMethod(queryFactory, ref start, ref end, ref limit);
内容的提问来源于stack exchange,提问作者Michail Golubjev
相关产品推荐
相关产品推荐

