2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / dlr / Runtime / Microsoft.Scripting.Core / Actions / ExpandoObject.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. \r
4  *\r
5  * This source code is subject to terms and conditions of the Microsoft Public License. A \r
6  * copy of the license can be found in the License.html file at the root of this distribution. If \r
7  * you cannot locate the  Microsoft Public License, please send an email to \r
8  * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound \r
9  * by the terms of the Microsoft Public License.\r
10  *\r
11  * You must not remove this notice, or any other, from this software.\r
12  *\r
13  *\r
14  * ***************************************************************************/\r
15 using System; using Microsoft;\r
16 \r
17 \r
18 using System.Collections.Generic;\r
19 using System.ComponentModel;\r
20 using System.Diagnostics;\r
21 #if CODEPLEX_40\r
22 using System.Dynamic;\r
23 using System.Dynamic.Utils;\r
24 using System.Linq.Expressions;\r
25 #else\r
26 using Microsoft.Scripting;\r
27 using Microsoft.Scripting.Utils;\r
28 using Microsoft.Linq.Expressions;\r
29 #endif\r
30 using System.Runtime.CompilerServices;\r
31 #if !CODEPLEX_40
32 using Microsoft.Runtime.CompilerServices;
33 #endif
34 \r
35 \r
36 #if CODEPLEX_40\r
37 namespace System.Dynamic {\r
38 #else\r
39 namespace Microsoft.Scripting {\r
40 #endif\r
41     /// <summary>\r
42     /// Represents an object with members that can be dynamically added and removed at runtime.\r
43     /// </summary>\r
44     [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]\r
45     public sealed class ExpandoObject : IDynamicMetaObjectProvider, IDictionary<string, object>, INotifyPropertyChanged {\r
46         internal readonly object LockObject;                          // the readonly field is used for locking the Expando object\r
47         private ExpandoData _data;                                    // the data currently being held by the Expando object\r
48         private int _count;                                           // the count of available members\r
49 \r
50         internal readonly static object Uninitialized = new object(); // A marker object used to identify that a value is uninitialized.\r
51 \r
52         internal const int AmbiguousMatchFound = -2;        // The value is used to indicate there exists ambiguous match in the Expando object\r
53         internal const int NoMatch = -1;                    // The value is used to indicate there is no matching member\r
54 \r
55         private PropertyChangedEventHandler _propertyChanged;\r
56 \r
57         /// <summary>\r
58         /// Creates a new ExpandoObject with no members.\r
59         /// </summary>\r
60         public ExpandoObject() {\r
61             _data = ExpandoData.Empty;\r
62             LockObject = new object();\r
63         }\r
64 \r
65         #region Get/Set/Delete Helpers\r
66 \r
67         /// <summary>\r
68         /// Try to get the data stored for the specified class at the specified index.  If the\r
69         /// class has changed a full lookup for the slot will be performed and the correct\r
70         /// value will be retrieved.\r
71         /// </summary>\r
72         internal bool TryGetValue(object indexClass, int index, string name, bool ignoreCase, out object value) {\r
73             // read the data now.  The data is immutable so we get a consistent view.\r
74             // If there's a concurrent writer they will replace data and it just appears\r
75             // that we won the race\r
76             ExpandoData data = _data;\r
77             if (data.Class != indexClass || ignoreCase) {\r
78                 /* Re-search for the index matching the name here if\r
79                  *  1) the class has changed, we need to get the correct index and return\r
80                  *  the value there.\r
81                  *  2) the search is case insensitive:\r
82                  *      a. the member specified by index may be deleted, but there might be other\r
83                  *      members matching the name if the binder is case insensitive.\r
84                  *      b. the member that exactly matches the name didn't exist before and exists now,\r
85                  *      need to find the exact match.\r
86                  */\r
87                 index = data.Class.GetValueIndex(name, ignoreCase, this);\r
88                 if (index == ExpandoObject.AmbiguousMatchFound) {\r
89                     throw Error.AmbiguousMatchInExpandoObject(name);\r
90                 }\r
91             }\r
92 \r
93             if (index == ExpandoObject.NoMatch) {\r
94                 value = null;\r
95                 return false;\r
96             }\r
97 \r
98             // Capture the value into a temp, so it doesn't get mutated after we check\r
99             // for Uninitialized.\r
100             object temp = data[index];\r
101             if (temp == Uninitialized) {\r
102                 value = null;\r
103                 return false;\r
104             }\r
105 \r
106             // index is now known to be correct\r
107             value = temp;\r
108             return true;\r
109         }\r
110         \r
111         /// <summary>\r
112         /// Sets the data for the specified class at the specified index.  If the class has\r
113         /// changed then a full look for the slot will be performed.  If the new class does\r
114         /// not have the provided slot then the Expando's class will change. Only case sensitive\r
115         /// setter is supported in ExpandoObject.\r
116         /// </summary>\r
117         internal void TrySetValue(object indexClass, int index, object value, string name, bool ignoreCase, bool add) {\r
118             ExpandoData data;\r
119             object oldValue;\r
120 \r
121             lock (LockObject) {\r
122                 data = _data;\r
123 \r
124                 if (data.Class != indexClass || ignoreCase) {\r
125                     // The class has changed or we are doing a case-insensitive search, \r
126                     // we need to get the correct index and set the value there.  If we \r
127                     // don't have the value then we need to promote the class - that \r
128                     // should only happen when we have multiple concurrent writers.\r
129                     index = data.Class.GetValueIndex(name, ignoreCase, this);\r
130                     if (index == ExpandoObject.AmbiguousMatchFound) {\r
131                         throw Error.AmbiguousMatchInExpandoObject(name);\r
132                     }\r
133                     if (index == ExpandoObject.NoMatch) {\r
134                         // Before creating a new class with the new member, need to check \r
135                         // if there is the exact same member but is deleted. We should reuse\r
136                         // the class if there is such a member.\r
137                         int exactMatch = ignoreCase ?\r
138                             data.Class.GetValueIndexCaseSensitive(name) :\r
139                             index;\r
140                         if (exactMatch != ExpandoObject.NoMatch) {\r
141                             Debug.Assert(data[exactMatch] == Uninitialized);\r
142                             index = exactMatch;\r
143                         } else {\r
144                             ExpandoClass newClass = data.Class.FindNewClass(name);\r
145                             data = PromoteClassCore(data.Class, newClass);\r
146                             // After the class promotion, there must be an exact match,\r
147                             // so we can do case-sensitive search here.\r
148                             index = data.Class.GetValueIndexCaseSensitive(name);\r
149                             Debug.Assert(index != ExpandoObject.NoMatch);\r
150                         }\r
151                     }\r
152                 }\r
153 \r
154                 // Setting an uninitialized member increases the count of available members\r
155                 oldValue = data[index];\r
156                 if (oldValue == Uninitialized) {\r
157                     _count++;\r
158                 } else if (add) {\r
159                     throw Error.SameKeyExistsInExpando(name);\r
160                 }\r
161 \r
162                 data[index] = value;\r
163             }\r
164 \r
165             // Notify property changed, outside of the lock.\r
166             var propertyChanged = _propertyChanged;\r
167             if (propertyChanged != null && value != oldValue) {\r
168                 // Use the canonical case for the key.\r
169                 propertyChanged(this, new PropertyChangedEventArgs(data.Class.Keys[index]));\r
170             }\r
171         }\r
172 \r
173         /// <summary>\r
174         /// Deletes the data stored for the specified class at the specified index.\r
175         /// </summary>\r
176         internal bool TryDeleteValue(object indexClass, int index, string name, bool ignoreCase, object deleteValue) {\r
177             ExpandoData data;\r
178             lock (LockObject) {\r
179                 data = _data;\r
180 \r
181                 if (data.Class != indexClass || ignoreCase) {\r
182                     // the class has changed or we are doing a case-insensitive search,\r
183                     // we need to get the correct index.  If there is no associated index\r
184                     // we simply can't have the value and we return false.\r
185                     index = data.Class.GetValueIndex(name, ignoreCase, this);\r
186                     if (index == ExpandoObject.AmbiguousMatchFound) {\r
187                         throw Error.AmbiguousMatchInExpandoObject(name);\r
188                     }\r
189                 }\r
190                 if (index == ExpandoObject.NoMatch) {\r
191                     return false;\r
192                 }\r
193 \r
194                 object oldValue = data[index];\r
195                 if (oldValue == Uninitialized) {\r
196                     return false;\r
197                 }\r
198 \r
199                 // Make sure the value matches, if requested.\r
200                 //\r
201                 // It's a shame we have to call Equals with the lock held but\r
202                 // there doesn't seem to be a good way around that, and\r
203                 // ConcurrentDictionary in mscorlib does the same thing.\r
204                 if (deleteValue != Uninitialized && !object.Equals(oldValue, deleteValue)) {\r
205                     return false;\r
206                 }\r
207 \r
208                 data[index] = Uninitialized;\r
209 \r
210                 // Deleting an available member decreases the count of available members\r
211                 _count--;\r
212             }\r
213 \r
214             // Notify property changed, outside of the lock.\r
215             var propertyChanged = _propertyChanged;\r
216             if (propertyChanged != null) {\r
217                 // Use the canonical case for the key.\r
218                 propertyChanged(this, new PropertyChangedEventArgs(data.Class.Keys[index]));\r
219             }\r
220 \r
221             return true;\r
222         }\r
223 \r
224         /// <summary>\r
225         /// Returns true if the member at the specified index has been deleted,\r
226         /// otherwise false. Call this function holding the lock.\r
227         /// </summary>\r
228         internal bool IsDeletedMember(int index) {\r
229             Debug.Assert(index >= 0 && index <= _data.Length);\r
230 \r
231             if (index == _data.Length) {\r
232                 // The member is a newly added by SetMemberBinder and not in data yet\r
233                 return false;\r
234             }\r
235 \r
236             return _data[index] == ExpandoObject.Uninitialized;\r
237         }\r
238 \r
239         /// <summary>\r
240         /// Exposes the ExpandoClass which we've associated with this \r
241         /// Expando object.  Used for type checks in rules.\r
242         /// </summary>\r
243         internal ExpandoClass Class {\r
244             get {\r
245                 return _data.Class;\r
246             }\r
247         }\r
248 \r
249         /// <summary>\r
250         /// Promotes the class from the old type to the new type and returns the new\r
251         /// ExpandoData object.\r
252         /// </summary>\r
253         private ExpandoData PromoteClassCore(ExpandoClass oldClass, ExpandoClass newClass) {\r
254             Debug.Assert(oldClass != newClass);\r
255 \r
256             lock (LockObject) {\r
257                 if (_data.Class == oldClass) {\r
258                     _data = _data.UpdateClass(newClass);\r
259                 }\r
260                 return _data;\r
261             }\r
262         }\r
263 \r
264         /// <summary>\r
265         /// Internal helper to promote a class.  Called from our RuntimeOps helper.  This\r
266         /// version simply doesn't expose the ExpandoData object which is a private\r
267         /// data structure.\r
268         /// </summary>\r
269         internal void PromoteClass(object oldClass, object newClass) {\r
270             PromoteClassCore((ExpandoClass)oldClass, (ExpandoClass)newClass);\r
271         }\r
272 \r
273         #endregion\r
274 \r
275         #region IDynamicMetaObjectProvider Members\r
276 \r
277         DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {\r
278             return new MetaExpando(parameter, this);\r
279         }\r
280         #endregion\r
281 \r
282         #region Helper methods\r
283         private void TryAddMember(string key, object value) {\r
284             ContractUtils.RequiresNotNull(key, "key");\r
285             // Pass null to the class, which forces lookup.\r
286             TrySetValue(null, -1, value, key, false, true);\r
287         }\r
288 \r
289         private bool TryGetValueForKey(string key, out object value) {\r
290             // Pass null to the class, which forces lookup.\r
291             return TryGetValue(null, -1, key, false, out value);\r
292         }\r
293 \r
294         private bool ExpandoContainsKey(string key) {\r
295             return _data.Class.GetValueIndexCaseSensitive(key) >= 0;\r
296         }\r
297 \r
298         // We create a non-generic type for the debug view for each different collection type\r
299         // that uses DebuggerTypeProxy, instead of defining a generic debug view type and\r
300         // using different instantiations. The reason for this is that support for generics\r
301         // with using DebuggerTypeProxy is limited. For C#, DebuggerTypeProxy supports only\r
302         // open types (from MSDN http://msdn.microsoft.com/en-us/library/d8eyd8zc.aspx).\r
303         private sealed class KeyCollectionDebugView {\r
304             private ICollection<string> collection;\r
305             public KeyCollectionDebugView(ICollection<string> collection) {\r
306                 Debug.Assert(collection != null);\r
307                 this.collection = collection;\r
308             }\r
309 \r
310             [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]\r
311             public string[] Items {\r
312                 get {\r
313                     string[] items = new string[collection.Count];\r
314                     collection.CopyTo(items, 0);\r
315                     return items;\r
316                 }\r
317             }\r
318         }\r
319 \r
320         [DebuggerTypeProxy(typeof(KeyCollectionDebugView))]\r
321         [DebuggerDisplay("Count = {Count}")]\r
322         private class KeyCollection : ICollection<string> {\r
323             private readonly ExpandoObject _expando;\r
324             private readonly int _expandoVersion;\r
325             private readonly int _expandoCount;\r
326             private readonly ExpandoData _expandoData;\r
327 \r
328             internal KeyCollection(ExpandoObject expando) {\r
329                 lock (expando.LockObject) {\r
330                     _expando = expando;\r
331                     _expandoVersion = expando._data.Version;\r
332                     _expandoCount = expando._count;\r
333                     _expandoData = expando._data;\r
334                 }\r
335             }\r
336 \r
337             private void CheckVersion() {\r
338                 if (_expando._data.Version != _expandoVersion || _expandoData != _expando._data) {\r
339                     //the underlying expando object has changed\r
340                     throw Error.CollectionModifiedWhileEnumerating();\r
341                 }\r
342             }\r
343 \r
344             #region ICollection<string> Members\r
345 \r
346             public void Add(string item) {\r
347                 throw Error.CollectionReadOnly();\r
348             }\r
349 \r
350             public void Clear() {\r
351                 throw Error.CollectionReadOnly();\r
352             }\r
353 \r
354             public bool Contains(string item) {\r
355                 lock (_expando.LockObject) {\r
356                     CheckVersion();\r
357                     return _expando.ExpandoContainsKey(item);\r
358                 }\r
359             }\r
360 \r
361             public void CopyTo(string[] array, int arrayIndex) {\r
362                 ContractUtils.RequiresNotNull(array, "array");\r
363                 ContractUtils.RequiresArrayRange(array, arrayIndex, _expandoCount, "arrayIndex", "Count");\r
364                 lock (_expando.LockObject) {\r
365                     CheckVersion();\r
366                     ExpandoData data = _expando._data;\r
367                     for (int i = 0; i < data.Class.Keys.Length; i++) {\r
368                         if (data[i] != Uninitialized) {\r
369                             array[arrayIndex++] = data.Class.Keys[i];\r
370                         }\r
371                     }\r
372                 }\r
373             }\r
374 \r
375             public int Count {\r
376                 get {\r
377                     CheckVersion();\r
378                     return _expandoCount;\r
379                 }\r
380             }\r
381 \r
382             public bool IsReadOnly {\r
383                 get { return true; }\r
384             }\r
385 \r
386             public bool Remove(string item) {\r
387                 throw Error.CollectionReadOnly();\r
388             }\r
389 \r
390             #endregion\r
391 \r
392             #region IEnumerable<string> Members\r
393 \r
394             public IEnumerator<string> GetEnumerator() {\r
395                 for (int i = 0, n = _expandoData.Class.Keys.Length; i < n; i++) {\r
396                     CheckVersion();\r
397                     if (_expandoData[i] != Uninitialized) {\r
398                         yield return _expandoData.Class.Keys[i];\r
399                     }\r
400                 }\r
401             }\r
402 \r
403             #endregion\r
404 \r
405             #region IEnumerable Members\r
406 \r
407             System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {\r
408                 return GetEnumerator();\r
409             }\r
410 \r
411             #endregion\r
412         }\r
413 \r
414         // We create a non-generic type for the debug view for each different collection type\r
415         // that uses DebuggerTypeProxy, instead of defining a generic debug view type and\r
416         // using different instantiations. The reason for this is that support for generics\r
417         // with using DebuggerTypeProxy is limited. For C#, DebuggerTypeProxy supports only\r
418         // open types (from MSDN http://msdn.microsoft.com/en-us/library/d8eyd8zc.aspx).\r
419         private sealed class ValueCollectionDebugView {\r
420             private ICollection<object> collection;\r
421             public ValueCollectionDebugView(ICollection<object> collection) {\r
422                 Debug.Assert(collection != null);\r
423                 this.collection = collection;\r
424             }\r
425 \r
426             [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]\r
427             public object[] Items {\r
428                 get {\r
429                     object[] items = new object[collection.Count];\r
430                     collection.CopyTo(items, 0);\r
431                     return items;\r
432                 }\r
433             }\r
434         }\r
435 \r
436         [DebuggerTypeProxy(typeof(ValueCollectionDebugView))]\r
437         [DebuggerDisplay("Count = {Count}")]\r
438         private class ValueCollection : ICollection<object> {\r
439             private readonly ExpandoObject _expando;\r
440             private readonly int _expandoVersion;\r
441             private readonly int _expandoCount;\r
442             private readonly ExpandoData _expandoData;\r
443 \r
444             internal ValueCollection(ExpandoObject expando) {\r
445                 lock (expando.LockObject) {\r
446                     _expando = expando;\r
447                     _expandoVersion = expando._data.Version;\r
448                     _expandoCount = expando._count;\r
449                     _expandoData = expando._data;\r
450                 }\r
451             }\r
452 \r
453             private void CheckVersion() {\r
454                 if (_expando._data.Version != _expandoVersion || _expandoData != _expando._data) {\r
455                     //the underlying expando object has changed\r
456                     throw Error.CollectionModifiedWhileEnumerating();\r
457                 }\r
458             }\r
459 \r
460             #region ICollection<string> Members\r
461 \r
462             public void Add(object item) {\r
463                 throw Error.CollectionReadOnly();\r
464             }\r
465 \r
466             public void Clear() {\r
467                 throw Error.CollectionReadOnly();\r
468             }\r
469 \r
470             public bool Contains(object item) {\r
471                 lock (_expando.LockObject) {\r
472                     CheckVersion();\r
473 \r
474                     ExpandoData data = _expando._data;\r
475                     for (int i = 0; i < data.Class.Keys.Length; i++) {\r
476 \r
477                         // See comment in TryDeleteValue; it's okay to call\r
478                         // object.Equals with the lock held.\r
479                         if (object.Equals(data[i], item)) {\r
480                             return true;\r
481                         }\r
482                     }\r
483                     return false;\r
484                 }\r
485             }\r
486 \r
487             public void CopyTo(object[] array, int arrayIndex) {\r
488                 ContractUtils.RequiresNotNull(array, "array");\r
489                 ContractUtils.RequiresArrayRange(array, arrayIndex, _expandoCount, "arrayIndex", "Count");\r
490                 lock (_expando.LockObject) {\r
491                     CheckVersion();\r
492                     ExpandoData data = _expando._data;\r
493                     for (int i = 0; i < data.Class.Keys.Length; i++) {\r
494                         if (data[i] != Uninitialized) {\r
495                             array[arrayIndex++] = data[i];\r
496                         }\r
497                     }\r
498                 }\r
499             }\r
500 \r
501             public int Count {\r
502                 get {\r
503                     CheckVersion();\r
504                     return _expandoCount;\r
505                 }\r
506             }\r
507 \r
508             public bool IsReadOnly {\r
509                 get { return true; }\r
510             }\r
511 \r
512             public bool Remove(object item) {\r
513                 throw Error.CollectionReadOnly();\r
514             }\r
515 \r
516             #endregion\r
517 \r
518             #region IEnumerable<string> Members\r
519 \r
520             public IEnumerator<object> GetEnumerator() {\r
521                 ExpandoData data = _expando._data;\r
522                 for (int i = 0; i < data.Class.Keys.Length; i++) {\r
523                     CheckVersion();\r
524                     // Capture the value into a temp so we don't inadvertently\r
525                     // return Uninitialized.\r
526                     object temp = data[i];\r
527                     if (temp != Uninitialized) {\r
528                         yield return temp;\r
529                     }\r
530                 }\r
531             }\r
532 \r
533             #endregion\r
534 \r
535             #region IEnumerable Members\r
536 \r
537             System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {\r
538                 return GetEnumerator();\r
539             }\r
540 \r
541             #endregion\r
542         }\r
543 \r
544         #endregion\r
545 \r
546         #region IDictionary<string, object> Members\r
547         ICollection<string> IDictionary<string, object>.Keys {\r
548             get {\r
549                 return new KeyCollection(this);\r
550             }\r
551         }\r
552 \r
553         ICollection<object> IDictionary<string, object>.Values {\r
554             get {\r
555                 return new ValueCollection(this);\r
556             }\r
557         }\r
558 \r
559         object IDictionary<string, object>.this[string key] {\r
560             get {\r
561                 object value;\r
562                 if (!TryGetValueForKey(key, out value)) {\r
563                     throw Error.KeyDoesNotExistInExpando(key);\r
564                 }\r
565                 return value;\r
566             }\r
567             set {\r
568                 ContractUtils.RequiresNotNull(key, "key");\r
569                 // Pass null to the class, which forces lookup.\r
570                 TrySetValue(null, -1, value, key, false, false);\r
571             }\r
572         }\r
573 \r
574         void IDictionary<string, object>.Add(string key, object value) {\r
575             this.TryAddMember(key, value);\r
576         }\r
577 \r
578         bool IDictionary<string, object>.ContainsKey(string key) {\r
579             ContractUtils.RequiresNotNull(key, "key");\r
580 \r
581             ExpandoData data = _data;\r
582             int index = data.Class.GetValueIndexCaseSensitive(key);\r
583             return index >= 0 && data[index] != Uninitialized;\r
584         }\r
585 \r
586         bool IDictionary<string, object>.Remove(string key) {\r
587             ContractUtils.RequiresNotNull(key, "key");\r
588             // Pass null to the class, which forces lookup.\r
589             return TryDeleteValue(null, -1, key, false, Uninitialized);\r
590         }\r
591 \r
592         bool IDictionary<string, object>.TryGetValue(string key, out object value) {\r
593             return TryGetValueForKey(key, out value);\r
594         }\r
595 \r
596         #endregion\r
597 \r
598         #region ICollection<KeyValuePair<string, object>> Members\r
599         int ICollection<KeyValuePair<string, object>>.Count {\r
600             get {\r
601                 return _count;\r
602             }\r
603         }\r
604 \r
605         bool ICollection<KeyValuePair<string, object>>.IsReadOnly {\r
606             get { return false; }\r
607         }\r
608 \r
609         void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) {\r
610             TryAddMember(item.Key, item.Value);\r
611         }\r
612 \r
613         void ICollection<KeyValuePair<string, object>>.Clear() {\r
614             // We remove both class and data!\r
615             ExpandoData data;\r
616             lock (LockObject) {\r
617                 data = _data;\r
618                 _data = ExpandoData.Empty;\r
619                 _count = 0;\r
620             }\r
621 \r
622             // Notify property changed for all properties.\r
623             var propertyChanged = _propertyChanged;\r
624             if (propertyChanged != null) {\r
625                 for (int i = 0, n = data.Class.Keys.Length; i < n; i++) {\r
626                     if (data[i] != Uninitialized) {\r
627                         propertyChanged(this, new PropertyChangedEventArgs(data.Class.Keys[i]));\r
628                     }\r
629                 }\r
630             }\r
631         }\r
632 \r
633         bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) {\r
634             object value;\r
635             if (!TryGetValueForKey(item.Key, out value)) {\r
636                 return false;\r
637             }\r
638 \r
639             return object.Equals(value, item.Value);\r
640         }\r
641 \r
642         void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) {\r
643             ContractUtils.RequiresNotNull(array, "array");\r
644             ContractUtils.RequiresArrayRange(array, arrayIndex, _count, "arrayIndex", "Count");\r
645 \r
646             // We want this to be atomic and not throw\r
647             lock (LockObject) {\r
648                 foreach (KeyValuePair<string, object> item in this) {\r
649                     array[arrayIndex++] = item;\r
650                 }\r
651             }\r
652         }\r
653 \r
654         bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) {\r
655             return TryDeleteValue(null, -1, item.Key, false, item.Value);\r
656         }\r
657         #endregion\r
658 \r
659         #region IEnumerable<KeyValuePair<string, object>> Member\r
660 \r
661         IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() {\r
662             ExpandoData data = _data;\r
663             return GetExpandoEnumerator(data, data.Version);\r
664         }\r
665 \r
666         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {\r
667             ExpandoData data = _data;\r
668             return GetExpandoEnumerator(data, data.Version);\r
669         }\r
670 \r
671         // Note: takes the data and version as parameters so they will be\r
672         // captured before the first call to MoveNext().\r
673         private IEnumerator<KeyValuePair<string, object>> GetExpandoEnumerator(ExpandoData data, int version) {\r
674             for (int i = 0; i < data.Class.Keys.Length; i++) {\r
675                 if (_data.Version != version || data != _data) {\r
676                     // The underlying expando object has changed:\r
677                     // 1) the version of the expando data changed\r
678                     // 2) the data object is changed \r
679                     throw Error.CollectionModifiedWhileEnumerating();\r
680                 }\r
681                 // Capture the value into a temp so we don't inadvertently\r
682                 // return Uninitialized.\r
683                 object temp = data[i];\r
684                 if (temp != Uninitialized) {\r
685                     yield return new KeyValuePair<string,object>(data.Class.Keys[i], temp);\r
686                 }\r
687             }\r
688         }\r
689         #endregion\r
690 \r
691         #region MetaExpando\r
692 \r
693         private class MetaExpando : DynamicMetaObject {\r
694             public MetaExpando(Expression expression, ExpandoObject value)\r
695                 : base(expression, BindingRestrictions.Empty, value) {\r
696             }\r
697 \r
698             private DynamicMetaObject GetDynamicMetaObjectForMember(string name, bool ignoreCase, DynamicMetaObject fallback) {\r
699                 ExpandoClass klass = Value.Class;\r
700 \r
701                 //try to find the member, including the deleted members\r
702                 int index = klass.GetValueIndex(name, ignoreCase, Value);\r
703 \r
704                 ParameterExpression value = Expression.Parameter(typeof(object), "value");\r
705 \r
706                 Expression tryGetValue = Expression.Call(\r
707                     typeof(RuntimeOps).GetMethod("ExpandoTryGetValue"),\r
708                     GetLimitedSelf(),\r
709                     Expression.Constant(klass, typeof(object)),\r
710                     Expression.Constant(index),\r
711                     Expression.Constant(name),\r
712                     Expression.Constant(ignoreCase),\r
713                     value\r
714                 );\r
715 \r
716                 Expression memberValue = Expression.Block(\r
717                     new[] { value },\r
718                     Expression.Condition(\r
719                         tryGetValue,\r
720                         value,\r
721                         fallback.Expression,\r
722                         typeof(object)\r
723                     )\r
724                 );\r
725 \r
726                 return new DynamicMetaObject(memberValue, fallback.Restrictions);\r
727             }\r
728 \r
729             public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {\r
730                 ContractUtils.RequiresNotNull(binder, "binder");\r
731                 DynamicMetaObject memberValue = GetDynamicMetaObjectForMember(\r
732                     binder.Name, \r
733                     binder.IgnoreCase,\r
734                     binder.FallbackGetMember(this)\r
735                 );\r
736 \r
737                 return AddDynamicTestAndDefer(binder, Value.Class, null, memberValue);\r
738             }\r
739 \r
740             public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {\r
741                 ContractUtils.RequiresNotNull(binder, "binder");\r
742                 DynamicMetaObject memberValue = GetDynamicMetaObjectForMember(\r
743                     binder.Name, \r
744                     binder.IgnoreCase,\r
745                     binder.FallbackInvokeMember(this, args)\r
746                 );\r
747                 //invoke the member value using the language's binder\r
748                 return AddDynamicTestAndDefer(\r
749                     binder,\r
750                     Value.Class,\r
751                     null,\r
752                     binder.FallbackInvoke(memberValue, args, null)\r
753                 );\r
754             }\r
755 \r
756             public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {\r
757                 ContractUtils.RequiresNotNull(binder, "binder");\r
758                 ContractUtils.RequiresNotNull(value, "value");\r
759 \r
760                 ExpandoClass klass;\r
761                 int index;\r
762 \r
763                 ExpandoClass originalClass = GetClassEnsureIndex(binder.Name, binder.IgnoreCase, Value, out klass, out index);\r
764 \r
765                 return AddDynamicTestAndDefer(\r
766                     binder,\r
767                     klass,\r
768                     originalClass,\r
769                     new DynamicMetaObject(\r
770                         Expression.Call(\r
771                             typeof(RuntimeOps).GetMethod("ExpandoTrySetValue"),\r
772                             GetLimitedSelf(),\r
773                             Expression.Constant(klass, typeof(object)),\r
774                             Expression.Constant(index),\r
775                             Expression.Convert(value.Expression, typeof(object)),\r
776                             Expression.Constant(binder.Name),\r
777                             Expression.Constant(binder.IgnoreCase)\r
778                         ),\r
779                         BindingRestrictions.Empty\r
780                     )\r
781                 );\r
782             }\r
783 \r
784             public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) {\r
785                 ContractUtils.RequiresNotNull(binder, "binder");\r
786 \r
787                 int index = Value.Class.GetValueIndex(binder.Name, binder.IgnoreCase, Value);\r
788 \r
789                 Expression tryDelete = Expression.Call(\r
790                     typeof(RuntimeOps).GetMethod("ExpandoTryDeleteValue"),\r
791                     GetLimitedSelf(),\r
792                     Expression.Constant(Value.Class, typeof(object)),\r
793                     Expression.Constant(index),\r
794                     Expression.Constant(binder.Name),\r
795                     Expression.Constant(binder.IgnoreCase)\r
796                 );\r
797                 DynamicMetaObject fallback = binder.FallbackDeleteMember(this);\r
798 \r
799                 DynamicMetaObject target = new DynamicMetaObject(\r
800                     Expression.IfThen(Expression.Not(tryDelete), fallback.Expression),\r
801                     fallback.Restrictions\r
802                 );\r
803 \r
804                 return AddDynamicTestAndDefer(binder, Value.Class, null, target);\r
805             }\r
806 \r
807             public override IEnumerable<string> GetDynamicMemberNames() {\r
808                 var expandoData = Value._data;\r
809                 var klass = expandoData.Class;\r
810                 for (int i = 0; i < klass.Keys.Length; i++) {\r
811                     object val = expandoData[i];\r
812                     if (val != ExpandoObject.Uninitialized) {\r
813                         yield return klass.Keys[i];\r
814                     }\r
815                 }\r
816             }\r
817 \r
818             /// <summary>\r
819             /// Adds a dynamic test which checks if the version has changed.  The test is only necessary for\r
820             /// performance as the methods will do the correct thing if called with an incorrect version.\r
821             /// </summary>\r
822             private DynamicMetaObject AddDynamicTestAndDefer(DynamicMetaObjectBinder binder, ExpandoClass klass, ExpandoClass originalClass, DynamicMetaObject succeeds) {\r
823 \r
824                 Expression ifTestSucceeds = succeeds.Expression;\r
825                 if (originalClass != null) {\r
826                     // we are accessing a member which has not yet been defined on this class.\r
827                     // We force a class promotion after the type check.  If the class changes the \r
828                     // promotion will fail and the set/delete will do a full lookup using the new\r
829                     // class to discover the name.\r
830                     Debug.Assert(originalClass != klass);\r
831 \r
832                     ifTestSucceeds = Expression.Block(\r
833                         Expression.Call(\r
834                             null,\r
835                             typeof(RuntimeOps).GetMethod("ExpandoPromoteClass"),\r
836                             GetLimitedSelf(),\r
837                             Expression.Constant(originalClass, typeof(object)),\r
838                             Expression.Constant(klass, typeof(object))\r
839                         ),\r
840                         succeeds.Expression\r
841                     );\r
842                 }\r
843 \r
844                 return new DynamicMetaObject(\r
845                     Expression.Condition(\r
846                         Expression.Call(\r
847                             null,\r
848                             typeof(RuntimeOps).GetMethod("ExpandoCheckVersion"),\r
849                             GetLimitedSelf(),\r
850                             Expression.Constant(originalClass ?? klass, typeof(object))\r
851                         ),\r
852                         ifTestSucceeds,\r
853                         binder.GetUpdateExpression(ifTestSucceeds.Type)\r
854                     ),\r
855                     GetRestrictions().Merge(succeeds.Restrictions)\r
856                 );\r
857             }\r
858 \r
859             /// <summary>\r
860             /// Gets the class and the index associated with the given name.  Does not update the expando object.  Instead\r
861             /// this returns both the original and desired new class.  A rule is created which includes the test for the\r
862             /// original class, the promotion to the new class, and the set/delete based on the class post-promotion.\r
863             /// </summary>\r
864             private ExpandoClass GetClassEnsureIndex(string name, bool caseInsensitive, ExpandoObject obj, out ExpandoClass klass, out int index) {\r
865                 ExpandoClass originalClass = Value.Class;\r
866 \r
867                 index = originalClass.GetValueIndex(name, caseInsensitive, obj) ;\r
868                 if (index == ExpandoObject.AmbiguousMatchFound) {\r
869                     klass = originalClass;\r
870                     return null;\r
871                 }\r
872                 if (index == ExpandoObject.NoMatch) {\r
873                     // go ahead and find a new class now...\r
874                     ExpandoClass newClass = originalClass.FindNewClass(name);\r
875 \r
876                     klass = newClass;\r
877                     index = newClass.GetValueIndexCaseSensitive(name);\r
878 \r
879                     Debug.Assert(index != ExpandoObject.NoMatch);\r
880                     return originalClass;\r
881                 } else {\r
882                     klass = originalClass;\r
883                     return null;\r
884                 }                \r
885             }\r
886 \r
887             /// <summary>\r
888             /// Returns our Expression converted to our known LimitType\r
889             /// </summary>\r
890             private Expression GetLimitedSelf() {\r
891                 if (TypeUtils.AreEquivalent(Expression.Type, LimitType)) {\r
892                     return Expression;\r
893                 }\r
894                 return Expression.Convert(Expression, LimitType);\r
895             }\r
896 \r
897             /// <summary>\r
898             /// Returns a Restrictions object which includes our current restrictions merged\r
899             /// with a restriction limiting our type\r
900             /// </summary>\r
901             private BindingRestrictions GetRestrictions() {\r
902                 Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty");\r
903 \r
904                 return BindingRestrictions.GetTypeRestriction(this);\r
905             }\r
906 \r
907             public new ExpandoObject Value {\r
908                 get {\r
909                     return (ExpandoObject)base.Value;\r
910                 }\r
911             }\r
912         }\r
913 \r
914         #endregion\r
915 \r
916         #region ExpandoData\r
917         \r
918         /// <summary>\r
919         /// Stores the class and the data associated with the class as one atomic\r
920         /// pair.  This enables us to do a class check in a thread safe manner w/o\r
921         /// requiring locks.\r
922         /// </summary>\r
923         private class ExpandoData {\r
924             internal static ExpandoData Empty = new ExpandoData();\r
925 \r
926             /// <summary>\r
927             /// the dynamically assigned class associated with the Expando object\r
928             /// </summary>\r
929             internal readonly ExpandoClass Class;\r
930 \r
931             /// <summary>\r
932             /// data stored in the expando object, key names are stored in the class.\r
933             /// \r
934             /// Expando._data must be locked when mutating the value.  Otherwise a copy of it \r
935             /// could be made and lose values.\r
936             /// </summary>\r
937             private readonly object[] _dataArray;\r
938 \r
939             /// <summary>\r
940             /// Indexer for getting/setting the data\r
941             /// </summary>\r
942             internal object this[int index] {\r
943                 get {\r
944                     return _dataArray[index];\r
945                 }\r
946                 set {\r
947                     //when the array is updated, version increases, even the new value is the same\r
948                     //as previous. Dictionary type has the same behavior.\r
949                     _version++;\r
950                     _dataArray[index] = value;\r
951                 }\r
952             }\r
953 \r
954             internal int Version {\r
955                 get { return _version; }\r
956             }\r
957 \r
958             internal int Length {\r
959                 get { return _dataArray.Length; }\r
960             }\r
961 \r
962             /// <summary>\r
963             /// Constructs an empty ExpandoData object with the empty class and no data.\r
964             /// </summary>\r
965             private ExpandoData() {\r
966                 Class = ExpandoClass.Empty;\r
967                 _dataArray = new object[0];\r
968             }\r
969 \r
970             /// <summary>\r
971             /// the version of the ExpandoObject that tracks set and delete operations\r
972             /// </summary>\r
973             private int _version;\r
974 \r
975             /// <summary>\r
976             /// Constructs a new ExpandoData object with the specified class and data.\r
977             /// </summary>\r
978             internal ExpandoData(ExpandoClass klass, object[] data, int version) {\r
979                 Class = klass;\r
980                 _dataArray = data;\r
981                 _version = version;\r
982             }\r
983 \r
984             /// <summary>\r
985             /// Update the associated class and increases the storage for the data array if needed.\r
986             /// </summary>\r
987             /// <returns></returns>\r
988             internal ExpandoData UpdateClass(ExpandoClass newClass) {\r
989                 if (_dataArray.Length >= newClass.Keys.Length) {\r
990                     // we have extra space in our buffer, just initialize it to Uninitialized.\r
991                     this[newClass.Keys.Length - 1] = ExpandoObject.Uninitialized;\r
992                     return new ExpandoData(newClass, this._dataArray, this._version);\r
993                 } else {\r
994                     // we've grown too much - we need a new object array\r
995                     int oldLength = _dataArray.Length;\r
996                     object[] arr = new object[GetAlignedSize(newClass.Keys.Length)];\r
997                     Array.Copy(_dataArray, arr, _dataArray.Length);\r
998                     ExpandoData newData = new ExpandoData(newClass, arr, this._version);\r
999                     newData[oldLength] = ExpandoObject.Uninitialized;\r
1000                     return newData;\r
1001                 }\r
1002             }\r
1003 \r
1004             private static int GetAlignedSize(int len) {\r
1005                 // the alignment of the array for storage of values (must be a power of two)\r
1006                 const int DataArrayAlignment = 8;\r
1007 \r
1008                 // round up and then mask off lower bits\r
1009                 return (len + (DataArrayAlignment - 1)) & (~(DataArrayAlignment - 1));\r
1010             }\r
1011         }\r
1012 \r
1013         #endregion            \r
1014     \r
1015         #region INotifyPropertyChanged Members\r
1016 \r
1017         event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged {\r
1018             add { _propertyChanged += value; }\r
1019             remove { _propertyChanged -= value; }\r
1020         }\r
1021 \r
1022         #endregion\r
1023     }\r
1024 }\r
1025 \r
1026 #if CODEPLEX_40\r
1027 namespace System.Runtime.CompilerServices {\r
1028 #else\r
1029 namespace Microsoft.Runtime.CompilerServices {\r
1030 #endif\r
1031 \r
1032     //\r
1033     // Note: these helpers are kept as simple wrappers so they have a better \r
1034     // chance of being inlined.\r
1035     //\r
1036     public static partial class RuntimeOps {\r
1037 \r
1038         /// <summary>\r
1039         /// Gets the value of an item in an expando object.\r
1040         /// </summary>\r
1041         /// <param name="expando">The expando object.</param>\r
1042         /// <param name="indexClass">The class of the expando object.</param>\r
1043         /// <param name="index">The index of the member.</param>\r
1044         /// <param name="name">The name of the member.</param>\r
1045         /// <param name="ignoreCase">true if the name should be matched ignoring case; false otherwise.</param>\r
1046         /// <param name="value">The out parameter containing the value of the member.</param>\r
1047         /// <returns>True if the member exists in the expando object, otherwise false.</returns>\r
1048         [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]\r
1049         public static bool ExpandoTryGetValue(ExpandoObject expando, object indexClass, int index, string name, bool ignoreCase, out object value) {\r
1050             return expando.TryGetValue(indexClass, index, name, ignoreCase, out value);\r
1051         }\r
1052 \r
1053         /// <summary>\r
1054         /// Sets the value of an item in an expando object.\r
1055         /// </summary>\r
1056         /// <param name="expando">The expando object.</param>\r
1057         /// <param name="indexClass">The class of the expando object.</param>\r
1058         /// <param name="index">The index of the member.</param>\r
1059         /// <param name="value">The value of the member.</param>\r
1060         /// <param name="name">The name of the member.</param>\r
1061         /// <param name="ignoreCase">true if the name should be matched ignoring case; false otherwise.</param>\r
1062         /// <returns>\r
1063         /// Returns the index for the set member.\r
1064         /// </returns>\r
1065         [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]\r
1066         public static object ExpandoTrySetValue(ExpandoObject expando, object indexClass, int index, object value, string name, bool ignoreCase) {\r
1067             expando.TrySetValue(indexClass, index, value, name, ignoreCase, false);\r
1068             return value;\r
1069         }\r
1070 \r
1071         /// <summary>\r
1072         /// Deletes the value of an item in an expando object.\r
1073         /// </summary>\r
1074         /// <param name="expando">The expando object.</param>\r
1075         /// <param name="indexClass">The class of the expando object.</param>\r
1076         /// <param name="index">The index of the member.</param>\r
1077         /// <param name="name">The name of the member.</param>\r
1078         /// <param name="ignoreCase">true if the name should be matched ignoring case; false otherwise.</param>\r
1079         /// <returns>true if the item was successfully removed; otherwise, false.</returns>\r
1080         [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]\r
1081         public static bool ExpandoTryDeleteValue(ExpandoObject expando, object indexClass, int index, string name, bool ignoreCase) {\r
1082             return expando.TryDeleteValue(indexClass, index, name, ignoreCase, ExpandoObject.Uninitialized);\r
1083         }\r
1084 \r
1085         /// <summary>\r
1086         /// Checks the version of the expando object.\r
1087         /// </summary>\r
1088         /// <param name="expando">The expando object.</param>\r
1089         /// <param name="version">The version to check.</param>\r
1090         /// <returns>true if the version is equal; otherwise, false.</returns>\r
1091         [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]\r
1092         public static bool ExpandoCheckVersion(ExpandoObject expando, object version) {\r
1093             return expando.Class == version;\r
1094         }\r
1095 \r
1096         /// <summary>\r
1097         /// Promotes an expando object from one class to a new class.\r
1098         /// </summary>\r
1099         /// <param name="expando">The expando object.</param>\r
1100         /// <param name="oldClass">The old class of the expando object.</param>\r
1101         /// <param name="newClass">The new class of the expando object.</param>\r
1102         [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]\r
1103         public static void ExpandoPromoteClass(ExpandoObject expando, object oldClass, object newClass) {\r
1104             expando.PromoteClass(oldClass, newClass);\r
1105         }\r
1106     }\r
1107 }\r
1108 \r