为什么我的LINQ表达式中的输出变量没有问题?

moa*_*ate 3 .net c# linq vb.net

给出以下代码:

var strings = Enumerable.Range(0, 100).Select(i => i.ToString());
int outValue = 0;
var someEnumerable = strings.Where(s => int.TryParse(s, out outValue))
                            .Select(s => outValue);
outValue = 3;
//enumerating over someEnumerable here shows ints from 0 to 99
Run Code Online (Sandbox Code Playgroud)

我能够看到每次迭代的out参数的"快照".为什么这个工作正常而不是我看到100 3(延迟执行)或100 99(访问修改后的闭包)?

Ser*_*rvy 5

首先定义一个查询,strings该查询在查询时知道如何生成字符串序列.每次询问一个值时,它都会生成一个新数字并将其转换为字符串.

然后声明一个变量outValue,并赋值0给它.

然后定义一个新的查询,someEnumerable它知道如何在询问值时从查询中获取下一个值strings,尝试解析该值,如果可以解析该值,则生成值outValue.我们再一次定义了一个可以执行此操作的查询,我们实际上没有完成任何此操作.

然后你设置outValue为3.

然后你要求someEnumerable它的第一个值,你要求实现Select它的价值.为了计算这个值就会问Where了它的第一个值.该Where会问strings.(我们现在跳过几步.)这Where将得到一个0.它将调用谓词0,特别是调用int.TryParse.这样做的副作用outValue是设置为0. TryParse返回true,因此该项目已产生. Select然后0使用其选择器将该值(字符串)映射到新值.选择器忽略该值并产生该outValue时间点的值,即0.我们的foreach循环现在做任何事情0.

现在我们someEnumerable在循环的下一次迭代中询问它的第二个值.它要求Select为一个值,Select询问Where,其中询问strings,strings产量"1",Where调用谓词,设置outValue到1作为副作用,Select产生的电流值outValue,这是1.该foreach循环现在不管用什么方法1.

因此,关键在于,由于执行方式Where和Select延迟执行,仅在需要值时立即执行其工作,Where谓词的副作用最终会在每次投影之前立即调用Select.如果您没有推迟执行,而是TryParse在任何投影之前执行所有调用Select,那么您将看到100每个值.我们实际上可以很容易地模拟这个.我们可以兑现的结果Where到一个集合,然后看到的结果Select被100一遍又一遍地重复着:

var someEnumerable = strings.Where(s => int.TryParse(s, out outValue))
    .ToList()//eagerly evaluate the query up to this point
    .Select(s => outValue);
Run Code Online (Sandbox Code Playgroud)

说完这一切之后,你所拥有的查询并不是特别好的设计.只要有可能,您应该避免有副作用的查询(例如您的Where).事实上,查询既会产生副作用,又能观察到它产生的副作用,这使得所有这些都变得相当困难.优选的设计是依靠不会引起副作用的纯功能方法.在此上下文中,最简单的方法是创建一个尝试解析字符串并返回以下内容的方法int?:

public static int? TryParse(string rawValue)
{
    int output;
    if (int.TryParse(rawValue, out output))
        return output;
    else
        return null;
}
Run Code Online (Sandbox Code Playgroud)

这允许我们写:

var someEnumerable = from s in strings
    let n = TryParse(s)
    where n != null
    select n.Value;
Run Code Online (Sandbox Code Playgroud)

这里查询中没有可观察到的副作用,查询也没有观察到任何外部副作用.它使整个查询更容易推理.

  • @moarboilerplate是*可能*,是的.它会是好设计,不是.如果你想同时进行投影和过滤,那么你应该使用过滤运算符和投影运算符,而不是尝试同时执行这两个操作.这样做会重新回到可行的代码领域,但设计很差. (2认同)