// dnlib: See LICENSE.txt for more info using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using dnlib.Threading; namespace dnlib.Utils { /// /// Gets notified of list events /// /// List value public interface IListListener { /// /// Called before a new value is lazily added to the list. /// /// If you must access this list, you can only call _NoLock() methods /// since a write lock is now held by this thread. /// Index where the value will be added /// Value that will be added to the list. It can be modified by /// the callee. void OnLazyAdd(int index, ref TListValue value); /// /// Called before a new value is added to the list. /// /// If you must access this list, you can only call _NoLock() methods /// since a write lock is now held by this thread. /// Index where the value will be added /// Value that will be added to the list void OnAdd(int index, TListValue value); /// /// Called before a value is removed from the list. If all elements are removed, /// is called, and this method is not called. /// /// If you must access this list, you can only call _NoLock() methods /// since a write lock is now held by this thread. /// Index of value /// The value that will be removed void OnRemove(int index, TListValue value); /// /// Called after the list has been resized (eg. an element has been added/removed). It's not /// called when an element is replaced. /// /// If you must access this list, you can only call _NoLock() methods /// since a write lock is now held by this thread. /// Index where the change occurred. void OnResize(int index); /// /// Called before the whole list is cleared. /// /// If you must access this list, you can only call _NoLock() methods /// since a write lock is now held by this thread. void OnClear(); } /// /// Implements a that is lazily initialized /// /// Type to store in list [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(CollectionDebugView<>))] public class LazyList : ILazyList where TValue : class { private protected readonly List list; int id = 0; private protected readonly IListListener listener; #if THREAD_SAFE readonly Lock theLock = Lock.Create(); #endif /// /// Stores a simple value /// private protected class Element { protected TValue value; /// /// true if it has been initialized, false otherwise /// public virtual bool IsInitialized_NoLock => true; /// /// Default constructor /// protected Element() { } /// /// Constructor that should be used when new elements are inserted into /// /// User data public Element(TValue data) => value = data; /// /// Gets the value /// /// Index in the list public virtual TValue GetValue_NoLock(int index) => value; /// /// Sets the value /// /// Index in the list /// New value public virtual void SetValue_NoLock(int index, TValue value) => this.value = value; /// public override string ToString() => value?.ToString() ?? string.Empty; } /// public int Count { get { #if THREAD_SAFE theLock.EnterReadLock(); try { #endif return Count_NoLock; #if THREAD_SAFE } finally { theLock.ExitReadLock(); } #endif } } /// internal int Count_NoLock => list.Count; /// public bool IsReadOnly => false; /// public TValue this[int index] { get { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif return Get_NoLock(index); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } set { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif Set_NoLock(index, value); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } } internal TValue Get_NoLock(int index) => list[index].GetValue_NoLock(index); void Set_NoLock(int index, TValue value) { if (listener is not null) { listener.OnRemove(index, list[index].GetValue_NoLock(index)); listener.OnAdd(index, value); } list[index].SetValue_NoLock(index, value); id++; } /// /// Default constructor /// public LazyList() : this(null) { } /// /// Constructor /// /// List listener public LazyList(IListListener listener) { this.listener = listener; list = new List(); } private protected LazyList(int length, IListListener listener) { this.listener = listener; list = new List(length); } /// public int IndexOf(TValue item) { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif return IndexOf_NoLock(item); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } int IndexOf_NoLock(TValue item) { for (int i = 0; i < list.Count; i++) { if (list[i].GetValue_NoLock(i) == item) return i; } return -1; } /// public void Insert(int index, TValue item) { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif Insert_NoLock(index, item); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } void Insert_NoLock(int index, TValue item) { if (listener is not null) listener.OnAdd(index, item); list.Insert(index, new Element(item)); if (listener is not null) listener.OnResize(index); id++; } /// public void RemoveAt(int index) { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif RemoveAt_NoLock(index); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } void RemoveAt_NoLock(int index) { if (listener is not null) listener.OnRemove(index, list[index].GetValue_NoLock(index)); list.RemoveAt(index); if (listener is not null) listener.OnResize(index); id++; } /// public void Add(TValue item) { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif Add_NoLock(item); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } void Add_NoLock(TValue item) { int index = list.Count; if (listener is not null) listener.OnAdd(index, item); list.Add(new Element(item)); if (listener is not null) listener.OnResize(index); id++; } /// public void Clear() { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif Clear_NoLock(); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } void Clear_NoLock() { if (listener is not null) listener.OnClear(); list.Clear(); if (listener is not null) listener.OnResize(0); id++; } /// public bool Contains(TValue item) => IndexOf(item) >= 0; /// public void CopyTo(TValue[] array, int arrayIndex) { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif CopyTo_NoLock(array, arrayIndex); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } void CopyTo_NoLock(TValue[] array, int arrayIndex) { for (int i = 0; i < list.Count; i++) array[arrayIndex + i] = list[i].GetValue_NoLock(i); } /// public bool Remove(TValue item) { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif return Remove_NoLock(item); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif } bool Remove_NoLock(TValue item) { int index = IndexOf_NoLock(item); if (index < 0) return false; RemoveAt_NoLock(index); return true; } internal bool IsInitialized(int index) { #if THREAD_SAFE theLock.EnterReadLock(); try { #endif return IsInitialized_NoLock(index); #if THREAD_SAFE } finally { theLock.ExitReadLock(); } #endif } bool IsInitialized_NoLock(int index) { if ((uint)index >= (uint)list.Count) return false; return list[index].IsInitialized_NoLock; } /// /// Enumerator /// public struct Enumerator : IEnumerator { readonly LazyList list; readonly int id; int index; TValue current; internal Enumerator(LazyList list) { this.list = list; index = 0; current = default; #if THREAD_SAFE list.theLock.EnterReadLock(); try { #endif id = list.id; #if THREAD_SAFE } finally { list.theLock.ExitReadLock(); } #endif } /// /// Gets the current value /// public TValue Current => current; object IEnumerator.Current => current; /// /// Moves to the next element in the collection /// /// public bool MoveNext() { #if THREAD_SAFE list.theLock.EnterWriteLock(); try { #endif if (list.id == id && index < list.Count_NoLock) { current = list.list[index].GetValue_NoLock(index); index++; return true; } else return MoveNextDoneOrThrow_NoLock(); #if THREAD_SAFE } finally { list.theLock.ExitWriteLock(); } #endif } bool MoveNextDoneOrThrow_NoLock() { if (list.id != id) throw new InvalidOperationException("List was modified"); current = default; return false; } /// /// Disposes the enumerator /// public void Dispose() { } void IEnumerator.Reset() => throw new NotSupportedException(); } /// /// Gets the list enumerator /// /// public Enumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); internal IEnumerable GetEnumerable_NoLock() { int id2 = id; for (int i = 0; i < list.Count; i++) { if (id != id2) throw new InvalidOperationException("List was modified"); yield return list[i].GetValue_NoLock(i); } } /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } /// /// Implements a that is lazily initialized /// /// Type to store in list /// Type of the context passed to the read-value delegate [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(CollectionDebugView<,>))] public class LazyList : LazyList, ILazyList where TValue : class { /*readonly*/ TContext context; readonly Func readOriginalValue; /// /// Stores data and keeps track of the original index and whether the data has been /// initialized or not. /// sealed class LazyElement : Element { internal readonly int origIndex; LazyList lazyList; /// public override bool IsInitialized_NoLock => lazyList is null; /// public override TValue GetValue_NoLock(int index) { if (lazyList is not null) { value = lazyList.ReadOriginalValue_NoLock(index, origIndex); lazyList = null; } return value; } /// public override void SetValue_NoLock(int index, TValue value) { this.value = value; lazyList = null; } /// /// Constructor that should only be called when is initialized. /// /// Original index of this element /// LazyList instance public LazyElement(int origIndex, LazyList lazyList) { this.origIndex = origIndex; this.lazyList = lazyList; } /// public override string ToString() { if (lazyList is not null) { value = lazyList.ReadOriginalValue_NoLock(this); lazyList = null; } return value is null ? string.Empty : value.ToString(); } } /// /// Default constructor /// public LazyList() : this(null) { } /// /// Constructor /// /// List listener public LazyList(IListListener listener) : base(listener) { } /// /// Constructor /// /// Initial length of the list /// Context passed to /// Delegate instance that returns original values public LazyList(int length, TContext context, Func readOriginalValue) : this(length, null, context, readOriginalValue) { } /// /// Constructor /// /// Initial length of the list /// List listener /// Context passed to /// Delegate instance that returns original values public LazyList(int length, IListListener listener, TContext context, Func readOriginalValue) : base(length, listener) { this.context = context; this.readOriginalValue = readOriginalValue; for (int i = 0; i < length; i++) list.Add(new LazyElement(i, this)); } TValue ReadOriginalValue_NoLock(LazyElement elem) => ReadOriginalValue_NoLock(list.IndexOf(elem), elem.origIndex); TValue ReadOriginalValue_NoLock(int index, int origIndex) { var newValue = readOriginalValue(context, origIndex); listener?.OnLazyAdd(index, ref newValue); return newValue; } } }