55using System ;
66using System . Collections . Generic ;
77using System . Linq ;
8+ using System . Threading ;
89using System . Threading . Tasks ;
910using Xunit ;
1011
@@ -162,6 +163,128 @@ public async Task Union_ToList()
162163 Assert . Equal ( new [ ] { 1 , 2 , 3 , 4 , 5 } , ( await res . ToListAsync ( ) ) . OrderBy ( x => x ) ) ;
163164 }
164165
166+
167+ [ Fact ]
168+ public async Task Union_DisposesNotEmpty ( )
169+ {
170+ var e1 = new DisposalDetectingEnumerable ( 10 , 2 ) ;
171+ var e2 = new DisposalDetectingEnumerable ( 20 , 2 ) ;
172+ var res = e1 . Union ( e2 ) . OrderBy ( x => x ) ;
173+
174+ var e = res . GetAsyncEnumerator ( ) ;
175+ await HasNextAsync ( e , 10 ) ;
176+ await HasNextAsync ( e , 11 ) ;
177+ await HasNextAsync ( e , 20 ) ;
178+ await HasNextAsync ( e , 21 ) ;
179+ await NoNextAsync ( e ) ;
180+
181+ Assert . Single ( e1 . Enumerators ) ;
182+ Assert . Single ( e2 . Enumerators ) ;
183+ Assert . Equal ( [ true , true ] , [ e1 . Enumerators [ 0 ] . Disposed , e2 . Enumerators [ 0 ] . Disposed ] ) ;
184+ }
185+
186+ [ Fact ]
187+ public async Task Union_DisposesFirstEmpty ( )
188+ {
189+ var e1 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
190+ var e2 = new DisposalDetectingEnumerable ( 1 , 1 ) ;
191+ var res = e1 . Union ( e2 ) ;
192+
193+ var e = res . GetAsyncEnumerator ( ) ;
194+ await HasNextAsync ( e , 1 ) ;
195+ await NoNextAsync ( e ) ;
196+
197+ Assert . Single ( e1 . Enumerators ) ;
198+ Assert . Single ( e2 . Enumerators ) ;
199+ Assert . Equal ( [ true , true ] , [ e1 . Enumerators [ 0 ] . Disposed , e2 . Enumerators [ 0 ] . Disposed ] ) ;
200+ }
201+
202+ [ Fact ]
203+ public async Task Union_DisposesSecondOfTwoEmpty ( )
204+ {
205+ var e1 = new DisposalDetectingEnumerable ( 1 , 1 ) ;
206+ var e2 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
207+ var res = e1 . Union ( e2 ) ;
208+
209+ var e = res . GetAsyncEnumerator ( ) ;
210+ await HasNextAsync ( e , 1 ) ;
211+ await NoNextAsync ( e ) ;
212+
213+ Assert . Single ( e1 . Enumerators ) ;
214+ Assert . Single ( e2 . Enumerators ) ;
215+ Assert . Equal ( [ true , true ] , [ e1 . Enumerators [ 0 ] . Disposed , e2 . Enumerators [ 0 ] . Disposed ] ) ;
216+ }
217+
218+ [ Fact ]
219+ public async Task Union_DisposesSecondOfThreeEmpty ( )
220+ {
221+ var e1 = new DisposalDetectingEnumerable ( 10 , 1 ) ;
222+ var e2 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
223+ var e3 = new DisposalDetectingEnumerable ( 30 , 1 ) ;
224+ var res = e1 . Union ( e2 ) . Union ( e3 ) ;
225+
226+ var e = res . GetAsyncEnumerator ( ) ;
227+ await HasNextAsync ( e , 10 ) ;
228+ await HasNextAsync ( e , 30 ) ;
229+ await NoNextAsync ( e ) ;
230+
231+ Assert . Single ( e1 . Enumerators ) ;
232+ Assert . Single ( e2 . Enumerators ) ;
233+ Assert . Single ( e3 . Enumerators ) ;
234+ Assert . Equal ( [ true , true , true ] , [ e1 . Enumerators [ 0 ] . Disposed , e2 . Enumerators [ 0 ] . Disposed , e3 . Enumerators [ 0 ] . Disposed ] ) ;
235+ }
236+
237+ [ Fact ]
238+ public async Task Union_DisposesThirdOfThreeEmpty ( )
239+ {
240+ var e1 = new DisposalDetectingEnumerable ( 10 , 1 ) ;
241+ var e2 = new DisposalDetectingEnumerable ( 20 , 1 ) ;
242+ var e3 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
243+ var res = e1 . Union ( e2 ) . Union ( e3 ) ;
244+
245+ var e = res . GetAsyncEnumerator ( ) ;
246+ await HasNextAsync ( e , 10 ) ;
247+ await HasNextAsync ( e , 20 ) ;
248+ await NoNextAsync ( e ) ;
249+
250+ Assert . Single ( e1 . Enumerators ) ;
251+ Assert . Single ( e2 . Enumerators ) ;
252+ Assert . Single ( e3 . Enumerators ) ;
253+ Assert . Equal ( [ true , true , true ] , [ e1 . Enumerators [ 0 ] . Disposed , e2 . Enumerators [ 0 ] . Disposed , e3 . Enumerators [ 0 ] . Disposed ] ) ;
254+ }
255+
256+ [ Fact ]
257+ public async Task Union_DisposesAllOfTwoEmpty ( )
258+ {
259+ var e1 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
260+ var e2 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
261+ var res = e1 . Union ( e2 ) ;
262+
263+ var e = res . GetAsyncEnumerator ( ) ;
264+ await NoNextAsync ( e ) ;
265+
266+ Assert . Single ( e1 . Enumerators ) ;
267+ Assert . Single ( e2 . Enumerators ) ;
268+ Assert . Equal ( [ true , true ] , [ e1 . Enumerators [ 0 ] . Disposed , e2 . Enumerators [ 0 ] . Disposed ] ) ;
269+ }
270+
271+ [ Fact ]
272+ public async Task Union_DisposesAllOfThreeEmpty ( )
273+ {
274+ var e1 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
275+ var e2 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
276+ var e3 = new DisposalDetectingEnumerable ( 0 , 0 ) ;
277+ var res = e1 . Union ( e2 ) . Union ( e3 ) ;
278+
279+ var e = res . GetAsyncEnumerator ( ) ;
280+ await NoNextAsync ( e ) ;
281+
282+ Assert . Single ( e1 . Enumerators ) ;
283+ Assert . Single ( e2 . Enumerators ) ;
284+ Assert . Single ( e3 . Enumerators ) ;
285+ Assert . Equal ( [ true , true , true ] , [ e1 . Enumerators [ 0 ] . Disposed , e2 . Enumerators [ 0 ] . Disposed , e3 . Enumerators [ 0 ] . Disposed ] ) ;
286+ }
287+
165288 private sealed class Eq : IEqualityComparer < int >
166289 {
167290 public bool Equals ( int x , int y )
@@ -174,5 +297,61 @@ public int GetHashCode(int obj)
174297 return EqualityComparer < int > . Default . GetHashCode ( Math . Abs ( obj ) ) ;
175298 }
176299 }
300+
301+ private class DisposalDetectingEnumerable : IAsyncEnumerable < int >
302+ {
303+ private readonly int _start ;
304+ private readonly int _count ;
305+
306+ public DisposalDetectingEnumerable ( int start , int count )
307+ {
308+ _start = start ;
309+ _count = count ;
310+ }
311+
312+ public List < Enumerator > Enumerators { get ; } = new List < Enumerator > ( ) ;
313+
314+ public IAsyncEnumerator < int > GetAsyncEnumerator ( CancellationToken cancellationToken = default )
315+ {
316+ Enumerator r = new ( _start , _count ) ;
317+ Enumerators . Add ( r ) ;
318+ return r ;
319+ }
320+
321+ public class Enumerator : IAsyncEnumerator < int >
322+ {
323+ private readonly int _max ;
324+
325+ public Enumerator ( int start , int count )
326+ {
327+ Current = start - 1 ;
328+ _max = start + count ;
329+ }
330+
331+ public int Current { get ; private set ; }
332+
333+ public bool Disposed { get ; private set ; }
334+
335+ public void Dispose ( )
336+ {
337+ Disposed = true ;
338+ }
339+
340+ public ValueTask DisposeAsync ( )
341+ {
342+ Disposed = true ;
343+ return new ValueTask ( ) ;
344+ }
345+ public ValueTask < bool > MoveNextAsync ( )
346+ {
347+ if ( ++ Current < _max )
348+ {
349+ return new ValueTask < bool > ( true ) ;
350+ }
351+
352+ return new ValueTask < bool > ( false ) ;
353+ }
354+ }
355+ }
177356 }
178357}
0 commit comments