File: Collections\CopyOnReadEnumerable.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// 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.Linq;
using Microsoft.Build.Shared;
 
#nullable disable
 
namespace Microsoft.Build.Collections
{
    /// <summary>
    /// A class which implements IEnumerable by creating a copy of the backing collection.
    /// </summary>
    /// <remarks>
    /// <see cref="GetEnumerator()"/> is thread safe for concurrent access.
    /// </remarks>
    /// <typeparam name="TSource">The type contained in the backing collection.</typeparam>
    /// <typeparam name="TResult">The type of items being enumerated.</typeparam>
    internal class CopyOnReadEnumerable<TSource, TResult> : IEnumerable<TResult>
    {
        /// <summary>
        /// The backing collection.
        /// </summary>
        private readonly IEnumerable<TSource> _backingEnumerable;
 
        /// <summary>
        /// The object used to synchronize access for copying.
        /// </summary>
        private readonly object _syncRoot;
 
        /// <summary>
        /// The function to translate items in the backing collection to the resulting type.
        /// </summary>
        private readonly Func<TSource, TResult> _selector;
 
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="backingEnumerable">The collection which serves as a source for enumeration.</param>
        /// <param name="syncRoot">The object used to synchronize access for copying.</param>
        /// <param name="selector">function to translate items in the backing collection to the resulting type.</param>
        public CopyOnReadEnumerable(IEnumerable<TSource> backingEnumerable, object syncRoot, Func<TSource, TResult> selector)
        {
            ErrorUtilities.VerifyThrowArgumentNull(backingEnumerable, nameof(backingEnumerable));
            ErrorUtilities.VerifyThrowArgumentNull(syncRoot, nameof(syncRoot));
 
            _backingEnumerable = backingEnumerable;
            _syncRoot = syncRoot;
            _selector = selector;
        }
 
        #region IEnumerable<T> Members
 
        /// <summary>
        /// Returns an enumerator over the collection.
        /// </summary>
        /// <returns>The enumerator.</returns>
        public IEnumerator<TResult> GetEnumerator()
        {
            List<TResult> list;
 
#if NETCOREAPP
            if (_backingEnumerable.TryGetNonEnumeratedCount(out int count))
            {
#else
            if (_backingEnumerable is ICollection backingCollection)
            {
                int count = backingCollection.Count;
#endif
                list = new List<TResult>(count);
            }
            else
            {
                list = new List<TResult>();
            }
 
            lock (_syncRoot)
            {
                list.AddRange(_backingEnumerable.Select(_selector));
            }
 
            return list.GetEnumerator();
        }
 
        #endregion
 
        #region IEnumerable Members
 
        /// <summary>
        /// Returns an numerator over the collection.
        /// </summary>
        /// <returns>The enumerator.</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<TResult>)this).GetEnumerator();
        }
 
        #endregion
    }
}