File: Microsoft\VisualBasic\Helpers\ForEachEnum.vb
Web Access
Project: src\src\libraries\Microsoft.VisualBasic.Core\src\Microsoft.VisualBasic.Core.vbproj (Microsoft.VisualBasic.Core)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
 
Imports System
Imports System.Collections
Imports System.Diagnostics
 
Namespace Microsoft.VisualBasic
 
    'This is in the helpers directory but not in the compilerservices namespace
 
    'The ForEachEnum class is publicly exposed through Collection.GetEnumerator().
    <System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)>
    Friend NotInheritable Class ForEachEnum
        Implements IEnumerator
        Implements IDisposable
 
        'No Finalize - this is intentional because we do not want to invoke RemoveIterator in finalize
        '              because of threading and synchronization issues involved which would then cause
        '              perf degrade.
 
        Private mDisposed As Boolean
 
        'The collection this enumerator is enumerating over
        Private mCollectionObject As Microsoft.VisualBasic.Collection
 
        'The current element being iterated.  Note: this element may no longer be in the list,
        '  (if it was deleted), so do *not* assume that its previous and next pointers are valid.
        Private mCurrent As Collection.Node
 
        'The next item to enumerate.  This is always updated on MoveNext and is used to determine where
        '  the enumeration goes next (not mCurrent).
        Private mNext As Collection.Node
 
        'A flag indicating that we are ready to start enumerating but have not done so yet (allows us
        '  to delay fixing mNext until the next MoveNext).
        Private mAtBeginning As Boolean
 
        Friend WeakRef As WeakReference
 
        Private Sub Dispose() Implements IDisposable.Dispose
            If Not mDisposed Then
                mCollectionObject.RemoveIterator(WeakRef)
                mDisposed = True
            End If
            mCurrent = Nothing
            mNext = Nothing
        End Sub
 
        Public Sub New(ByVal coll As Microsoft.VisualBasic.Collection)
            MyBase.New()
            mCollectionObject = coll
 
            Reset()
        End Sub
 
        Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
            If mDisposed Then
                Return False
            End If
 
            If mAtBeginning Then
                'We haven't started iterating yet.  Start at the beginning.
 
                mAtBeginning = False
                mNext = mCollectionObject.GetFirstListNode()
            End If
 
            Debug.Assert(Not mAtBeginning)
            If mNext Is Nothing Then
                Dispose()
                Return False
            End If
 
            mCurrent = mNext
            If mCurrent IsNot Nothing Then
                mNext = mCurrent.m_Next
                Return True
            Else
                Debug.Assert(mNext Is Nothing)
                Dispose()
                Return False
            End If
        End Function
 
        Public Sub Reset() Implements IEnumerator.Reset
            If mDisposed Then
                mCollectionObject.AddIterator(WeakRef)
                mDisposed = False
            End If
 
            mCurrent = Nothing
            mNext = Nothing
            mAtBeginning = True
        End Sub
 
        Public ReadOnly Property Current() As Object Implements IEnumerator.Current
            Get
                If mCurrent Is Nothing Then
                    Return Nothing
                Else
                    Return mCurrent.m_Value
                End If
            End Get
        End Property
 
        Friend Enum AdjustIndexType
            Insert
            Remove
        End Enum
 
        'Adjusts the enumerator to account for newly-inserted or removed items in/from the collection.
        '  For insertion, this call must be made *after* the insertion has taken place.  For deletion, 
        '  this call must have been made before the next/prev pointers in the deleted node have been
        '  invalidated (they must still be pointing to the values before the deletion).
        Public Sub Adjust(ByVal Node As Collection.Node, ByVal Type As AdjustIndexType)
 
            If Node Is Nothing Then
                Debug.Fail("Node shouldn't be nothing")
                Exit Sub 'defensive
            End If
 
            If mDisposed Then
                'Nothing to do
                Exit Sub
            End If
 
            Select Case Type
                Case AdjustIndexType.Insert
                    Debug.Assert(Node IsNot mCurrent, "If we just inserted Node, then it couldn't be the current node because it's not in the list yet")
 
                    'mCurrent may not necessarily be still in the list, so we have to be wary of using mCurrent.m_Next.  However, if
                    '  there is a current node, and its next is pointing to Node, then mCurrent must still be in the list (since Node wasn't
                    '  in the list before).  So in this case we'll go ahead and set our mNext to the newly-inserted node.
                    'It would seem to make sense also to set mNext to the new node if mNext is Nothing (i.e., we're at the end of the list), but
                    '  this would be a difference in RTM/Everett behavior that doesn't seem warranted.
                    If mCurrent IsNot Nothing AndAlso Node Is mCurrent.m_Next Then
                        'The new node was inserted right after our current node.
                        '  Iterate through it next.
                        mNext = Node
                    End If
 
                    'Note that the case of inserting at the beginning before we've
                    '  iterated through any nodes will be handled in MoveNext with the
                    '  mAtBeginning flag.
 
                Case AdjustIndexType.Remove
                    If Node Is mCurrent Then
                        'Current node was removed.  No need to do anything, because we want GetCurrent() to continue to
                        '  return this same node's data until the next MoveNext().
                    ElseIf Node Is mNext Then
                        'The next node was removed.  Make our next node to iterate through
                        '  be the one after that instead
                        mNext = mNext.m_Next
                    End If
                Case Else
                    Debug.Fail("Unexpected adjustment type in enumerator")
            End Select
        End Sub
 
        'Should be called if the list this is enumerating is cleared.  It will set the
        '  enumerator past the end of the list (nothing more to enumerate).
        Friend Sub AdjustOnListCleared()
            mNext = Nothing
        End Sub
 
    End Class
 
End Namespace