using AyCode.Core.Enums; using AyCode.Core.Extensions; using AyCode.Core.Helpers; using AyCode.Core.Interfaces; using AyCode.Services.SignalRs; using System.Collections; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace AyCode.Services.Server.SignalRs { //public class TrackingItemGuid(TrackingState trackingState, TDataItem currentValue, TDataItem? originalValue = null) : TrackingItem(trackingState, currentValue, originalValue) // where TDataItem : class, IId { } //public class TrackingItemInt(TrackingState trackingState, TDataItem currentValue, TDataItem? originalValue = null) : TrackingItem(trackingState, currentValue, originalValue) // where TDataItem : class, IId {} public class TrackingItem(TrackingState trackingState, TDataItem currentValue, TDataItem? originalValue = null) where TDataItem : class, IId where TId : struct { public TrackingState TrackingState { get; internal set; } = trackingState; public TDataItem CurrentValue { get; internal set; } = currentValue; public TDataItem? OriginalValue { get; init; } = originalValue; internal TrackingItem UpdateItem(TrackingState trackingState, TDataItem newValue) //where TTrackingItem : TrackingItem { CurrentValue = newValue; if (TrackingState != TrackingState.Add) TrackingState = trackingState; return (TrackingItem)this; } } //public class ChangeTrackingGuid : ChangeTracking /*: IEnumerable>*/ where TDataItem : class, IId //{ // protected override bool HasIdValue(TDataItem dataItem) => !dataItem.Id.IsNullOrEmpty(); // protected override int FindIndex(TDataItem newValue) => TrackingItems.FindIndex(x => x.CurrentValue.Id == newValue.Id); // public override bool TryGetTrackingItem(Guid id, [NotNullWhen(true)] out TrackingItem? trackingItem) // { // trackingItem = TrackingItems.FirstOrDefault(x => x.CurrentValue.Id == id); // return trackingItem != null; // } //} //public class ChangeTrackingInt : ChangeTracking /*: IEnumerable>*/ where TDataItem : class, IId //{ // protected override bool HasIdValue(TDataItem dataItem) => true;//dataItem.Id.IsNullOrEmpty(); // protected override int FindIndex(TDataItem newValue) => TrackingItems.FindIndex(x => x.CurrentValue.Id == newValue.Id); // public override bool TryGetTrackingItem(int id, [NotNullWhen(true)] out TrackingItem? trackingItem) // { // trackingItem = TrackingItems.FirstOrDefault(x => x.CurrentValue.Id == id); // return trackingItem != null; // } //} public class ChangeTracking /*: IEnumerable>*/where TDataItem : class, IId where TId : struct { private readonly EqualityComparer _equalityComparerId = EqualityComparer.Default; private readonly List> _trackingItems = []; //TODO: Dictionary... - J. //protected abstract bool HasIdValue(TDataItem dataItem); //protected abstract int FindIndex(TDataItem newValue); //public abstract bool TryGetTrackingItem(TId id, [NotNullWhen(true)] out TrackingItem? trackingItem); private bool HasIdValue(TDataItem dataItem) => !_equalityComparerId.Equals(dataItem.Id, default);//dataItem.Id.IsNullOrEmpty(); public int FindIndex(TDataItem newValue) => _trackingItems.FindIndex(x => _equalityComparerId.Equals(x.CurrentValue.Id, newValue.Id)); public bool TryGetTrackingItem(TId id, [NotNullWhen(true)] out TrackingItem? trackingItem) { trackingItem = _trackingItems.FirstOrDefault(x => _equalityComparerId.Equals(x.CurrentValue.Id, id)); return trackingItem != null; } internal TrackingItem? AddTrackingItem(TrackingState trackingState, TDataItem newValue, TDataItem? originalValue = null) { if (!HasIdValue(newValue)) throw new ArgumentNullException(nameof(newValue), $@"currentValue.Id.IsNullOrEmpty()"); var itemIndex = FindIndex(newValue); //_trackingItems.FindIndex(x => x.CurrentValue.Id == newValue.Id); TrackingItem? trackingItem = null; if (itemIndex > -1) { trackingItem = _trackingItems[itemIndex]; if (trackingState == TrackingState.Remove && trackingItem.TrackingState == TrackingState.Add) { _trackingItems.RemoveAt(itemIndex); return null; } return trackingItem.UpdateItem(trackingState, newValue); } if (originalValue != null && Equals(newValue, originalValue)) originalValue = TrackingItemHelpers.JsonClone(originalValue); //trackingItem = new TrackingItem(trackingState, newValue, originalValue); trackingItem = new TrackingItem(trackingState, newValue, originalValue); _trackingItems.Add(trackingItem); return trackingItem; } public int Count => _trackingItems.Count; internal void Clear() => _trackingItems.Clear(); public List> ToList() => _trackingItems.ToList(); internal void Remove(TrackingItem trackingItem) => _trackingItems.Remove(trackingItem); //public IEnumerator> GetEnumerator() //{ // return _trackingItems.GetEnumerator(); //} //IEnumerator IEnumerable.GetEnumerator() //{ // return GetEnumerator(); //} } //[Serializable] //[DebuggerDisplay("Count = {Count}")] //public abstract class AcSignalRDataSourceGuid : AcSignalRDataSource // where TDataItem : class, IId where TIList : class, IList //{ // public AcSignalRDataSourceGuid(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, object[]? contextIds = null) // : base(signalRClient, signalRCrudTags, contextIds) // { // } // protected override bool HasIdValue(TDataItem dataItem) => !dataItem.Id.IsNullOrEmpty(); // protected override bool IdEquals(Guid id1, Guid id2) => id1 == id2; // protected override int FindIndexInnerList(Guid id) => InnerList.FindIndex(x => x.Id == id); // protected override TDataItem? FirstOrDefaultInnerList(Guid id) => InnerList.FirstOrDefault(x => x.Id == id); //} //[Serializable] //[DebuggerDisplay("Count = {Count}")] //public abstract class AcSignalRDataSourceInt : AcSignalRDataSource // where TDataItem : class, IId where TIList : class, IList //{ // public AcSignalRDataSourceInt(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, object[]? contextIds = null) // : base(signalRClient, signalRCrudTags, contextIds) // { // } // protected override bool HasIdValue(TDataItem dataItem) => true; // protected override bool IdEquals(int id1, int id2) => id1 == id2; // protected override int FindIndexInnerList(int id) => InnerList.FindIndex(x => x.Id == id); // protected override TDataItem? FirstOrDefaultInnerList(int id) => InnerList.FirstOrDefault(x => x.Id == id); //} [Serializable] [DebuggerDisplay("Count = {Count}")] public abstract class AcSignalRDataSource : IList, IList, IReadOnlyList where TDataItem : class, IId where TId : struct where TIList : class, IList { private readonly object _syncRoot = new(); private readonly EqualityComparer _equalityComparerId = EqualityComparer.Default; protected TIList InnerList = Activator.CreateInstance();// []; //TODO: Dictionary??? - J. protected readonly ChangeTracking TrackingItems = new(); public object[]? ContextIds; public string? FilterText { get; set; } public AcSignalRClientBase SignalRClient; protected readonly SignalRCrudTags SignalRCrudTags; public Func, Task>? OnDataSourceItemChanged; public Func? OnDataSourceLoaded; //protected abstract bool HasIdValue(TDataItem dataItem); //protected abstract bool IdEquals(TId id1, TId id2); //protected abstract int FindIndexInnerList(TId id); //protected abstract TDataItem? FirstOrDefaultInnerList(TId id); protected bool HasIdValue(TDataItem dataItem) => !_equalityComparerId.Equals(dataItem.Id, default);//dataItem.Id.IsNullOrEmpty(); protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2); protected int FindIndexInnerList(TId id) => InnerList.FindIndex(x => IdEquals(x.Id, id)); protected TDataItem? FirstOrDefaultInnerList(TId id) => InnerList.FirstOrDefault(x => IdEquals(x.Id, id)); public AcSignalRDataSource(AcSignalRClientBase signalRClient, SignalRCrudTags signalRCrudTags, object[]? contextIds = null) { //if (contextIds != null) (ContextIds = new List()).AddRange(contextIds); ContextIds = contextIds; SignalRCrudTags = signalRCrudTags; SignalRClient = signalRClient; } public bool IsSynchronized => true; public object SyncRoot => _syncRoot; public bool IsFixedSize => false; public bool HasWorkingReferenceList { get; private set; } public void SetWorkingReferenceList(TIList? workingIList) { if (workingIList == null) return; //throw new ArgumentNullException(nameof(workingList)); Monitor.Enter(_syncRoot); try { HasWorkingReferenceList = true; if (ReferenceEquals(InnerList, workingIList)) return; if (workingIList.Count == 0) AddRange(InnerList, workingIList); Clear(true); InnerList = workingIList; } finally { Monitor.Exit(_syncRoot); } } public TIList GetReferenceInnerList() => InnerList; private object[]? GetContextParams() { var parameters = new List(); if (ContextIds != null) parameters.AddRange(ContextIds); if (FilterText != null) parameters.Add(FilterText); //Az empty string-et beletesszük, h legyen paraméter! - J. if (parameters.Count == 0) parameters = null; return parameters?.ToArray(); } /// /// GetAllMessageTag /// /// /// public async Task LoadDataSource(bool clearChangeTracking = true) { if (SignalRCrudTags.GetAllMessageTag == AcSignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetAllMessageTag == SignalRTags.None"); var responseData = (await SignalRClient.GetAllAsync(SignalRCrudTags.GetAllMessageTag, GetContextParams())) ?? throw new NullReferenceException(); await LoadDataSource(responseData, false, false, clearChangeTracking); } public Task LoadDataSourceAsync(bool clearChangeTracking = true) { if (SignalRCrudTags.GetAllMessageTag == AcSignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetAllMessageTag == SignalRTags.None"); return SignalRClient.GetAllAsync(SignalRCrudTags.GetAllMessageTag, result=> { if (result.Status != SignalResponseStatus.Success || result.ResponseData == null) throw new NullReferenceException($"LoadDataSourceAsync; result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); return LoadDataSource(result.ResponseData, false, false, clearChangeTracking); }, GetContextParams()); } public async Task LoadDataSource(TIList fromSource, bool refreshDataFromDbAsync = false, bool setSourceToWorkingReferenceList = false, bool clearChangeTracking = true) { Monitor.Enter(_syncRoot); try { if (!ReferenceEquals(InnerList, fromSource)) { if (!setSourceToWorkingReferenceList) { fromSource.CopyTo(InnerList); } else { Clear(clearChangeTracking); if (setSourceToWorkingReferenceList) SetWorkingReferenceList(fromSource); else AddRange(fromSource); } } else if (clearChangeTracking) TrackingItems.Clear(); //TODO: Átgondolni, OnDataSourceLoaded meghívódik mielőtt az adatok betöltődnének a .Forget() miatt! - J. if (refreshDataFromDbAsync) { LoadDataSourceAsync(false).Forget(); return; } } finally { Monitor.Exit(_syncRoot); } if (OnDataSourceLoaded != null) await OnDataSourceLoaded.Invoke(); } public async Task LoadItem(TId id) { if (SignalRCrudTags.GetItemMessageTag == AcSignalRTags.None) throw new ArgumentException($"SignalRCrudTags.GetItemMessageTag == SignalRTags.None"); TDataItem? resultitem = null; Monitor.Enter(_syncRoot); try { resultitem = await SignalRClient.GetByIdAsync(SignalRCrudTags.GetItemMessageTag, id); if (resultitem == null) return null; if (TryGetIndex(id, out var index)) resultitem.CopyTo(InnerList[index]);//InnerList[index] = resultitem); else InnerList.Add(resultitem); var eventArgs = new ItemChangedEventArgs(resultitem, TrackingState.Get); if (OnDataSourceItemChanged != null) await OnDataSourceItemChanged.Invoke(eventArgs); } finally { Monitor.Exit(_syncRoot); } return resultitem; } /// /// set: UpdateMessageTag /// /// /// /// public TDataItem this[int index] { get { if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); Monitor.Enter(_syncRoot); try { return InnerList[index]; } finally { Monitor.Exit(_syncRoot); } } set { Monitor.Enter(_syncRoot); try { UpdateUnsafe(index, value); } finally { Monitor.Exit(_syncRoot); } } } public void Add(TDataItem newValue) { if (!HasIdValue(newValue)) throw new ArgumentNullException(nameof(newValue), @"Add->HasIdValue(newValue) == false"); Monitor.Enter(_syncRoot); try { if (Contains(newValue)) throw new ArgumentException($@"It already contains this Id! {newValue}", nameof(newValue)); UnsafeAdd(newValue); } finally { Monitor.Exit(_syncRoot); } } /// /// AddMessageTag /// /// /// /// public async Task Add(TDataItem newValue, bool autoSave) { Monitor.Enter(_syncRoot); try { Add(newValue); return autoSave ? await SaveItem(newValue, TrackingState.Add) : newValue; } finally { Monitor.Exit(_syncRoot); } } /// /// AddMessageTag or UpdateMessageTag /// /// /// /// public async Task AddOrUpdate(TDataItem newValue, bool autoSave) { if (!HasIdValue(newValue)) throw new ArgumentNullException(nameof(newValue), @"AddOrUpdate->newValue.Id.IsNullOrEmpty()"); Monitor.Enter(_syncRoot); try { var index = IndexOf(newValue); return index > -1 ? await Update(index, newValue, autoSave) : await Add(newValue, autoSave); } finally { Monitor.Exit(_syncRoot); } } //public void AddRange(IEnumerable collection) //{ // lock (_syncRoot) // { // } //} protected void UnsafeAdd(TDataItem newValue) { TrackingItems.AddTrackingItem(TrackingState.Add, newValue); InnerList.Add(newValue); } public void AddRange(IEnumerable source) => AddRange(source, InnerList); protected void AddRange(IEnumerable source, TIList destination) { //TODO: CHANGETRACKINGITEM - J. switch (destination) { case IAcObservableCollection dest: dest.AddRange(source); break; case List dest: dest.AddRange(source); break; default: { foreach (var dataItem in source) destination.Add(dataItem); break; } } } /// /// AddMessageTag /// /// /// /// /// /// public void Insert(int index, TDataItem newValue) { if (!HasIdValue(newValue)) throw new ArgumentNullException(nameof(newValue), @"Insert->newValue.Id.IsNullOrEmpty()"); Monitor.Enter(_syncRoot); try { if (Contains(newValue)) throw new ArgumentException($@"Insert; It already contains this Id! {newValue}", nameof(newValue)); TrackingItems.AddTrackingItem(TrackingState.Add, newValue); InnerList.Insert(index, newValue); } finally { Monitor.Exit(_syncRoot); } } public async Task Insert(int index, TDataItem newValue, bool autoSave) { Monitor.Enter(_syncRoot); try { Insert(index, newValue); return autoSave ? await SaveItem(newValue, TrackingState.Add) : newValue; } finally { Monitor.Exit(_syncRoot); } } /// /// UpdateMessageTag /// /// /// public Task Update(TDataItem newItem, bool autoSave) => Update(IndexOf(newItem), newItem, autoSave); /// /// UpdateMessageTag /// /// /// /// /// /// /// /// /// /// public async Task Update(int index, TDataItem newValue, bool autoSave) { Monitor.Enter(_syncRoot); try { UpdateUnsafe(index, newValue); return autoSave ? await SaveItem(newValue, TrackingState.Update) : newValue; } finally { Monitor.Exit(_syncRoot); } } private void UpdateUnsafe(int index, TDataItem newValue) { if (default(TDataItem) != null && newValue == null) throw new NullReferenceException(nameof(newValue)); if (!HasIdValue(newValue)) throw new ArgumentNullException(nameof(newValue), @"UpdateUnsafe->newValue.Id.IsNullOrEmpty()"); if ((uint)index >= (uint)Count) throw new ArgumentOutOfRangeException(nameof(index)); Monitor.Enter(_syncRoot); try { var currentItem = InnerList[index]; if (!IdEquals(currentItem.Id, newValue.Id)) throw new ArgumentException($@"UpdateUnsafe; currentItem.Id != item.Id! {newValue}", nameof(newValue)); TrackingItems.AddTrackingItem(TrackingState.Update, newValue, currentItem); InnerList[index] = newValue; } finally { Monitor.Exit(_syncRoot); } } /// /// RemoveMessageTag /// /// /// public bool Remove(TDataItem item) { Monitor.Enter(_syncRoot); try { var index = IndexOf(item); if (index < 0) return false; RemoveAt(index); return true; } finally { Monitor.Exit(_syncRoot); } } public async Task Remove(TId id, bool autoSave) { Monitor.Enter(_syncRoot); try { var item = FirstOrDefaultInnerList(id); return item == null || await Remove(item, autoSave); } finally { Monitor.Exit(_syncRoot); } } public async Task Remove(TDataItem item, bool autoSave) { Monitor.Enter(_syncRoot); try { var result = Remove(item); if (!autoSave || !result) return result; await SaveItem(item, TrackingState.Remove); return true; } finally { Monitor.Exit(_syncRoot); } } /// /// /// /// /// /// public bool TryRemove(TId id, out TDataItem? item) { Monitor.Enter(_syncRoot); try { return TryGetValue(id, out item) && Remove(item); } finally { Monitor.Exit(_syncRoot); } } /// /// RemoveMessageTag /// /// /// /// /// /// public void RemoveAt(int index) { Monitor.Enter(_syncRoot); try { var currentItem = InnerList[index]; if (!HasIdValue(currentItem)) throw new ArgumentNullException(nameof(currentItem), $@"RemoveAt->item.Id.IsNullOrEmpty(); index: {index}"); TrackingItems.AddTrackingItem(TrackingState.Remove, currentItem, currentItem); InnerList.RemoveAt(index); } finally { Monitor.Exit(_syncRoot); } } public async Task RemoveAt(int index, bool autoSave) { Monitor.Enter(_syncRoot); try { var currentItem = InnerList[index]; RemoveAt(index); if (autoSave) { await SaveItem(currentItem, TrackingState.Remove); } } finally { Monitor.Exit(_syncRoot); } } /// /// /// /// public List> GetTrackingItems() { Monitor.Enter(_syncRoot); try { return TrackingItems.ToList(); } finally { Monitor.Exit(_syncRoot); } } public void SetTrackingStateToUpdate(TDataItem item) { Monitor.Enter(_syncRoot); try { if (TrackingItems.TryGetTrackingItem(item.Id, out var trackingItem)) { if (trackingItem.TrackingState != TrackingState.Add) trackingItem.TrackingState = TrackingState.Update; return; } if (!TryGetValue(item.Id, out var originalItem)) return; TrackingItems.AddTrackingItem(TrackingState.Update, item, originalItem); } finally { Monitor.Exit(_syncRoot); } } /// /// /// /// /// /// public bool TryGetTrackingItem(TId id, [NotNullWhen(true)] out TrackingItem? trackingItem) { Monitor.Enter(_syncRoot); try { return TrackingItems.TryGetTrackingItem(id, out trackingItem); } finally { Monitor.Exit(_syncRoot); } } /// /// /// /// Unsaved items public async Task>> SaveChanges() { Monitor.Enter(_syncRoot); try { foreach (var trackingItem in TrackingItems.ToList()) { try { await SaveTrackingItemUnsafe(trackingItem); } catch(Exception ex) { TryRollbackItem(trackingItem.CurrentValue.Id, out _); } } return TrackingItems.ToList(); } finally { Monitor.Exit(_syncRoot); } } public async Task SaveChangesAsync() { Monitor.Enter(_syncRoot); try { foreach (var trackingItem in TrackingItems.ToList()) { try { await SaveTrackingItemUnsafeAsync(trackingItem); } catch(Exception ex) { TryRollbackItem(trackingItem.CurrentValue.Id, out _); } } } finally { Monitor.Exit(_syncRoot); } } /// /// /// /// /// public async Task SaveItem(TId id) { Monitor.Enter(_syncRoot); try { TDataItem resultItem = null!; if (TryGetTrackingItem(id, out var trackingItem)) resultItem = await SaveTrackingItemUnsafe(trackingItem); if (resultItem == null) throw new NullReferenceException($"SaveItem; resultItem == null"); return resultItem; } finally { Monitor.Exit(_syncRoot); } } public async Task SaveItem(TId id, TrackingState trackingState) { //Monitor.Enter(_syncRoot); try { TDataItem resultItem = null!; if (TryGetValue(id, out var item)) resultItem = await SaveItem(item, trackingState); if (resultItem == null) throw new NullReferenceException($"SaveItem; resultItem == null"); return resultItem; } finally { //Monitor.Exit(_syncRoot); } } public Task SaveItem(TDataItem item, TrackingState trackingState) => SaveItemUnsafe(item, trackingState); protected Task SaveTrackingItemUnsafe(TrackingItem trackingItem) => SaveItemUnsafe(trackingItem.CurrentValue, trackingItem.TrackingState); protected Task SaveTrackingItemUnsafeAsync(TrackingItem trackingItem) => SaveItemUnsafeAsync(trackingItem.CurrentValue, trackingItem.TrackingState); protected Task SaveItemUnsafe(TDataItem item, TrackingState trackingState) { var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); if (messageTag == AcSignalRTags.None) throw new ArgumentException($"SaveItemUnsafe; messageTag == SignalRTags.None"); return SignalRClient.PostDataAsync(messageTag, item).ContinueWith(x => { if (x.Result == null) { if (TryRollbackItem(item.Id, out _)) return item; throw new NullReferenceException($"SaveItemUnsafe; result == null"); } ProcessSavedResponseItem(x.Result, trackingState, item.Id); return x.Result; }); } protected Task SaveItemUnsafeAsync(TDataItem item, TrackingState trackingState) { var messageTag = SignalRCrudTags.GetMessageTagByTrackingState(trackingState); if (messageTag == AcSignalRTags.None) return Task.CompletedTask; return SignalRClient.PostDataAsync(messageTag, item, response => { Monitor.Enter(_syncRoot); try { if (response.Status != SignalResponseStatus.Success || response.ResponseData == null) { if (TryRollbackItem(item.Id, out _)) return Task.CompletedTask; throw new NullReferenceException($"SaveItemUnsafeAsync; result.Status != SignalResponseStatus.Success || result.ResponseData == null; Status: {SignalResponseStatus.Success}"); } return ProcessSavedResponseItem(response.ResponseData, trackingState, item.Id); } finally { Monitor.Exit(_syncRoot); } }); } private Task ProcessSavedResponseItem(TDataItem? resultItem, TrackingState trackingState, TId originalId) { if (resultItem == null) return Task.CompletedTask; if (TryGetTrackingItem(originalId, out var trackingItem)) TrackingItems.Remove(trackingItem); if (TryGetIndex(originalId, out var index)) { //InnerList[index] = resultItem; resultItem.CopyTo(InnerList[index]); } var eventArgs = new ItemChangedEventArgs(resultItem, trackingState); if (OnDataSourceItemChanged != null) return OnDataSourceItemChanged.Invoke(eventArgs); return Task.CompletedTask; } protected void RollbackItemUnsafe(TrackingItem trackingItem) { if (TryGetIndex(trackingItem.CurrentValue.Id, out var index)) { if (trackingItem.TrackingState == TrackingState.Add) InnerList.RemoveAt(index); else trackingItem.OriginalValue!.CopyTo(InnerList[index]);//InnerList[index] = trackingItem.OriginalValue!); } else if (trackingItem.TrackingState != TrackingState.Add) InnerList.Add(trackingItem.OriginalValue!); TrackingItems.Remove(trackingItem); } public bool TryRollbackItem(TId id, out TDataItem? originalValue) { Monitor.Enter(_syncRoot); try { if (TryGetTrackingItem(id, out var trackingItem)) { originalValue = trackingItem.OriginalValue; RollbackItemUnsafe(trackingItem); return true; } originalValue = null; return false; } finally { Monitor.Exit(_syncRoot); } } public void Rollback() { Monitor.Enter(_syncRoot); try { foreach (var trackingItem in TrackingItems.ToList()) RollbackItemUnsafe(trackingItem); } finally { Monitor.Exit(_syncRoot); } } public int Count { get { Monitor.Enter(_syncRoot); try { return InnerList.Count; } finally { Monitor.Exit(_syncRoot); } } } public void Clear() => Clear(true); public void Clear(bool clearChangeTracking) { Monitor.Enter(_syncRoot); try { if (clearChangeTracking) TrackingItems.Clear(); InnerList.Clear(); } finally { Monitor.Exit(_syncRoot); } } public int IndexOf(TId id) { Monitor.Enter(_syncRoot); try { return FindIndexInnerList(id); } finally { Monitor.Exit(_syncRoot); } } public int IndexOf(TDataItem item) => IndexOf(item.Id); public bool TryGetIndex(TId id, out int index) => (index = IndexOf(id)) > -1; public bool Contains(TDataItem item) => IndexOf(item) > -1; public bool TryGetValue(TId id, [NotNullWhen(true)] out TDataItem? item) { Monitor.Enter(_syncRoot); try { item = FirstOrDefaultInnerList(id);//InnerList.FirstOrDefault(x => x.Id == id); return item != null; } finally { Monitor.Exit(_syncRoot); } } public void CopyTo(TDataItem[] array) => CopyTo(array, 0); public void CopyTo(TDataItem[] array, int arrayIndex) { Monitor.Enter(_syncRoot); try { InnerList.CopyTo(array, arrayIndex); } finally { Monitor.Exit(_syncRoot); } } public int BinarySearch(int index, int count, TDataItem item, IComparer? comparer) { throw new NotImplementedException($"BinarySearch"); if (index < 0) throw new ArgumentOutOfRangeException(nameof(index)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (Count - index < count) throw new ArgumentException("Invalid length"); //Monitor.Enter(_syncRoot); //try //{ // return InnerList.BinarySearch(index, count, item, comparer); //} //finally //{ // Monitor.Exit(_syncRoot); //} } public int BinarySearch(TDataItem item) => BinarySearch(0, Count, item, null); public int BinarySearch(TDataItem item, IComparer? comparer) => BinarySearch(0, Count, item, comparer); public IEnumerator GetEnumerator() { Monitor.Enter(_syncRoot); try { //return InnerList.ToList().GetEnumerator(); return InnerList.GetEnumerator(); } finally { Monitor.Exit(_syncRoot); } } public ReadOnlyCollection AsReadOnly() => new(this); private static bool IsCompatibleObject(object? value) => (value is TDataItem) || (value == null && default(TDataItem) == null); #region IList, ICollection bool IList.IsReadOnly => false; object? IList.this[int index] { get => this[index]; set { if (default(TDataItem) != null && value == null) throw new NullReferenceException(nameof(value)); try { this[index] = (TDataItem)value!; } catch (InvalidCastException) { throw new InvalidCastException(nameof(value)); } } } int IList.Add(object? item) { if (default(TDataItem) != null && item == null) throw new NullReferenceException(nameof(item)); try { Add((TDataItem)item!); } catch (InvalidCastException) { throw new InvalidCastException(nameof(item)); } return Count - 1; } void IList.Clear() => Clear(true); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); bool IList.Contains(object? item) => IsCompatibleObject(item) && Contains((TDataItem)item!); int IList.IndexOf(object? item) => (IsCompatibleObject(item)) ? IndexOf((TDataItem)item!) : -1; void IList.Insert(int index, object? item) { if (default(TDataItem) != null && item == null) throw new NullReferenceException(nameof(item)); try { Insert(index, (TDataItem)item!); } catch (InvalidCastException) { throw new InvalidCastException(nameof(item)); } } void IList.Remove(object? item) { if (IsCompatibleObject(item)) Remove((TDataItem)item!); } void ICollection.Clear() => Clear(true); void ICollection.CopyTo(Array array, int arrayIndex) { if ((array != null) && (array.Rank != 1)) { throw new ArgumentException(); } try { Monitor.Enter(_syncRoot); try { //TODO: _list.ToArray() - ez nem az igazi... - J. Array.Copy(InnerList.ToArray(), 0, array!, arrayIndex, InnerList.Count); } finally { Monitor.Exit(_syncRoot); } } catch (ArrayTypeMismatchException) { throw new ArrayTypeMismatchException(); } } int ICollection.Count => Count; int ICollection.Count => Count; bool ICollection.IsReadOnly => false; void IList.RemoveAt(int index) => RemoveAt(index); int IReadOnlyCollection.Count => Count; #endregion IList, ICollection } public class ItemChangedEventArgs where T : class { internal ItemChangedEventArgs(T item, TrackingState trackingState) { Item = item; TrackingState = trackingState; } public T Item { get; } public TrackingState TrackingState { get; } } }