If you have an List, and you use .Where() to filter that list into itself using AddRange, it will loop until the system runs out of memory.
.Net 4.6.1 Behavior "Collection was modified enumeration operation may not execute"
.Net Core 2.1 Behavior OutOfMemoryException
The List
Quick sample repo for both 4.6.1 and .Net Core 2.1.
c#
class Program
{
static void Main(string[] args)
{
var testList = new List<TestObject>();
testList.Add(new TestObject() { Property1 = "test" });
testList.AddRange(testList.Where(x => x.Property1 == "test"));
}
}
public class TestObject
{
public string Property1 { get; set; }
}
Does it reproduce if you take Linq out of the picture? Or does the repro depend on Linq (.Where) being used?
Removing the .Where does not cause any exception. Also adding a .ToList in the statement also does not cause any exceptions to generate. It just seems to only occur if it is an IEnumerable.
What happens if you add items that _don't_ match the Where clause - before or after the matching one? What about multiple matching items?
Same result, The original code that ran into this issue had ~100 records in it but only 1 matched the Where clause, and this was a quick way to additional data without copy/pasting an object initializer.
Simpler repro: (tried on .NET 4.7.1 vs. .NET Core 2.1)
```c#
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var testList = new List
testList.AddRange(testList.Where(_ => true));
}
}
```
I have to admit I feel this shouldn't be fixed but who am I to know.
In any case I think josbartley's code should be doing .First(predicate) rather than .Where(predicate) to obviously get the behavior he wants.
Mutating the source collection while enumerating really should be causing 'Collection was modified enumeration operation may not execute' on all platforms. That's the appropriate fix, I'm pretty sure.
I expect this was broken by https://github.com/dotnet/coreclr/pull/8306. Previously the AddRange implementation would enumerate, and for each item, would call Add, which would increment the version number, and then the next access to the enumerator would throw. With that change, as noted in https://github.com/dotnet/coreclr/pull/8306#discussion_r89682000, the version increment is removed for each item, and thus the enumerator doesn't know it should be invalidated.
There are no perf numbers included in that PR. My initial reaction is that PR should just be reverted.
cc: @jkotas, @safern, @ianhays, @jamesqo
There are no perf numbers included in that PR. My initial reaction is that PR should just be reverted.
From looking at the discussions in the PR and the changes it looks like there wasn't a good/enough investigation on what was changing from that PR. So I agree that I have the same feeling that we should just revert that PR and then do a deeper investigation on why Add wasn't being inlined in the first place.
Also it seems to me like we should've caught this in our tests, so it seems like a test hole here to me.
Most helpful comment
Mutating the source collection while enumerating really should be causing 'Collection was modified enumeration operation may not execute' on all platforms. That's the appropriate fix, I'm pretty sure.