< Summary

Information
Class: MyNet.Observable.Behaviors.CollectionTrackingBehavior
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Behaviors/CollectionTrackingBehavior.cs
Tag: 323_28699572109
Line coverage
80%
Covered lines: 51
Uncovered lines: 12
Coverable lines: 63
Total lines: 171
Line coverage: 80.9%
Branch coverage
58%
Covered branches: 20
Total branches: 34
Branch coverage: 58.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Track(...)50%7666.66%
Untrack(...)50%2283.33%
TrackExistingCollections()75%4480%
TryTrackProperty(...)75%8881.81%
ScheduleDeferredInitialTrack()50%5462.5%
DeferredInitialTrack()50%2275%
OnCollectionChanged(...)50%6680%
DisposeManagedResources()50%2287.5%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Behaviors/CollectionTrackingBehavior.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="CollectionTrackingBehavior.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Collections.Specialized;
 10using System.Reflection;
 11using System.Threading;
 12using MyNet.Reflection;
 13
 14namespace MyNet.Observable.Behaviors;
 15
 16/// <summary>
 17/// Tracks changes on collections of an ObservableObject. This behavior allows you to track changes on collections that 
 18/// </summary>
 19public sealed class CollectionTrackingBehavior : SuspendableBehavior<ObservableObject>
 20{
 7221    private readonly HashSet<string> _trackedProperties = new(StringComparer.Ordinal);
 7222    private readonly HashSet<INotifyCollectionChanged> _trackedCollections = [];
 7223    private readonly Lock _gate = new();
 24    private int _initialTrackScheduled;
 25
 26    /// <summary>
 27    /// Initializes a new instance of the <see cref="CollectionTrackingBehavior"/> class, which tracks changes on collec
 28    /// </summary>
 29    /// <param name="owner">The ObservableObject whose collections will be tracked.</param>
 30    /// <exception cref="ArgumentNullException">Thrown if the owner parameter is null.</exception>
 31    public CollectionTrackingBehavior(ObservableObject owner)
 7232        : base(owner)
 33    {
 7234        TrackExistingCollections();
 7235        ScheduleDeferredInitialTrack();
 7236    }
 37
 38    /// <summary>
 39    /// Occurs when a tracked collection changes.
 40    /// </summary>
 41    public event EventHandler<NotifyCollectionChangedEventArgs>? CollectionChanged;
 42
 43    #region Tracking
 44
 45    /// <summary>
 46    /// Registers a collection manually.
 47    /// </summary>
 48    public void Track(INotifyCollectionChanged collection)
 49    {
 950        if (IsDisposed)
 051            return;
 52
 953        if (IsSuspended)
 054            return;
 55
 56        lock (_gate)
 57        {
 958            if (!_trackedCollections.Add(collection))
 059                return;
 60
 961            collection.CollectionChanged += OnCollectionChanged;
 962        }
 963    }
 64
 65    /// <summary>
 66    /// Unregisters a collection manually.
 67    /// </summary>
 68    public void Untrack(INotifyCollectionChanged collection)
 369    {
 70        lock (_gate)
 71        {
 372            if (!_trackedCollections.Remove(collection))
 073                return;
 74
 375            collection.CollectionChanged -= OnCollectionChanged;
 376        }
 377    }
 78
 79    /// <summary>
 80    /// Tracks observable collections exposed by readable owner properties that are already initialized.
 81    /// </summary>
 82    internal void TrackExistingCollections()
 83    {
 15084        if (IsDisposed)
 085            return;
 86
 413487        foreach (var property in Owner.GetType().GetPublicProperties())
 191788            TryTrackProperty(property);
 15089    }
 90
 91    private void TryTrackProperty(PropertyInfo property)
 92    {
 191793        if (!typeof(INotifyCollectionChanged).IsAssignableFrom(property.PropertyType))
 187894            return;
 95
 3996        if (_trackedProperties.Contains(property.Name))
 697            return;
 98
 3399        if (!PropertyValueAccess.TryGetValue(Owner, property, out var value))
 30100            return;
 101
 3102        if (value is not INotifyCollectionChanged collection)
 0103            return;
 104
 3105        _trackedProperties.Add(property.Name);
 3106        Track(collection);
 3107    }
 108
 109    private void ScheduleDeferredInitialTrack()
 110    {
 72111        if (Interlocked.Exchange(ref _initialTrackScheduled, 1) != 0)
 0112            return;
 113
 72114        var context = SynchronizationContext.Current;
 72115        if (context is not null)
 116        {
 72117            context.Post(static state => ((CollectionTrackingBehavior)state!).DeferredInitialTrack(), this);
 72118            return;
 119        }
 120
 0121        ThreadPool.QueueUserWorkItem(static state => ((CollectionTrackingBehavior)state!).DeferredInitialTrack(), this);
 0122    }
 123
 124    private void DeferredInitialTrack()
 125    {
 72126        if (IsDisposed)
 0127            return;
 128
 72129        TrackExistingCollections();
 72130    }
 131
 132    #endregion
 133
 134    #region Event handling
 135
 136    /// <summary>
 137    /// Handles the CollectionChanged event for tracked collections. This method is called when a collection that implem
 138    /// </summary>
 139    /// <param name="sender">The collection that changed.</param>
 140    /// <param name="e">The event arguments describing the change.</param>
 141    private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
 142    {
 6143        if (IsDisposed || IsSuspended)
 0144            return;
 145
 6146        TrackExistingCollections();
 6147        CollectionChanged?.Invoke(sender, e);
 6148    }
 149
 150    #endregion
 151
 152    #region IDisposable
 153
 154    /// <inheritdoc/>
 155    protected override void DisposeManagedResources()
 3156    {
 157        lock (_gate)
 158        {
 6159            foreach (var collection in _trackedCollections)
 0160                collection.CollectionChanged -= OnCollectionChanged;
 161
 3162            _trackedCollections.Clear();
 3163            _trackedProperties.Clear();
 3164        }
 165
 3166        base.DisposeManagedResources();
 3167    }
 168
 169    #endregion
 170}
 171