Skip to content

Commit 202a63e

Browse files
authored
Add Empty property to MemoizingEnumerable (#9576)
Adds a boolean Empty property that reports whether the enumerable yields any items, forcing enumeration of only the first item so memoization and lazy evaluation are preserved. Includes unit tests covering empty/non-empty sources, lazy single enumeration, disabled memoization, and enumerable integrity after accessing Empty.
1 parent feec818 commit 202a63e

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

Common/Util/MemoizingEnumerable.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@ public int Count
5555
}
5656
}
5757

58+
/// <summary>
59+
/// Gets whether the enumerable is empty. This will force enumeration of the first item if it has not already been enumerated.
60+
/// </summary>
61+
public bool Empty
62+
{
63+
get
64+
{
65+
if (!Enabled)
66+
{
67+
throw new InvalidOperationException("Empty is not supported when memoization is disabled");
68+
}
69+
if (_buffer == null || _buffer.Count == 0)
70+
{
71+
using var enumerator = GetEnumerator();
72+
return !enumerator.MoveNext();
73+
}
74+
return _buffer.Count == 0;
75+
}
76+
}
77+
5878
/// <summary>
5979
/// Initializes a new instance of the <see cref="MemoizingEnumerable{T}"/> class
6080
/// </summary>

Tests/Common/Util/MemoizingEnumerableTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,56 @@ public void GetsCount()
6060
Assert.AreEqual(5, memoized.Count);
6161
Assert.AreEqual(memoized.Count, memoized.ToList().Count);
6262
}
63+
64+
[Test]
65+
public void EmptyIsFalseForNonEmptyEnumerable()
66+
{
67+
var list = new List<int> {1, 2, 3, 4, 5};
68+
var memoized = new MemoizingEnumerable<int>(list);
69+
Assert.IsFalse(memoized.Empty);
70+
// still enumerates fully after checking Empty
71+
CollectionAssert.AreEqual(list, memoized);
72+
}
73+
74+
[Test]
75+
public void EmptyIsTrueForEmptyEnumerable()
76+
{
77+
var memoized = new MemoizingEnumerable<int>(Enumerable.Empty<int>());
78+
Assert.IsTrue(memoized.Empty);
79+
}
80+
81+
[Test]
82+
public void EmptyDoesNotForceFullEnumeration()
83+
{
84+
var enumerated = 0;
85+
var enumerable = Enumerable.Range(0, 10).Select(x => { enumerated++; return x; });
86+
var memoized = new MemoizingEnumerable<int>(enumerable);
87+
Assert.IsFalse(memoized.Empty);
88+
// only the first item should have been enumerated
89+
Assert.AreEqual(1, enumerated);
90+
}
91+
92+
[Test]
93+
public void EnumerationAfterEmptyKeepsIntegrity()
94+
{
95+
var i = 0;
96+
// lazy source where each element is produced exactly once
97+
var enumerable = Enumerable.Range(0, 5).Select(x => i++);
98+
var memoized = new MemoizingEnumerable<int>(enumerable);
99+
100+
// accessing Empty consumes the first item from the source
101+
Assert.IsFalse(memoized.Empty);
102+
103+
// enumerating should still yield the full sequence without duplicating the first item
104+
CollectionAssert.AreEqual(new[] { 0, 1, 2, 3, 4 }, memoized.ToList());
105+
}
106+
107+
[Test]
108+
public void EmptyThrowsWhenDisabled()
109+
{
110+
var list = new List<int> {1, 2, 3, 4, 5};
111+
var memoized = new MemoizingEnumerable<int>(list) { Enabled = false };
112+
Assert.Throws<InvalidOperationException>(() => { var _ = memoized.Empty; });
113+
}
63114
}
64115
}

0 commit comments

Comments
 (0)