DotNext by Roman Sakno

<PackageReference Include="DotNext" Version="1.2.7" />

 CopyOnWriteList<T>

A thread-safe variant of List<T> in which all mutative operations are implemented by making a snapshot copy of the underlying array.
using DotNext.Runtime.InteropServices; using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace DotNext.Collections.Concurrent { [Serializable] public class CopyOnWriteList<T> : IReadOnlyList<T>, IEnumerable<T>, IEnumerable, IReadOnlyCollection<T>, IList<T>, ICollection<T>, ICloneable { [StructLayout(LayoutKind.Auto)] public struct Enumerator : IEnumerator<T>, IEnumerator, IDisposable { private const long InitialPosition = -1; private readonly T[] snapshot; private long position; [System.Runtime.CompilerServices.IsReadOnly] public ref T Current { [return: System.Runtime.CompilerServices.IsReadOnly] get { return ref Memory.GetReadonlyRef<T>(snapshot, position); } } T IEnumerator<T>.Current { get { return Current; } } object IEnumerator.Current { get { return Current; } } internal Enumerator(T[] backingStore) { snapshot = backingStore; position = -1; } void IDisposable.Dispose() { this = default(Enumerator); } public bool MoveNext() { return ++position < snapshot.LongLength; } void IEnumerator.Reset() { position = -1; } } private volatile T[] backingStore; bool ICollection<T>.IsReadOnly { get { return false; } } int ICollection<T>.Count { get { return backingStore.Length; } } int IReadOnlyCollection<T>.Count { get { return backingStore.Length; } } public long Count => backingStore.LongLength; public ReadOnlyMemory<T> Snapshot => new ReadOnlyMemory<T>(backingStore); public T this[long index] { get { return backingStore[index]; } set { T[] array = Snapshot.ToArray(); array[index] = value; ReplaceStore(array); } } T IList<T>.this[int index] { get { return this[index]; } set { this[index] = value; } } T IReadOnlyList<T>.this[int index] { get { return this[index]; } } public CopyOnWriteList(IReadOnlyCollection<T> collection) { backingStore = new T[collection.Count]; long num = 0; foreach (T item in collection) { T[] array = backingStore; long num2 = num; num = num2 + 1; array[num2] = item; } } private CopyOnWriteList(T[] backingStore) { this.backingStore = backingStore; } public CopyOnWriteList<T> Clone() { return new CopyOnWriteList<T>(Snapshot.ToArray()); } object ICloneable.Clone() { return Clone(); } public CopyOnWriteList() { backingStore = Array.Empty<T>(); } private static T[] Add(T[] backingStore, T item) { long num = backingStore.LongLength; T[] array = new T[num + 1]; backingStore.CopyTo(array, 0); array[num] = item; return array; } [MethodImpl(MethodImplOptions.Synchronized)] public void Add(T item) { backingStore = Add(backingStore, item); } public int IndexOf(T item) { return Array.IndexOf<T>(backingStore, item); } public int LastIndexOf(T item) { return Array.LastIndexOf<T>(backingStore, item); } public T Find(Predicate<T> match) { return Array.Find<T>(backingStore, match); } public T FindLast(Predicate<T> match) { return Array.FindLast<T>(backingStore, match); } public int FindIndex(Predicate<T> match) { return Array.FindIndex<T>(backingStore, match); } public int FindLastIndex(Predicate<T> match) { return Array.FindLastIndex<T>(backingStore, match); } public bool Contains(T item) { return IndexOf(item) >= 0; } public bool Exists(Predicate<T> match) { return Array.Exists<T>(backingStore, match); } public void CopyTo(T[] array, int arrayIndex) { backingStore.CopyTo(array, arrayIndex); } [MethodImpl(MethodImplOptions.Synchronized)] private T[] ReplaceStore(T[] newStore) { T[] result = backingStore; backingStore = newStore; return result; } public void Set(ReadOnlySpan<T> array) { ReplaceStore(array.ToArray()); } public void Set<G>(ICollection<G> items, [In] [System.Runtime.CompilerServices.IsReadOnly] ref ValueFunc<G, T> converter) { if (items.Count == 0) this.ReplaceStore(Array.Empty<T>()); else { T[] array = new T[items.Count]; long num = 0; foreach (G item in (IEnumerable<G>)items) { T[] array2 = array; long num2 = num; num = num2 + 1; array2[num2] = converter.Invoke(item); } this.ReplaceStore(array); } } public void Set<G>(ICollection<G> items, Converter<G, T> converter) { ValueFunc<G, T> converter2 = Converter.AsValueFunc<G, T>(converter, true); Set(items, ref converter2); } public void Clear() { T[] array = ReplaceStore(Array.Empty<T>()); Array.Clear(array, 0, array.Length); } public void Clear([In] [System.Runtime.CompilerServices.IsReadOnly] ref ValueAction<T> cleaner) { T[] array = ReplaceStore(Array.Empty<T>()); for (long num = 0; num < array.LongLength; num++) { ref T reference = ref array[num]; cleaner.Invoke(reference); reference = default(T); } } public void Clear(Action<T> cleaner) { ValueAction<T> cleaner2 = new ValueAction<T>(cleaner, true); Clear(ref cleaner2); } [MethodImpl(MethodImplOptions.Synchronized)] public void RemoveAt(long index) { backingStore = OneDimensionalArray.RemoveAt<T>(backingStore, index); } [MethodImpl(MethodImplOptions.Synchronized)] public bool Remove(T item) { int num = IndexOf(item); if ((long)num >= 0) { RemoveAt(num); return true; } return false; } [MethodImpl(MethodImplOptions.Synchronized)] public void Insert(long index, T item) { backingStore = OneDimensionalArray.Insert<T>(backingStore, item, index); } [MethodImpl(MethodImplOptions.Synchronized)] public long RemoveAll(ValueFunc<T, bool> match) { backingStore = OneDimensionalArray.RemoveAll<T>(backingStore, ref match, out long count); return count; } public long RemoveAll(Predicate<T> match) { return RemoveAll(Predicate.AsValueFunc<T>(match, true)); } [MethodImpl(MethodImplOptions.Synchronized)] public void RemoveAll([In] [System.Runtime.CompilerServices.IsReadOnly] ref ValueFunc<T, bool> match, [In] [System.Runtime.CompilerServices.IsReadOnly] ref ValueAction<T> callback) { backingStore = OneDimensionalArray.RemoveAll<T>(backingStore, ref match, ref callback); } public void RemoveAll(Predicate<T> match, Action<T> callback) { ValueFunc<T, bool> match2 = Predicate.AsValueFunc<T>(match, true); ValueAction<T> callback2 = new ValueAction<T>(callback, false); RemoveAll(ref match2, ref callback2); } void IList<T>.Insert(int index, T item) { Insert(index, item); } void IList<T>.RemoveAt(int index) { RemoveAt(index); } public Enumerator GetEnumerator() { return new Enumerator(backingStore); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return backingStore.GetEnumerator(); } public static explicit operator ReadOnlySpan<T>(CopyOnWriteList<T> list) { if (list != null) return new ReadOnlySpan<T>(list.backingStore); return default(ReadOnlySpan<T>); } } }