1
// -----------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 // -----------------------------------------------------------------------
5 using System
.Collections
.ObjectModel
;
6 using System
.Collections
.Generic
;
7 using System
.ComponentModel
.Composition
.Primitives
;
10 using System
.Threading
;
11 using System
.Diagnostics
;
12 using Microsoft
.Internal
;
13 using Microsoft
.Internal
.Collections
;
14 using System
.Collections
;
15 using Lock
= Microsoft
.Internal
.Lock
;
17 namespace System
.ComponentModel
.Composition
.Hosting
20 /// This class implements a threadsafe ICollection{T} of ComposablePartCatalog.
21 /// It is exposed as an ICollection(ComposablePartCatalog)
22 /// It is threadsafe, notifications are not marshalled using a SynchronizationContext.
25 internal class ComposablePartCatalogCollection
: ICollection
<ComposablePartCatalog
>, INotifyComposablePartCatalogChanged
, IDisposable
27 private readonly Lock _lock
= new Lock();
28 private Action
<ComposablePartCatalogChangeEventArgs
> _onChanged
;
29 private Action
<ComposablePartCatalogChangeEventArgs
> _onChanging
;
30 private List
<ComposablePartCatalog
> _catalogs
= new List
<ComposablePartCatalog
>();
31 private volatile bool _isCopyNeeded
= false;
32 private volatile bool _isDisposed
= false;
33 private bool _hasChanged
= false;
35 public ComposablePartCatalogCollection(
36 IEnumerable
<ComposablePartCatalog
> catalogs
,
37 Action
<ComposablePartCatalogChangeEventArgs
> onChanged
,
38 Action
<ComposablePartCatalogChangeEventArgs
> onChanging
)
40 catalogs
= catalogs
?? Enumerable
.Empty
<ComposablePartCatalog
>();
41 this._catalogs
= new List
<ComposablePartCatalog
>(catalogs
);
42 this._onChanged
= onChanged
;
43 this._onChanging
= onChanging
;
45 SubscribeToCatalogNotifications(catalogs
);
48 public void Add(ComposablePartCatalog item
)
50 Requires
.NotNull(item
, "item");
52 this.ThrowIfDisposed();
54 var addedParts
= new Lazy
<IEnumerable
<ComposablePartDefinition
>>(() => item
.ToArray(), LazyThreadSafetyMode
.PublicationOnly
);
56 using (var atomicComposition
= new AtomicComposition())
58 this.RaiseChangingEvent(addedParts
, null, atomicComposition
);
60 using (new WriteLock(this._lock
))
62 if (this._isCopyNeeded
)
64 this._catalogs
= new List
<ComposablePartCatalog
>(this._catalogs
);
65 this._isCopyNeeded
= false;
67 this._hasChanged
= true;
68 this._catalogs
.Add(item
);
71 this.SubscribeToCatalogNotifications(item
);
73 // Complete after the catalog changes are written
74 atomicComposition
.Complete();
77 this.RaiseChangedEvent(addedParts
, null);
81 /// Notify when the contents of the Catalog has changed.
83 public event EventHandler
<ComposablePartCatalogChangeEventArgs
> Changed
;
86 /// Notify when the contents of the Catalog has changing.
88 public event EventHandler
<ComposablePartCatalogChangeEventArgs
> Changing
;
92 this.ThrowIfDisposed();
94 // No action is required if we are already empty
95 ComposablePartCatalog
[] catalogs
= null;
96 using (new ReadLock(this._lock
))
98 if (this._catalogs
.Count
== 0)
102 catalogs
= this._catalogs
.ToArray();
105 //TODO-MT: This is pretty suspect - we can easily eliminate catalogs that aren't listed as being
106 // removed. Then again, the idea of trying to mutate the catalog on two threads at the same time is pretty
107 // suspect to begin with. When would that ever result in a meaningful composition?
109 // We are doing this outside of the lock, so it's possible that the catalog will continute propagating events from things
110 // we are about to unsubscribe from. Given the non-specificity of our event, in the worst case scenario we would simply fire
111 // unnecessary events.
113 var removedParts
= new Lazy
<IEnumerable
<ComposablePartDefinition
>>(() => catalogs
.SelectMany(catalog
=> catalog
).ToArray(), LazyThreadSafetyMode
.PublicationOnly
);
115 // Validate the changes before applying them
116 using (var atomicComposition
= new AtomicComposition())
118 this.RaiseChangingEvent(null, removedParts
, atomicComposition
);
119 this.UnsubscribeFromCatalogNotifications(catalogs
);
121 using (new WriteLock(this._lock
))
123 this._catalogs
= new List
<ComposablePartCatalog
>();
125 this._isCopyNeeded
= false;
126 this._hasChanged
= true;
129 // Complete after the catalog changes are written
130 atomicComposition
.Complete();
133 this.RaiseChangedEvent(null, removedParts
);
136 public bool Contains(ComposablePartCatalog item
)
138 Requires
.NotNull(item
, "item");
140 this.ThrowIfDisposed();
142 using (new ReadLock(this._lock
))
144 return this._catalogs
.Contains(item
);
148 public void CopyTo(ComposablePartCatalog
[] array
, int arrayIndex
)
150 this.ThrowIfDisposed();
152 using (new ReadLock(this._lock
))
154 this._catalogs
.CopyTo(array
, arrayIndex
);
162 this.ThrowIfDisposed();
164 using (new ReadLock(this._lock
))
166 return this._catalogs
.Count
;
171 public bool IsReadOnly
175 this.ThrowIfDisposed();
181 public bool Remove(ComposablePartCatalog item
)
183 Requires
.NotNull(item
, "item");
185 this.ThrowIfDisposed();
187 using (new ReadLock(this._lock
))
189 if (!this._catalogs
.Contains(item
))
195 bool isSuccessfulRemoval
= false;
197 var removedParts
= new Lazy
<IEnumerable
<ComposablePartDefinition
>>(() => item
.ToArray(), LazyThreadSafetyMode
.PublicationOnly
);
198 using (var atomicComposition
= new AtomicComposition())
200 this.RaiseChangingEvent(null, removedParts
, atomicComposition
);
202 using (new WriteLock(this._lock
))
206 this._catalogs
= new List
<ComposablePartCatalog
>(this._catalogs
);
207 this._isCopyNeeded
= false;
210 isSuccessfulRemoval
= this._catalogs
.Remove(item
);
211 if (isSuccessfulRemoval
)
213 this._hasChanged
= true;
217 this.UnsubscribeFromCatalogNotifications(item
);
219 // Complete after the catalog changes are written
220 atomicComposition
.Complete();
223 this.RaiseChangedEvent(null, removedParts
);
225 return isSuccessfulRemoval
;
228 internal bool HasChanged
232 this.ThrowIfDisposed();
234 using (new ReadLock(this._lock
))
236 return this._hasChanged
;
241 public IEnumerator
<ComposablePartCatalog
> GetEnumerator()
243 this.ThrowIfDisposed();
245 using (new WriteLock(this._lock
))
247 IEnumerator
<ComposablePartCatalog
> enumerator
= this._catalogs
.GetEnumerator();
248 this._isCopyNeeded
= true;
253 IEnumerator IEnumerable
.GetEnumerator()
255 return this.GetEnumerator();
258 public void Dispose()
261 GC
.SuppressFinalize(this);
264 protected virtual void Dispose(bool disposing
)
268 if (!this._isDisposed
)
270 bool disposeLock
= false;
271 IEnumerable
<ComposablePartCatalog
> catalogs
= null;
274 using (new WriteLock(this._lock
))
276 if (!this._isDisposed
)
280 catalogs
= this._catalogs
;
281 this._catalogs
= null;
283 this._isDisposed
= true;
289 if (catalogs
!= null)
291 this.UnsubscribeFromCatalogNotifications(catalogs
);
292 catalogs
.ForEach(catalog
=> catalog
.Dispose());
297 this._lock
.Dispose();
304 private void RaiseChangedEvent(
305 Lazy
<IEnumerable
<ComposablePartDefinition
>> addedDefinitions
,
306 Lazy
<IEnumerable
<ComposablePartDefinition
>> removedDefinitions
)
308 if (this._onChanged
== null || this.Changed
== null)
313 var added
= (addedDefinitions
== null ? Enumerable
.Empty
<ComposablePartDefinition
>() : addedDefinitions
.Value
);
314 var removed
= (removedDefinitions
== null ? Enumerable
.Empty
<ComposablePartDefinition
>() : removedDefinitions
.Value
);
316 this._onChanged
.Invoke(new ComposablePartCatalogChangeEventArgs(added
, removed
, null));
319 public void OnChanged(object sender
, ComposablePartCatalogChangeEventArgs e
)
321 var changedEvent
= this.Changed
;
322 if (changedEvent
!= null)
324 changedEvent(sender
, e
);
328 private void RaiseChangingEvent(
329 Lazy
<IEnumerable
<ComposablePartDefinition
>> addedDefinitions
,
330 Lazy
<IEnumerable
<ComposablePartDefinition
>> removedDefinitions
,
331 AtomicComposition atomicComposition
)
333 if (this._onChanging
== null || this.Changing
== null)
337 var added
= (addedDefinitions
== null ? Enumerable
.Empty
<ComposablePartDefinition
>() : addedDefinitions
.Value
);
338 var removed
= (removedDefinitions
== null ? Enumerable
.Empty
<ComposablePartDefinition
>() : removedDefinitions
.Value
);
340 this._onChanging
.Invoke(new ComposablePartCatalogChangeEventArgs(added
, removed
, atomicComposition
));
343 public void OnChanging(object sender
, ComposablePartCatalogChangeEventArgs e
)
345 var changingEvent
= this.Changing
;
346 if (changingEvent
!= null)
348 changingEvent(sender
, e
);
352 private void OnContainedCatalogChanged(object sender
, ComposablePartCatalogChangeEventArgs e
)
354 if (this._onChanged
== null || this.Changed
== null)
359 this._onChanged
.Invoke(e
);
362 private void OnContainedCatalogChanging(object sender
, ComposablePartCatalogChangeEventArgs e
)
364 if (this._onChanging
== null || this.Changing
== null)
369 this._onChanging
.Invoke(e
);
372 private void SubscribeToCatalogNotifications(ComposablePartCatalog catalog
)
374 INotifyComposablePartCatalogChanged notifyCatalog
= catalog
as INotifyComposablePartCatalogChanged
;
375 if (notifyCatalog
!= null)
377 notifyCatalog
.Changed
+= this.OnContainedCatalogChanged
;
378 notifyCatalog
.Changing
+= this.OnContainedCatalogChanging
;
382 private void SubscribeToCatalogNotifications(IEnumerable
<ComposablePartCatalog
> catalogs
)
384 foreach (var catalog
in catalogs
)
386 SubscribeToCatalogNotifications(catalog
);
390 private void UnsubscribeFromCatalogNotifications(ComposablePartCatalog catalog
)
392 INotifyComposablePartCatalogChanged notifyCatalog
= catalog
as INotifyComposablePartCatalogChanged
;
393 if (notifyCatalog
!= null)
395 notifyCatalog
.Changed
-= this.OnContainedCatalogChanged
;
396 notifyCatalog
.Changing
-= this.OnContainedCatalogChanging
;
400 private void UnsubscribeFromCatalogNotifications(IEnumerable
<ComposablePartCatalog
> catalogs
)
402 foreach (var catalog
in catalogs
)
404 UnsubscribeFromCatalogNotifications(catalog
);
408 private void ThrowIfDisposed()
410 if (this._isDisposed
)
412 throw ExceptionBuilder
.CreateObjectDisposed(this);