Skip to content

Commit d660e01

Browse files
Copilotstephentoub
andcommitted
Fix missing Dispose override in SizeOptIListSelectIterator
Add Dispose() method to properly dispose the enumerator when UseSizeOptimizedLinq is enabled. This fixes the bug where custom enumerator's Dispose was not called when the source implements IList<T>. Fixes #115239 Co-authored-by: stephentoub <[email protected]>
1 parent 8b5aac8 commit d660e01

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

src/libraries/System.Linq/src/System/Linq/Select.SizeOpt.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ public override bool MoveNext()
7373
return false;
7474
}
7575

76+
public override void Dispose()
77+
{
78+
if (_enumerator is not null)
79+
{
80+
_enumerator.Dispose();
81+
_enumerator = null;
82+
}
83+
84+
base.Dispose();
85+
}
86+
7687
public override TResult[] ToArray()
7788
{
7889
TResult[] array = new TResult[_source.Count];

src/libraries/System.Linq/tests/SelectTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,5 +1261,89 @@ public static IEnumerable<object[]> RunSelectorDuringCountData()
12611261
}
12621262
}
12631263
}
1264+
1265+
[Fact]
1266+
public void Select_SourceIsIList_EnumeratorDisposedOnComplete()
1267+
{
1268+
var source = new DisposeTrackingList<int>([1, 2, 3, 4, 5]);
1269+
1270+
foreach (int item in source.Select(i => i * 2))
1271+
{
1272+
}
1273+
1274+
Assert.Equal(1, source.DisposeCalls);
1275+
}
1276+
1277+
[Fact]
1278+
public void Select_SourceIsIList_EnumeratorDisposedOnExplicitDispose()
1279+
{
1280+
var source = new DisposeTrackingList<int>([1, 2, 3, 4, 5]);
1281+
1282+
using (var enumerator = source.Select(i => i * 2).GetEnumerator())
1283+
{
1284+
enumerator.MoveNext();
1285+
}
1286+
1287+
Assert.Equal(1, source.DisposeCalls);
1288+
}
1289+
1290+
private sealed class DisposeTrackingList<T> : IList<T>, IReadOnlyList<T>
1291+
{
1292+
private readonly List<T> _list;
1293+
private int _disposeCalls;
1294+
1295+
public DisposeTrackingList(T[] items)
1296+
{
1297+
_list = [.. items];
1298+
}
1299+
1300+
public int DisposeCalls => _disposeCalls;
1301+
1302+
public T this[int index]
1303+
{
1304+
get => _list[index];
1305+
set => _list[index] = value;
1306+
}
1307+
1308+
public int Count => _list.Count;
1309+
1310+
public bool IsReadOnly => false;
1311+
1312+
public void Add(T item) => _list.Add(item);
1313+
public void Clear() => _list.Clear();
1314+
public bool Contains(T item) => _list.Contains(item);
1315+
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
1316+
public int IndexOf(T item) => _list.IndexOf(item);
1317+
public void Insert(int index, T item) => _list.Insert(index, item);
1318+
public bool Remove(T item) => _list.Remove(item);
1319+
public void RemoveAt(int index) => _list.RemoveAt(index);
1320+
1321+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
1322+
1323+
public IEnumerator<T> GetEnumerator() => new DisposeTrackingEnumerator(this);
1324+
1325+
private sealed class DisposeTrackingEnumerator : IEnumerator<T>
1326+
{
1327+
private readonly DisposeTrackingList<T> _parent;
1328+
private readonly IEnumerator<T> _enumerator;
1329+
1330+
public DisposeTrackingEnumerator(DisposeTrackingList<T> parent)
1331+
{
1332+
_parent = parent;
1333+
_enumerator = parent._list.GetEnumerator();
1334+
}
1335+
1336+
public T Current => _enumerator.Current;
1337+
object? IEnumerator.Current => Current;
1338+
public bool MoveNext() => _enumerator.MoveNext();
1339+
public void Reset() => throw new NotSupportedException();
1340+
1341+
public void Dispose()
1342+
{
1343+
_parent._disposeCalls++;
1344+
_enumerator.Dispose();
1345+
}
1346+
}
1347+
}
12641348
}
12651349
}

0 commit comments

Comments
 (0)