C# Linq中Select延迟加载行为差异解析求助
C# LINQ中Select延迟加载的行为差异问题
我是C#新手,碰到了LINQ中Select延迟加载的行为差异问题:
- FlipNumber方法使用带索引的
Select((_, i)...)结合Last(),能正确执行n.Length-1次数字字符串翻转; - FlipNumber1方法使用普通
Select(i=>...)结合Last()仅执行一次翻转; - FlipNumber11在Select后添加
ToList()再调用Last(),结果与FlipNumber一致; - FlipNumber2的执行结果也不符合预期。
以下是测试代码(暂不考虑效率):
using System; using System.Linq; public class Test { public static string FlipNumber(string n) { Console.WriteLine($"FlipNumber"); return Enumerable.Range(0, n.Length - 1).Select((_, i) => { n = new string(n.Take(i).Concat(n.Skip(i).Reverse()).ToArray()); Console.WriteLine($"{i} : {n}"); return n; }).Last(); } public static string FlipNumber1(string n) { Console.WriteLine($"FlipNumber1"); return Enumerable.Range(0, n.Length - 1).Select(i => { n = new string(n.Take(i).Concat(n.Skip(i).Reverse()).ToArray()); Console.WriteLine($"{i} : {n}"); return n; }).Last(); } // suggested by @DiplomacyNotWar // add `ToList()` before `Last()` makes the result same with FlipNumber public static string FlipNumber11(string n) { Console.WriteLine($"FlipNumber11"); return Enumerable.Range(0, n.Length - 1).Select(i => { n = new string(n.Take(i).Concat(n.Skip(i).Reverse()).ToArray()); Console.WriteLine($"{i} : {n}"); return n; }).ToList().Last(); } public static string FlipNumber2(string n) { Console.WriteLine($"FlipNumber2"); return n.Select(i => { n = new string(n.Take(i).Concat(n.Skip(i).Reverse()).ToArray()); Console.WriteLine($"{i} : {n}"); return n; }).Last(); } public static void Main(string[] args) { FlipNumber("123456789"); FlipNumber1("123456789"); FlipNumber11("123456789"); FlipNumber2("123456789"); } }
运行结果:
FlipNumber 0 : 987654321 1 : 912345678 2 : 918765432 3 : 918234567 4 : 918276543 5 : 918273456 6 : 918273654 7 : 918273645 FlipNumber1 7 : 123456798 FlipNumber11 0 : 987654321 1 : 912345678 2 : 918765432 3 : 918234567 4 : 918276543 5 : 918273456 6 : 918273654 7 : 918273645 FlipNumber2 1 : 123456789 2 : 123456789 3 : 123456789 4 : 123456789 5 : 123456789 6 : 123456789 7 : 123456789 8 : 123456789 9 : 123456789
原因解析
核心在于LINQ的延迟执行特性,以及不同Select重载、Last()方法的执行逻辑差异:
FlipNumber与FlipNumber1的差异
Enumerable.Range返回的序列实现了IList<int>,属于可索引序列。对于普通Select(i => ...),Last()方法会直接利用源序列的可索引性,直接获取最后一个元素(索引为7),仅触发一次Select委托执行,所以只完成一次翻转。- 带索引的
Select((_, i) => ...)重载,底层不会利用源序列的可索引性来跳转到最后一个元素,而是会从头遍历整个序列,逐个执行委托直到最后一个元素,因此所有0到7的翻转逻辑都会被执行。
FlipNumber11的修正逻辑
ToList()方法会强制LINQ立即遍历整个序列,执行所有Select委托中的翻转操作,并将结果存入List集合。之后调用Last()只是取List的最后一个元素,因此结果和FlipNumber完全一致。FlipNumber2的问题所在
n.Select(i => ...)中的i是字符串的每个字符(char类型),而非索引。当执行n.Take(i)时,char会被隐式转换为int(比如字符'1'对应ASCII码49),远大于原字符串长度9,因此Take(49)等价于取整个字符串,Skip(49)为空序列,反转后仍是原字符串,所以每次翻转都无变化;同时原字符串有9个字符,因此会输出9次相同结果。
内容的提问来源于stack exchange,提问作者Harold
相关产品推荐
相关产品推荐

