@@ -16,6 +16,23 @@ public class TrackedCollectionWrapper<T, TCollection>
1616{
1717 protected TCollection InternalSource ;
1818
19+ /// <summary>
20+ /// Event fired after a chnage or group of changes has been made.
21+ /// </summary>
22+ public event EventHandler ? Modified ;
23+
24+ /// <summary>
25+ /// Event fired after an item has been added or removed from the collection.
26+ /// </summary>
27+ public event EventHandler < ItemChangedEventArgs < T > > ? Changed ;
28+
29+ protected bool HasChangedListeners => Changed is not null ;
30+
31+ /// <summary>
32+ /// Event fired after the collection has been cleared.
33+ /// </summary>
34+ public event EventHandler < int > ? Cleared ;
35+
1936 [ ExcludeFromCodeCoverage ]
2037 public TrackedCollectionWrapper ( TCollection collection , ModificationSynchronizer ? sync = null )
2138 : base ( sync ) => InternalSource = collection ?? throw new ArgumentNullException ( nameof ( collection ) ) ;
@@ -36,10 +53,13 @@ protected override void OnDispose()
3653 {
3754 base . OnDispose ( ) ;
3855 Nullify ( ref InternalSource ) ; // Eliminate risk from wrapper.
56+ Modified = null ;
57+ Changed = null ;
58+ Cleared = null ;
3959 }
4060
4161 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
42- private bool AssertIsAliveCore ( ) => AssertIsAlive ( ) ;
62+ protected bool AssertIsAlive ( ) => base . AssertIsAlive ( ) ;
4363
4464 /// <inheritdoc />
4565 public int Count
@@ -53,24 +73,60 @@ public int Count
5373 protected virtual void AddInternal ( T item )
5474 => InternalSource . Add ( item ) ;
5575
76+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
77+ protected override void OnModified ( )
78+ => Modified ? . Invoke ( this , EventArgs . Empty ) ;
79+
80+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
81+ protected void OnChanged ( ItemChange change , T item , int version )
82+ => Changed ? . Invoke ( this , change . CreateArgs ( item , version ) ) ;
83+
84+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
85+ protected virtual void OnChanged < TIndex > ( ItemChange change , TIndex index , T item , int version )
86+ => Changed ? . Invoke ( this , change . CreateArgs ( index , item , version ) ) ;
87+
88+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
89+ private void OnAdded ( T item , int version )
90+ => OnChanged ( ItemChange . Added , item , version ) ;
91+
92+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
93+ private void OnRemoved ( T item , int version )
94+ => OnChanged ( ItemChange . Removed , item , version ) ;
95+
96+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
97+ protected virtual void OnCleared ( int version )
98+ => Cleared ? . Invoke ( this , version ) ;
99+
56100 /// <inheritdoc />
57101 public void Add ( T item )
58- => Sync ! . Modifying ( AssertIsAliveCore , ( ) =>
59- {
60- AddInternal ( item ) ;
61- return true ;
62- } ) ;
63-
64- /// <inheritdoc cref="IAddMultiple{T}.Add(T, T, T[])" />
65- public void Add ( T item1 , T item2 , params T [ ] items )
66- => Sync ! . Modifying ( AssertIsAliveCore , ( ) =>
67- {
68- AddInternal ( item1 ) ;
69- AddInternal ( item2 ) ;
70- foreach ( T ? i in items )
71- AddInternal ( i ) ;
72- return true ;
73- } ) ;
102+ => Sync ! . Modifying (
103+ AssertIsAlive ,
104+ ( ) =>
105+ {
106+ AddInternal ( item ) ;
107+ return true ;
108+ } ,
109+ version => OnAdded ( item , version ) ) ;
110+
111+ /// <inheritdoc cref="IAddMultiple{T}.AddThese(T, T, T[])" />
112+ public void AddThese ( T item1 , T item2 , params T [ ] items )
113+ => Sync ! . Modifying ( AssertIsAlive ,
114+ ( ) =>
115+ {
116+ AddInternal ( item1 ) ;
117+ AddInternal ( item2 ) ;
118+ foreach ( T ? i in items )
119+ AddInternal ( i ) ;
120+ return true ;
121+ } ,
122+ version =>
123+ {
124+ if ( Changed is null ) return ;
125+ OnAdded ( item1 , version ) ;
126+ OnAdded ( item2 , version ) ;
127+ foreach ( T ? i in items )
128+ OnAdded ( i , version ) ;
129+ } ) ;
74130
75131 /// <inheritdoc />
76132 public void AddRange ( IEnumerable < T > items )
@@ -87,11 +143,16 @@ public void AddRange(IEnumerable<T> items)
87143 if ( enumerable . Count == 0 )
88144 return ;
89145
90- Sync ! . Modifying ( AssertIsAliveCore , ( ) =>
146+ Sync ! . Modifying ( AssertIsAlive , ( ) =>
91147 {
92148 foreach ( var item in enumerable )
93149 AddInternal ( item ) ;
94150 return true ;
151+ } ,
152+ version =>
153+ {
154+ foreach ( var item in enumerable )
155+ OnAdded ( item , version ) ;
95156 } ) ;
96157 }
97158
@@ -109,7 +170,7 @@ public void Clear()
109170 bool hasItems = count != 0 ;
110171 if ( hasItems ) ClearInternal ( ) ;
111172 return hasItems ;
112- } ) ;
173+ } , OnCleared ) ;
113174
114175 /// <inheritdoc />
115176 public bool Contains ( T item )
@@ -126,20 +187,13 @@ public void CopyTo(T[] array, int arrayIndex)
126187 /// <inheritdoc />
127188 public virtual bool Remove ( T item )
128189 => Sync ! . Modifying (
129- AssertIsAliveCore ,
130- ( ) => InternalSource . Remove ( item ) ) ;
131-
132- [ ExcludeFromCodeCoverage ]
133- protected virtual IEnumerator < T > GetEnumeratorInternal ( )
134- => InternalSource . GetEnumerator ( ) ;
190+ AssertIsAlive ,
191+ ( ) => InternalSource . Remove ( item ) ,
192+ version => OnRemoved ( item , version ) ) ;
135193
136194 /// <inheritdoc />
137195 public IEnumerator < T > GetEnumerator ( )
138- => Sync ! . Reading ( ( ) =>
139- {
140- AssertIsAlive ( ) ;
141- return GetEnumeratorInternal ( ) ;
142- } ) ;
196+ => InternalSource . GetEnumerator ( ) ;
143197
144198 [ ExcludeFromCodeCoverage ]
145199 IEnumerator IEnumerable . GetEnumerator ( ) => GetEnumerator ( ) ;
0 commit comments