1 /* ****************************************************************************
\r
3 * Copyright (c) Microsoft Corporation.
\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
11 * You must not remove this notice, or any other, from this software.
\r
14 * ***************************************************************************/
\r
15 using System; using Microsoft;
\r
18 using System.Collections.Generic;
\r
19 using System.ComponentModel;
\r
20 using System.Diagnostics;
\r
22 using System.Dynamic;
\r
23 using System.Dynamic.Utils;
\r
24 using System.Linq.Expressions;
\r
26 using Microsoft.Scripting;
\r
27 using Microsoft.Scripting.Utils;
\r
28 using Microsoft.Linq.Expressions;
\r
30 using System.Runtime.CompilerServices;
\r
32 using Microsoft.Runtime.CompilerServices;
37 namespace System.Dynamic {
\r
39 namespace Microsoft.Scripting {
\r
42 /// Represents an object with members that can be dynamically added and removed at runtime.
\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
50 internal readonly static object Uninitialized = new object(); // A marker object used to identify that a value is uninitialized.
\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
55 private PropertyChangedEventHandler _propertyChanged;
\r
58 /// Creates a new ExpandoObject with no members.
\r
60 public ExpandoObject() {
\r
61 _data = ExpandoData.Empty;
\r
62 LockObject = new object();
\r
65 #region Get/Set/Delete Helpers
\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
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
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
87 index = data.Class.GetValueIndex(name, ignoreCase, this);
\r
88 if (index == ExpandoObject.AmbiguousMatchFound) {
\r
89 throw Error.AmbiguousMatchInExpandoObject(name);
\r
93 if (index == ExpandoObject.NoMatch) {
\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
106 // index is now known to be correct
\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
117 internal void TrySetValue(object indexClass, int index, object value, string name, bool ignoreCase, bool add) {
\r
121 lock (LockObject) {
\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
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
140 if (exactMatch != ExpandoObject.NoMatch) {
\r
141 Debug.Assert(data[exactMatch] == Uninitialized);
\r
142 index = exactMatch;
\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
154 // Setting an uninitialized member increases the count of available members
\r
155 oldValue = data[index];
\r
156 if (oldValue == Uninitialized) {
\r
159 throw Error.SameKeyExistsInExpando(name);
\r
162 data[index] = value;
\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
174 /// Deletes the data stored for the specified class at the specified index.
\r
176 internal bool TryDeleteValue(object indexClass, int index, string name, bool ignoreCase, object deleteValue) {
\r
178 lock (LockObject) {
\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
190 if (index == ExpandoObject.NoMatch) {
\r
194 object oldValue = data[index];
\r
195 if (oldValue == Uninitialized) {
\r
199 // Make sure the value matches, if requested.
\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
208 data[index] = Uninitialized;
\r
210 // Deleting an available member decreases the count of available members
\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
225 /// Returns true if the member at the specified index has been deleted,
\r
226 /// otherwise false. Call this function holding the lock.
\r
228 internal bool IsDeletedMember(int index) {
\r
229 Debug.Assert(index >= 0 && index <= _data.Length);
\r
231 if (index == _data.Length) {
\r
232 // The member is a newly added by SetMemberBinder and not in data yet
\r
236 return _data[index] == ExpandoObject.Uninitialized;
\r
240 /// Exposes the ExpandoClass which we've associated with this
\r
241 /// Expando object. Used for type checks in rules.
\r
243 internal ExpandoClass Class {
\r
245 return _data.Class;
\r
250 /// Promotes the class from the old type to the new type and returns the new
\r
251 /// ExpandoData object.
\r
253 private ExpandoData PromoteClassCore(ExpandoClass oldClass, ExpandoClass newClass) {
\r
254 Debug.Assert(oldClass != newClass);
\r
256 lock (LockObject) {
\r
257 if (_data.Class == oldClass) {
\r
258 _data = _data.UpdateClass(newClass);
\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
269 internal void PromoteClass(object oldClass, object newClass) {
\r
270 PromoteClassCore((ExpandoClass)oldClass, (ExpandoClass)newClass);
\r
275 #region IDynamicMetaObjectProvider Members
\r
277 DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
\r
278 return new MetaExpando(parameter, this);
\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
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
294 private bool ExpandoContainsKey(string key) {
\r
295 return _data.Class.GetValueIndexCaseSensitive(key) >= 0;
\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
310 [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
\r
311 public string[] Items {
\r
313 string[] items = new string[collection.Count];
\r
314 collection.CopyTo(items, 0);
\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
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
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
344 #region ICollection<string> Members
\r
346 public void Add(string item) {
\r
347 throw Error.CollectionReadOnly();
\r
350 public void Clear() {
\r
351 throw Error.CollectionReadOnly();
\r
354 public bool Contains(string item) {
\r
355 lock (_expando.LockObject) {
\r
357 return _expando.ExpandoContainsKey(item);
\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
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
378 return _expandoCount;
\r
382 public bool IsReadOnly {
\r
383 get { return true; }
\r
386 public bool Remove(string item) {
\r
387 throw Error.CollectionReadOnly();
\r
392 #region IEnumerable<string> Members
\r
394 public IEnumerator<string> GetEnumerator() {
\r
395 for (int i = 0, n = _expandoData.Class.Keys.Length; i < n; i++) {
\r
397 if (_expandoData[i] != Uninitialized) {
\r
398 yield return _expandoData.Class.Keys[i];
\r
405 #region IEnumerable Members
\r
407 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
\r
408 return GetEnumerator();
\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
426 [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
\r
427 public object[] Items {
\r
429 object[] items = new object[collection.Count];
\r
430 collection.CopyTo(items, 0);
\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
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
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
460 #region ICollection<string> Members
\r
462 public void Add(object item) {
\r
463 throw Error.CollectionReadOnly();
\r
466 public void Clear() {
\r
467 throw Error.CollectionReadOnly();
\r
470 public bool Contains(object item) {
\r
471 lock (_expando.LockObject) {
\r
474 ExpandoData data = _expando._data;
\r
475 for (int i = 0; i < data.Class.Keys.Length; i++) {
\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
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
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
504 return _expandoCount;
\r
508 public bool IsReadOnly {
\r
509 get { return true; }
\r
512 public bool Remove(object item) {
\r
513 throw Error.CollectionReadOnly();
\r
518 #region IEnumerable<string> Members
\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
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
535 #region IEnumerable Members
\r
537 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
\r
538 return GetEnumerator();
\r
546 #region IDictionary<string, object> Members
\r
547 ICollection<string> IDictionary<string, object>.Keys {
\r
549 return new KeyCollection(this);
\r
553 ICollection<object> IDictionary<string, object>.Values {
\r
555 return new ValueCollection(this);
\r
559 object IDictionary<string, object>.this[string key] {
\r
562 if (!TryGetValueForKey(key, out value)) {
\r
563 throw Error.KeyDoesNotExistInExpando(key);
\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
574 void IDictionary<string, object>.Add(string key, object value) {
\r
575 this.TryAddMember(key, value);
\r
578 bool IDictionary<string, object>.ContainsKey(string key) {
\r
579 ContractUtils.RequiresNotNull(key, "key");
\r
581 ExpandoData data = _data;
\r
582 int index = data.Class.GetValueIndexCaseSensitive(key);
\r
583 return index >= 0 && data[index] != Uninitialized;
\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
592 bool IDictionary<string, object>.TryGetValue(string key, out object value) {
\r
593 return TryGetValueForKey(key, out value);
\r
598 #region ICollection<KeyValuePair<string, object>> Members
\r
599 int ICollection<KeyValuePair<string, object>>.Count {
\r
605 bool ICollection<KeyValuePair<string, object>>.IsReadOnly {
\r
606 get { return false; }
\r
609 void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) {
\r
610 TryAddMember(item.Key, item.Value);
\r
613 void ICollection<KeyValuePair<string, object>>.Clear() {
\r
614 // We remove both class and data!
\r
616 lock (LockObject) {
\r
618 _data = ExpandoData.Empty;
\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
633 bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) {
\r
635 if (!TryGetValueForKey(item.Key, out value)) {
\r
639 return object.Equals(value, item.Value);
\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
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
654 bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) {
\r
655 return TryDeleteValue(null, -1, item.Key, false, item.Value);
\r
659 #region IEnumerable<KeyValuePair<string, object>> Member
\r
661 IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() {
\r
662 ExpandoData data = _data;
\r
663 return GetExpandoEnumerator(data, data.Version);
\r
666 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
\r
667 ExpandoData data = _data;
\r
668 return GetExpandoEnumerator(data, data.Version);
\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
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
691 #region MetaExpando
\r
693 private class MetaExpando : DynamicMetaObject {
\r
694 public MetaExpando(Expression expression, ExpandoObject value)
\r
695 : base(expression, BindingRestrictions.Empty, value) {
\r
698 private DynamicMetaObject GetDynamicMetaObjectForMember(string name, bool ignoreCase, DynamicMetaObject fallback) {
\r
699 ExpandoClass klass = Value.Class;
\r
701 //try to find the member, including the deleted members
\r
702 int index = klass.GetValueIndex(name, ignoreCase, Value);
\r
704 ParameterExpression value = Expression.Parameter(typeof(object), "value");
\r
706 Expression tryGetValue = Expression.Call(
\r
707 typeof(RuntimeOps).GetMethod("ExpandoTryGetValue"),
\r
709 Expression.Constant(klass, typeof(object)),
\r
710 Expression.Constant(index),
\r
711 Expression.Constant(name),
\r
712 Expression.Constant(ignoreCase),
\r
716 Expression memberValue = Expression.Block(
\r
718 Expression.Condition(
\r
721 fallback.Expression,
\r
726 return new DynamicMetaObject(memberValue, fallback.Restrictions);
\r
729 public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
\r
730 ContractUtils.RequiresNotNull(binder, "binder");
\r
731 DynamicMetaObject memberValue = GetDynamicMetaObjectForMember(
\r
734 binder.FallbackGetMember(this)
\r
737 return AddDynamicTestAndDefer(binder, Value.Class, null, memberValue);
\r
740 public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
\r
741 ContractUtils.RequiresNotNull(binder, "binder");
\r
742 DynamicMetaObject memberValue = GetDynamicMetaObjectForMember(
\r
745 binder.FallbackInvokeMember(this, args)
\r
747 //invoke the member value using the language's binder
\r
748 return AddDynamicTestAndDefer(
\r
752 binder.FallbackInvoke(memberValue, args, null)
\r
756 public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
\r
757 ContractUtils.RequiresNotNull(binder, "binder");
\r
758 ContractUtils.RequiresNotNull(value, "value");
\r
760 ExpandoClass klass;
\r
763 ExpandoClass originalClass = GetClassEnsureIndex(binder.Name, binder.IgnoreCase, Value, out klass, out index);
\r
765 return AddDynamicTestAndDefer(
\r
769 new DynamicMetaObject(
\r
771 typeof(RuntimeOps).GetMethod("ExpandoTrySetValue"),
\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
779 BindingRestrictions.Empty
\r
784 public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) {
\r
785 ContractUtils.RequiresNotNull(binder, "binder");
\r
787 int index = Value.Class.GetValueIndex(binder.Name, binder.IgnoreCase, Value);
\r
789 Expression tryDelete = Expression.Call(
\r
790 typeof(RuntimeOps).GetMethod("ExpandoTryDeleteValue"),
\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
797 DynamicMetaObject fallback = binder.FallbackDeleteMember(this);
\r
799 DynamicMetaObject target = new DynamicMetaObject(
\r
800 Expression.IfThen(Expression.Not(tryDelete), fallback.Expression),
\r
801 fallback.Restrictions
\r
804 return AddDynamicTestAndDefer(binder, Value.Class, null, target);
\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
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
822 private DynamicMetaObject AddDynamicTestAndDefer(DynamicMetaObjectBinder binder, ExpandoClass klass, ExpandoClass originalClass, DynamicMetaObject succeeds) {
\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
832 ifTestSucceeds = Expression.Block(
\r
835 typeof(RuntimeOps).GetMethod("ExpandoPromoteClass"),
\r
837 Expression.Constant(originalClass, typeof(object)),
\r
838 Expression.Constant(klass, typeof(object))
\r
840 succeeds.Expression
\r
844 return new DynamicMetaObject(
\r
845 Expression.Condition(
\r
848 typeof(RuntimeOps).GetMethod("ExpandoCheckVersion"),
\r
850 Expression.Constant(originalClass ?? klass, typeof(object))
\r
853 binder.GetUpdateExpression(ifTestSucceeds.Type)
\r
855 GetRestrictions().Merge(succeeds.Restrictions)
\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
864 private ExpandoClass GetClassEnsureIndex(string name, bool caseInsensitive, ExpandoObject obj, out ExpandoClass klass, out int index) {
\r
865 ExpandoClass originalClass = Value.Class;
\r
867 index = originalClass.GetValueIndex(name, caseInsensitive, obj) ;
\r
868 if (index == ExpandoObject.AmbiguousMatchFound) {
\r
869 klass = originalClass;
\r
872 if (index == ExpandoObject.NoMatch) {
\r
873 // go ahead and find a new class now...
\r
874 ExpandoClass newClass = originalClass.FindNewClass(name);
\r
877 index = newClass.GetValueIndexCaseSensitive(name);
\r
879 Debug.Assert(index != ExpandoObject.NoMatch);
\r
880 return originalClass;
\r
882 klass = originalClass;
\r
888 /// Returns our Expression converted to our known LimitType
\r
890 private Expression GetLimitedSelf() {
\r
891 if (TypeUtils.AreEquivalent(Expression.Type, LimitType)) {
\r
894 return Expression.Convert(Expression, LimitType);
\r
898 /// Returns a Restrictions object which includes our current restrictions merged
\r
899 /// with a restriction limiting our type
\r
901 private BindingRestrictions GetRestrictions() {
\r
902 Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty");
\r
904 return BindingRestrictions.GetTypeRestriction(this);
\r
907 public new ExpandoObject Value {
\r
909 return (ExpandoObject)base.Value;
\r
916 #region ExpandoData
\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
923 private class ExpandoData {
\r
924 internal static ExpandoData Empty = new ExpandoData();
\r
927 /// the dynamically assigned class associated with the Expando object
\r
929 internal readonly ExpandoClass Class;
\r
932 /// data stored in the expando object, key names are stored in the class.
\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
937 private readonly object[] _dataArray;
\r
940 /// Indexer for getting/setting the data
\r
942 internal object this[int index] {
\r
944 return _dataArray[index];
\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
950 _dataArray[index] = value;
\r
954 internal int Version {
\r
955 get { return _version; }
\r
958 internal int Length {
\r
959 get { return _dataArray.Length; }
\r
963 /// Constructs an empty ExpandoData object with the empty class and no data.
\r
965 private ExpandoData() {
\r
966 Class = ExpandoClass.Empty;
\r
967 _dataArray = new object[0];
\r
971 /// the version of the ExpandoObject that tracks set and delete operations
\r
973 private int _version;
\r
976 /// Constructs a new ExpandoData object with the specified class and data.
\r
978 internal ExpandoData(ExpandoClass klass, object[] data, int version) {
\r
981 _version = version;
\r
985 /// Update the associated class and increases the storage for the data array if needed.
\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
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
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
1008 // round up and then mask off lower bits
\r
1009 return (len + (DataArrayAlignment - 1)) & (~(DataArrayAlignment - 1));
\r
1015 #region INotifyPropertyChanged Members
\r
1017 event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged {
\r
1018 add { _propertyChanged += value; }
\r
1019 remove { _propertyChanged -= value; }
\r
1027 namespace System.Runtime.CompilerServices {
\r
1029 namespace Microsoft.Runtime.CompilerServices {
\r
1033 // Note: these helpers are kept as simple wrappers so they have a better
\r
1034 // chance of being inlined.
\r
1036 public static partial class RuntimeOps {
\r
1039 /// Gets the value of an item in an expando object.
\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
1054 /// Sets the value of an item in an expando object.
\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
1063 /// Returns the index for the set member.
\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
1072 /// Deletes the value of an item in an expando object.
\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
1086 /// Checks the version of the expando object.
\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
1097 /// Promotes an expando object from one class to a new class.
\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