File: System\DirectoryServices\AccountManagement\TrackedCollectionEnumerator.cs
Web Access
Project: src\src\runtime\src\libraries\System.DirectoryServices.AccountManagement\src\System.DirectoryServices.AccountManagement.csproj (System.DirectoryServices.AccountManagement)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace System.DirectoryServices.AccountManagement
{
    internal sealed class TrackedCollectionEnumerator<T> : IEnumerator, IEnumerator<T>
    {
        //
        // Public properties
        //

        public T Current
        {
            get
            {
                CheckDisposed();

                // Since MoveNext() saved off the current value for us, this is largely trivial.

                if (_endReached || _enumerator == null)
                {
                    // Either we're at the end or before the beginning
                    GlobalDebug.WriteLineIf(GlobalDebug.Warn, "TrackedCollectionEnumerator", "Current: bad position, endReached={0}", _endReached);
                    throw new InvalidOperationException(SR.TrackedCollectionEnumInvalidPos);
                }

                return _current;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        //
        // Public methods
        //

        public bool MoveNext()
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "Entering MoveNext");

            CheckDisposed();
            CheckChanged();

            if (_endReached)
            {
                GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "MoveNext: endReached");
                return false;
            }

            if (_enumerator == null)
            {
                // Must be at the very beginning
                GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "MoveNext: at beginning");

                _enumerator = ((IEnumerable)_combinedValues).GetEnumerator();
                Debug.Assert(_enumerator != null);
            }

            bool gotNextValue = _enumerator.MoveNext();

            // If we got the next value,
            // save it off so that Current can later return it.
            if (gotNextValue)
            {
                // Have to handle differently, since inserted values are just a T, while
                // original value are a Pair<T,T>, where Pair.Right is the current value
                TrackedCollection<T>.ValueEl el = (TrackedCollection<T>.ValueEl)_enumerator.Current;

                if (el.isInserted)
                {
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "MoveNext: current ({0}) is inserted", _current);
                    _current = el.insertedValue;
                }
                else
                {
                    _current = el.originalValue.Right;
                    GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "MoveNext: current ({0}) is original", _current);
                }
            }
            else
            {
                // Nothing more to enumerate
                GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "MoveNext: nothing more to enumerate");

                _endReached = true;
            }

            return gotNextValue;
        }

        bool IEnumerator.MoveNext()
        {
            return MoveNext();
        }

        public void Reset()
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "Reset");

            CheckDisposed();
            CheckChanged();

            _endReached = false;
            _enumerator = null;
        }

        void IEnumerator.Reset()
        {
            Reset();
        }

        public void Dispose()
        {
            _disposed = true;
        }

        //
        // Internal constructors
        //
        internal TrackedCollectionEnumerator(string outerClassName, TrackedCollection<T> trackedCollection, List<TrackedCollection<T>.ValueEl> combinedValues)
        {
            GlobalDebug.WriteLineIf(GlobalDebug.Info, "TrackedCollectionEnumerator", "Ctor");

            _outerClassName = outerClassName;
            _trackedCollection = trackedCollection;
            _combinedValues = combinedValues;
        }

        //
        // Private implementation
        //

        // Have we been disposed?
        private bool _disposed;

        //  The name of our outer class. Used when throwing an ObjectDisposedException.
        private readonly string _outerClassName;

        private readonly List<TrackedCollection<T>.ValueEl> _combinedValues;

        // The value we're currently positioned at
        private T _current;

        // The enumerator for our inner list, combinedValues.
        private IEnumerator _enumerator;

        // True when we reach the end of combinedValues (no more values to enumerate in the TrackedCollection)
        private bool _endReached;

        // When this enumerator was constructed, to detect changes made to the TrackedCollection after it was constructed
        private readonly DateTime _creationTime = DateTime.UtcNow;
        private readonly TrackedCollection<T> _trackedCollection;

        private void CheckDisposed()
        {
            if (_disposed)
            {
                GlobalDebug.WriteLineIf(GlobalDebug.Warn, "TrackedCollectionEnumerator", "CheckDisposed: accessing disposed object");
                throw new ObjectDisposedException(_outerClassName);
            }
        }

        private void CheckChanged()
        {
            // Make sure the app hasn't changed our underlying list
            if (_trackedCollection.LastChange > _creationTime)
            {
                GlobalDebug.WriteLineIf(
                            GlobalDebug.Warn,
                            "TrackedCollectionEnumerator",
                            "CheckChanged: has changed (last change={0}, creation={1})",
                            _trackedCollection.LastChange,
                            _creationTime);

                throw new InvalidOperationException(SR.TrackedCollectionEnumHasChanged);
            }
        }
    }
}