问题 是什么原因导致这个列表在被单向调用时通过引用传递,但是按值传递另一个?


我正在为运行验证方法做一个简单的测试,并遇到了这种奇怪的情况。

public IEnumerable<int> ints (List<int> l)
{
 if(false)yield return 6;
 l.Add(4);
}


void Main()
{
 var a = new List<int>();
 var b = new List<int>();
 for( int i = 0; i < 4; i++ ){
  a.Add(i);
  b.Add(i);
 }
 a.AddRange(ints(a));
 ints(b);
 Console.WriteLine(a);
 Console.WriteLine(b);
}

一旦此代码运行, a 将包含 [0,1,2,3,4]。然而, b 将包含 [0,1,2,3]。为什么将方法作为参数调用 AddRange 允许列表通过引用传递?或者,如果没有发生,那做了什么?


1387
2018-06-26 21:08


起源

好像是这样的 AddRange 称自己导致额外的进入 a,而不是电话 ints()。虽然我不能完全遵循这个代码 - 例如,是否有一个缺少的返回语句(或关于死代码的警告) ints() 方法? - Rob I
顺便说一句,我认为更清楚的方式来写你的 ints() 将会: l.Add(4); yield break;。 - svick
@svick - 我还没用过 yield 所以我只是写了一部分内容,看看回归行为是如何起作用的,并遇到了这种情况。我不会在生产中使用这样的代码,它只是强调了问题:)。为了满足好奇心,这就是真正的代码: public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {DateTime date = this.FirstDate;foreach (var step in Sequence(this.FinalStep, this.TopSteps, this.BottomSteps)){if (date < step.DateChosen){yield return new ValidationResult("Dates Not In Chronological Order");}date = step.DateChosen;}} - Travis J


答案:


ints(b) 电话不会枚举 IEnumerable,所以代码永远不会到达 l.Add(4) 线,不像在 AddRange 枚举所有项目以将其添加到列表的情况。

看到它需要 b case手动枚举结果:

ints(b).ToList();

IEnumerable<T> 通过函数实现的函数不是在枚举开始之前执行函数体 - 代码实际上由编译器转换为具有状态的类,以支持可枚举的真正延迟评估(细节可以在多篇文章中找到,即 迭代器模式揭秘  - Tim Schmelter提供的链接)。


15
2018-06-26 21:13



@Tigran ints 返回一个具有良好定义行为的IEnumerable,如果你 不要 枚举可枚举的预期行为是 永远不会执行枚举。延迟执行的行为是LINQ建立在Linq2Sql之类的基础上的全部基础。 - Scott Chamberlain
@Tigran除了没有“取决于如何 ints() 被称为“。该方法应该做什么,做什么是构建并返回一个合成类实现 IEnumerable<int> 那, 枚举时,产生您期望从方法体中获得的值。编译器没有理由保证任何行为都类似于常规方法,因为它不是一个。 - millimoose
这不是一个错误,它完全是设计的。如果您不了解它可能会出乎意料,但对于一个bug来说,对于那些设计和编写此编译器的人来说,它不是出乎意料的。 - Lasse Vågsæther Karlsen
@Tigran无法访问的事实不会改变结果,将代码更改为 { bool temp = true; if(temp){ temp = false; yield return 6;} l.Add(4); } 你仍然会得到同样的行为。 - Scott Chamberlain
@Tigran 这是一个简单的程序 这表明代码仅在枚举枚举器时运行。它有 没有 处理无法访问的代码。 - Scott Chamberlain