2 using System.Diagnostics;
\r
3 using System.Collections.Generic;
\r
4 using Microsoft.Internal;
\r
5 using System.Diagnostics.CodeAnalysis;
\r
7 namespace System.ComponentModel.Composition.Hosting
\r
10 /// AtomicComposition provides lightweight atomicCompositional semantics to enable temporary
\r
11 /// state to be managed for a series of nested atomicCompositions. Each atomicComposition maintains
\r
12 /// queryable state along with a sequence of actions necessary to complete the state when
\r
13 /// the atomicComposition is no longer in danger of being rolled back. State is completed or
\r
14 /// rolled back when the atomicComposition is disposed, depending on the state of the
\r
15 /// CompleteOnDipose property which defaults to false. The using(...) pattern in C# is a
\r
16 /// convenient mechanism for defining atomicComposition scopes.
\r
18 /// The least obvious aspects of AtomicComposition deal with nesting.
\r
20 /// Firstly, no complete actions are actually performed until the outermost atomicComposition is
\r
21 /// completed. Completeting or rolling back nested atomicCompositions serves only to change which
\r
22 /// actions would be completed the outer atomicComposition.
\r
24 /// Secondly, state is added in the form of queries associated with an object key. The
\r
25 /// key represents a unique object the state is being held on behalf of. The quieries are
\r
26 /// accessed throught the Query methods which provide automatic chaining to execute queries
\r
27 /// across the target atomicComposition and its inner atomicComposition as appropriate.
\r
29 /// Lastly, when a nested atomicComposition is created for a given outer the outer atomicComposition is locked.
\r
30 /// It remains locked until the inner atomicComposition is disposed or completeed preventing the addition of
\r
31 /// state, actions or other inner atomicCompositions.
\r
33 public class AtomicComposition : IDisposable
\r
35 private readonly AtomicComposition _outerAtomicComposition;
\r
36 private KeyValuePair<object, object>[] _values;
\r
37 private int _valueCount = 0;
\r
38 private List<Action> _completeActionList;
\r
39 private List<Action> _revertActionList;
\r
40 private bool _isDisposed = false;
\r
41 private bool _isCompleted = false;
\r
42 private bool _containsInnerAtomicComposition = false;
\r
44 public AtomicComposition()
\r
49 public AtomicComposition(AtomicComposition outerAtomicComposition)
\r
51 // Lock the inner atomicComposition so that we can assume nothing changes except on
\r
52 // the innermost scope, and thereby optimize the query path
\r
53 if (outerAtomicComposition != null)
\r
55 this._outerAtomicComposition = outerAtomicComposition;
\r
56 this._outerAtomicComposition.ContainsInnerAtomicComposition = true;
\r
60 public void SetValue(object key, object value)
\r
63 ThrowIfCompleteed();
\r
64 ThrowIfContainsInnerAtomicComposition();
\r
66 Requires.NotNull(key, "key");
\r
68 SetValueInternal(key, value);
\r
71 public bool TryGetValue<T>(object key, out T value)
\r
73 return TryGetValue(key, false, out value);
\r
76 [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
\r
77 public bool TryGetValue<T>(object key, bool localAtomicCompositionOnly, out T value)
\r
80 ThrowIfCompleteed();
\r
82 Requires.NotNull(key, "key");
\r
84 return TryGetValueInternal(key, localAtomicCompositionOnly, out value);
\r
87 public void AddCompleteAction(Action completeAction)
\r
90 ThrowIfCompleteed();
\r
91 ThrowIfContainsInnerAtomicComposition();
\r
93 Requires.NotNull(completeAction, "completeAction");
\r
95 if (this._completeActionList == null)
\r
97 this._completeActionList = new List<Action>();
\r
99 this._completeActionList.Add(completeAction);
\r
102 public void AddRevertAction(Action revertAction)
\r
105 ThrowIfCompleteed();
\r
106 ThrowIfContainsInnerAtomicComposition();
\r
108 Requires.NotNull(revertAction, "revertAction");
\r
110 if (this._revertActionList == null)
\r
112 this._revertActionList = new List<Action>();
\r
114 this._revertActionList.Add(revertAction);
\r
117 public void Complete()
\r
120 ThrowIfCompleteed();
\r
122 if (this._outerAtomicComposition == null)
\r
123 { // Execute all the complete actions
\r
127 { // Copy the actions and state to the outer atomicComposition
\r
131 this._isCompleted = true;
\r
134 public void Dispose()
\r
137 GC.SuppressFinalize(this);
\r
140 protected virtual void Dispose(bool disposing)
\r
143 this._isDisposed = true;
\r
145 if (this._outerAtomicComposition != null)
\r
147 this._outerAtomicComposition.ContainsInnerAtomicComposition = false;
\r
150 // Revert is always immediate and involves forgetting information and
\r
151 // exceuting any appropriate revert actions
\r
152 if (!this._isCompleted)
\r
154 if (this._revertActionList != null)
\r
156 // Execute the revert actions in reverse order to ensure
\r
157 // everything incrementally rollsback its state.
\r
158 for (int i = this._revertActionList.Count - 1; i >= 0; i--)
\r
160 Action action = this._revertActionList[i];
\r
163 this._revertActionList = null;
\r
168 private void FinalComplete()
\r
170 // Completeting the outer most scope is easy, just execute all the actions
\r
171 if (this._completeActionList != null)
\r
173 foreach (Action action in this._completeActionList)
\r
177 this._completeActionList = null;
\r
181 private void CopyComplete()
\r
183 Assumes.NotNull(this._outerAtomicComposition);
\r
185 this._outerAtomicComposition.ContainsInnerAtomicComposition = false;
\r
187 // Inner scopes are much odder, because completeting them means coalescing them into the
\r
188 // outer scope - the complete or revert actions are deferred until the outermost scope completes
\r
189 // or any intermediate rolls back
\r
190 if (this._completeActionList != null)
\r
192 foreach (Action action in this._completeActionList)
\r
194 this._outerAtomicComposition.AddCompleteAction(action);
\r
198 if (this._revertActionList != null)
\r
200 foreach (Action action in this._revertActionList)
\r
202 this._outerAtomicComposition.AddRevertAction(action);
\r
206 // We can copy over existing atomicComposition entries because they're either already chained or
\r
207 // overwrite by design and can now be completed or rolled back together
\r
208 for (var index = 0; index < this._valueCount; index++)
\r
210 this._outerAtomicComposition.SetValueInternal(
\r
211 this._values[index].Key, this._values[index].Value);
\r
215 private bool ContainsInnerAtomicComposition
\r
219 if (value == true && this._containsInnerAtomicComposition == true)
\r
221 throw new InvalidOperationException(Strings.AtomicComposition_AlreadyNested);
\r
223 this._containsInnerAtomicComposition = value;
\r
227 private bool TryGetValueInternal<T>(object key, bool localAtomicCompositionOnly, out T value)
\r
229 for (var index = 0; index < this._valueCount; index++)
\r
231 if (this._values[index].Key == key)
\r
233 value = (T)this._values[index].Value;
\r
238 // If there's no atomicComposition available then recurse until we hit the outermost
\r
239 // scope, where upon we go ahead and return null
\r
240 if (!localAtomicCompositionOnly && this._outerAtomicComposition != null)
\r
242 return this._outerAtomicComposition.TryGetValueInternal<T>(key, localAtomicCompositionOnly, out value);
\r
245 value = default(T);
\r
249 private void SetValueInternal(object key, object value)
\r
251 // Handle overwrites quickly
\r
252 for (var index = 0; index < this._valueCount; index++)
\r
254 if (this._values[index].Key == key)
\r
256 this._values[index] = new KeyValuePair<object,object>(key, value);
\r
261 // Expand storage when needed
\r
262 if (this._values == null || this._valueCount == this._values.Length)
\r
264 var newQueries = new KeyValuePair<object, object>[this._valueCount == 0 ? 5 : this._valueCount * 2];
\r
265 if (this._values != null)
\r
267 Array.Copy(this._values, newQueries, this._valueCount);
\r
269 this._values = newQueries;
\r
272 // Store a new entry
\r
273 this._values[_valueCount] = new KeyValuePair<object, object>(key, value);
\r
274 this._valueCount++;
\r
278 [DebuggerStepThrough]
\r
279 private void ThrowIfContainsInnerAtomicComposition()
\r
281 if (this._containsInnerAtomicComposition)
\r
283 throw new InvalidOperationException(Strings.AtomicComposition_PartOfAnotherAtomicComposition);
\r
287 [DebuggerStepThrough]
\r
288 private void ThrowIfCompleteed()
\r
290 if (this._isCompleted)
\r
292 throw new InvalidOperationException(Strings.AtomicComposition_AlreadyCompleted);
\r
296 [DebuggerStepThrough]
\r
297 private void ThrowIfDisposed()
\r
299 if (this._isDisposed)
\r
301 throw ExceptionBuilder.CreateObjectDisposed(this);
\r