|
31 | 31 | using System; |
32 | 32 | using System.Collections; |
33 | 33 | using System.Collections.Generic; |
| 34 | +using System.Diagnostics; |
| 35 | +using System.Threading; |
34 | 36 |
|
35 | 37 | namespace System.Runtime.CompilerServices |
36 | 38 | { |
@@ -375,11 +377,109 @@ internal ICollection<TValue> Values |
375 | 377 | } |
376 | 378 | } |
377 | 379 |
|
| 380 | + // IEnumerable implementation was copied from CoreCLR |
378 | 381 | IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator () |
379 | 382 | { |
380 | | - throw new NotImplementedException (); |
| 383 | + lock (_lock) |
| 384 | + { |
| 385 | + return size == 0 ? |
| 386 | + ((IEnumerable<KeyValuePair<TKey, TValue>>)Array.Empty<KeyValuePair<TKey, TValue>>()).GetEnumerator() : |
| 387 | + new Enumerator(this); |
| 388 | + } |
381 | 389 | } |
382 | 390 |
|
383 | 391 | IEnumerator IEnumerable.GetEnumerator () => ((IEnumerable<KeyValuePair<TKey, TValue>>)this).GetEnumerator (); |
| 392 | + |
| 393 | + /// <summary>Provides an enumerator for the table.</summary> |
| 394 | + private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> |
| 395 | + { |
| 396 | + // The enumerator would ideally hold a reference to the Container and the end index within that |
| 397 | + // container. However, the safety of the CWT depends on the only reference to the Container being |
| 398 | + // from the CWT itself; the Container then employs a two-phase finalization scheme, where the first |
| 399 | + // phase nulls out that parent CWT's reference, guaranteeing that the second time it's finalized there |
| 400 | + // can be no other existing references to it in use that would allow for concurrent usage of the |
| 401 | + // native handles with finalization. We would break that if we allowed this Enumerator to hold a |
| 402 | + // reference to the Container. Instead, the Enumerator holds a reference to the CWT rather than to |
| 403 | + // the Container, and it maintains the CWT._activeEnumeratorRefCount field to track whether there |
| 404 | + // are outstanding enumerators that have yet to be disposed/finalized. If there aren't any, the CWT |
| 405 | + // behaves as it normally does. If there are, certain operations are affected, in particular resizes. |
| 406 | + // Normally when the CWT is resized, it enumerates the contents of the table looking for indices that |
| 407 | + // contain entries which have been collected or removed, and it frees those up, effectively moving |
| 408 | + // down all subsequent entries in the container (not in the existing container, but in a replacement). |
| 409 | + // This, however, would cause the enumerator's understanding of indices to break. So, as long as |
| 410 | + // there is any outstanding enumerator, no compaction is performed. |
| 411 | + |
| 412 | + private ConditionalWeakTable<TKey, TValue> _table; // parent table, set to null when disposed |
| 413 | + private int _currentIndex = -1; // the current index into the container |
| 414 | + private KeyValuePair<TKey, TValue> _current; // the current entry set by MoveNext and returned from Current |
| 415 | + |
| 416 | + public Enumerator(ConditionalWeakTable<TKey, TValue> table) |
| 417 | + { |
| 418 | + // Store a reference to the parent table and increase its active enumerator count. |
| 419 | + _table = table; |
| 420 | + _currentIndex = -1; |
| 421 | + } |
| 422 | + |
| 423 | + ~Enumerator() { Dispose(); } |
| 424 | + |
| 425 | + public void Dispose() |
| 426 | + { |
| 427 | + // Use an interlocked operation to ensure that only one thread can get access to |
| 428 | + // the _table for disposal and thus only decrement the ref count once. |
| 429 | + ConditionalWeakTable<TKey, TValue> table = Interlocked.Exchange(ref _table, null); |
| 430 | + if (table != null) |
| 431 | + { |
| 432 | + // Ensure we don't keep the last current alive unnecessarily |
| 433 | + _current = default; |
| 434 | + |
| 435 | + // Finalization is purely to decrement the ref count. We can suppress it now. |
| 436 | + GC.SuppressFinalize(this); |
| 437 | + } |
| 438 | + } |
| 439 | + |
| 440 | + public bool MoveNext() |
| 441 | + { |
| 442 | + // Start by getting the current table. If it's already been disposed, it will be null. |
| 443 | + ConditionalWeakTable<TKey, TValue> table = _table; |
| 444 | + if (table != null) |
| 445 | + { |
| 446 | + // Once have the table, we need to lock to synchronize with other operations on |
| 447 | + // the table, like adding. |
| 448 | + lock (table._lock) |
| 449 | + { |
| 450 | + var tombstone = GC.EPHEMERON_TOMBSTONE; |
| 451 | + while (_currentIndex < table.data.Length - 1) |
| 452 | + { |
| 453 | + _currentIndex++; |
| 454 | + var currentDataItem = table.data[_currentIndex]; |
| 455 | + if (currentDataItem.key != null && currentDataItem.key != tombstone) |
| 456 | + { |
| 457 | + _current = new KeyValuePair<TKey, TValue>((TKey)currentDataItem.key, (TValue)currentDataItem.value); |
| 458 | + return true; |
| 459 | + } |
| 460 | + } |
| 461 | + } |
| 462 | + } |
| 463 | + |
| 464 | + // Nothing more to enumerate. |
| 465 | + return false; |
| 466 | + } |
| 467 | + |
| 468 | + public KeyValuePair<TKey, TValue> Current |
| 469 | + { |
| 470 | + get |
| 471 | + { |
| 472 | + if (_currentIndex < 0) |
| 473 | + { |
| 474 | + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); |
| 475 | + } |
| 476 | + return _current; |
| 477 | + } |
| 478 | + } |
| 479 | + |
| 480 | + object IEnumerator.Current => Current; |
| 481 | + |
| 482 | + public void Reset() { } |
| 483 | + } |
384 | 484 | } |
385 | 485 | } |
0 commit comments