ExpandoObject.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Core / Microsoft / Scripting / Actions / ExpandoObject.cs / 1305376 / ExpandoObject.cs

                            /* **************************************************************************** 
 *
 * Copyright (c) Microsoft Corporation.
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/ 

using System.Collections.Generic; 
using System.ComponentModel; 
using System.Diagnostics;
using System.Dynamic; 
using System.Dynamic.Utils;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
 
#if SILVERLIGHT
using System.Core; 
#endif 

namespace System.Dynamic { 
    /// 
    /// Represents an object with members that can be dynamically added and removed at runtime.
    /// 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] 
    public sealed class ExpandoObject : IDynamicMetaObjectProvider, IDictionary, INotifyPropertyChanged {
        internal readonly object LockObject;                          // the readonly field is used for locking the Expando object 
        private ExpandoData _data;                                    // the data currently being held by the Expando object 
        private int _count;                                           // the count of available members
 
        internal readonly static object Uninitialized = new object(); // A marker object used to identify that a value is uninitialized.

        internal const int AmbiguousMatchFound = -2;        // The value is used to indicate there exists ambiguous match in the Expando object
        internal const int NoMatch = -1;                    // The value is used to indicate there is no matching member 

        private PropertyChangedEventHandler _propertyChanged; 
 
        /// 
        /// Creates a new ExpandoObject with no members. 
        /// 
        public ExpandoObject() {
            _data = ExpandoData.Empty;
            LockObject = new object(); 
        }
 
        #region Get/Set/Delete Helpers 

        ///  
        /// Try to get the data stored for the specified class at the specified index.  If the
        /// class has changed a full lookup for the slot will be performed and the correct
        /// value will be retrieved.
        ///  
        internal bool TryGetValue(object indexClass, int index, string name, bool ignoreCase, out object value) {
            // read the data now.  The data is immutable so we get a consistent view. 
            // If there's a concurrent writer they will replace data and it just appears 
            // that we won the ----
            ExpandoData data = _data; 
            if (data.Class != indexClass || ignoreCase) {
                /* Re-search for the index matching the name here if
                 *  1) the class has changed, we need to get the correct index and return
                 *  the value there. 
                 *  2) the search is case insensitive:
                 *      a. the member specified by index may be deleted, but there might be other 
                 *      members matching the name if the binder is case insensitive. 
                 *      b. the member that exactly matches the name didn't exist before and exists now,
                 *      need to find the exact match. 
                 */
                index = data.Class.GetValueIndex(name, ignoreCase, this);
                if (index == ExpandoObject.AmbiguousMatchFound) {
                    throw Error.AmbiguousMatchInExpandoObject(name); 
                }
            } 
 
            if (index == ExpandoObject.NoMatch) {
                value = null; 
                return false;
            }

            // Capture the value into a temp, so it doesn't get mutated after we check 
            // for Uninitialized.
            object temp = data[index]; 
            if (temp == Uninitialized) { 
                value = null;
                return false; 
            }

            // index is now known to be correct
            value = temp; 
            return true;
        } 
 
        /// 
        /// Sets the data for the specified class at the specified index.  If the class has 
        /// changed then a full look for the slot will be performed.  If the new class does
        /// not have the provided slot then the Expando's class will change. Only case sensitive
        /// setter is supported in ExpandoObject.
        ///  
        internal void TrySetValue(object indexClass, int index, object value, string name, bool ignoreCase, bool add) {
            ExpandoData data; 
            object oldValue; 

            lock (LockObject) { 
                data = _data;

                if (data.Class != indexClass || ignoreCase) {
                    // The class has changed or we are doing a case-insensitive search, 
                    // we need to get the correct index and set the value there.  If we
                    // don't have the value then we need to promote the class - that 
                    // should only happen when we have multiple concurrent writers. 
                    index = data.Class.GetValueIndex(name, ignoreCase, this);
                    if (index == ExpandoObject.AmbiguousMatchFound) { 
                        throw Error.AmbiguousMatchInExpandoObject(name);
                    }
                    if (index == ExpandoObject.NoMatch) {
                        // Before creating a new class with the new member, need to check 
                        // if there is the exact same member but is deleted. We should reuse
                        // the class if there is such a member. 
                        int exactMatch = ignoreCase ? 
                            data.Class.GetValueIndexCaseSensitive(name) :
                            index; 
                        if (exactMatch != ExpandoObject.NoMatch) {
                            Debug.Assert(data[exactMatch] == Uninitialized);
                            index = exactMatch;
                        } else { 
                            ExpandoClass newClass = data.Class.FindNewClass(name);
                            data = PromoteClassCore(data.Class, newClass); 
                            // After the class promotion, there must be an exact match, 
                            // so we can do case-sensitive search here.
                            index = data.Class.GetValueIndexCaseSensitive(name); 
                            Debug.Assert(index != ExpandoObject.NoMatch);
                        }
                    }
                } 

                // Setting an uninitialized member increases the count of available members 
                oldValue = data[index]; 
                if (oldValue == Uninitialized) {
                    _count++; 
                } else if (add) {
                    throw Error.SameKeyExistsInExpando(name);
                }
 
                data[index] = value;
            } 
 
            // Notify property changed, outside of the lock.
            var propertyChanged = _propertyChanged; 
            if (propertyChanged != null && value != oldValue) {
                // Use the canonical case for the key.
                propertyChanged(this, new PropertyChangedEventArgs(data.Class.Keys[index]));
            } 
        }
 
        ///  
        /// Deletes the data stored for the specified class at the specified index.
        ///  
        internal bool TryDeleteValue(object indexClass, int index, string name, bool ignoreCase, object deleteValue) {
            ExpandoData data;
            lock (LockObject) {
                data = _data; 

                if (data.Class != indexClass || ignoreCase) { 
                    // the class has changed or we are doing a case-insensitive search, 
                    // we need to get the correct index.  If there is no associated index
                    // we simply can't have the value and we return false. 
                    index = data.Class.GetValueIndex(name, ignoreCase, this);
                    if (index == ExpandoObject.AmbiguousMatchFound) {
                        throw Error.AmbiguousMatchInExpandoObject(name);
                    } 
                }
                if (index == ExpandoObject.NoMatch) { 
                    return false; 
                }
 
                object oldValue = data[index];
                if (oldValue == Uninitialized) {
                    return false;
                } 

                // Make sure the value matches, if requested. 
                // 
                // It's a shame we have to call Equals with the lock held but
                // there doesn't seem to be a good way around that, and 
                // ConcurrentDictionary in mscorlib does the same thing.
                if (deleteValue != Uninitialized && !object.Equals(oldValue, deleteValue)) {
                    return false;
                } 

                data[index] = Uninitialized; 
 
                // Deleting an available member decreases the count of available members
                _count--; 
            }

            // Notify property changed, outside of the lock.
            var propertyChanged = _propertyChanged; 
            if (propertyChanged != null) {
                // Use the canonical case for the key. 
                propertyChanged(this, new PropertyChangedEventArgs(data.Class.Keys[index])); 
            }
 
            return true;
        }

        ///  
        /// Returns true if the member at the specified index has been deleted,
        /// otherwise false. Call this function holding the lock. 
        ///  
        internal bool IsDeletedMember(int index) {
            Debug.Assert(index >= 0 && index <= _data.Length); 

            if (index == _data.Length) {
                // The member is a newly added by SetMemberBinder and not in data yet
                return false; 
            }
 
            return _data[index] == ExpandoObject.Uninitialized; 
        }
 
        /// 
        /// Exposes the ExpandoClass which we've associated with this
        /// Expando object.  Used for type checks in rules.
        ///  
        internal ExpandoClass Class {
            get { 
                return _data.Class; 
            }
        } 

        /// 
        /// Promotes the class from the old type to the new type and returns the new
        /// ExpandoData object. 
        /// 
        private ExpandoData PromoteClassCore(ExpandoClass oldClass, ExpandoClass newClass) { 
            Debug.Assert(oldClass != newClass); 

            lock (LockObject) { 
                if (_data.Class == oldClass) {
                    _data = _data.UpdateClass(newClass);
                }
                return _data; 
            }
        } 
 
        /// 
        /// Internal helper to promote a class.  Called from our RuntimeOps helper.  This 
        /// version simply doesn't expose the ExpandoData object which is a private
        /// data structure.
        /// 
        internal void PromoteClass(object oldClass, object newClass) { 
            PromoteClassCore((ExpandoClass)oldClass, (ExpandoClass)newClass);
        } 
 
        #endregion
 
        #region IDynamicMetaObjectProvider Members

        DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) {
            return new MetaExpando(parameter, this); 
        }
        #endregion 
 
        #region Helper methods
        private void TryAddMember(string key, object value) { 
            ContractUtils.RequiresNotNull(key, "key");
            // Pass null to the class, which forces lookup.
            TrySetValue(null, -1, value, key, false, true);
        } 

        private bool TryGetValueForKey(string key, out object value) { 
            // Pass null to the class, which forces lookup. 
            return TryGetValue(null, -1, key, false, out value);
        } 

        private bool ExpandoContainsKey(string key) {
            return _data.Class.GetValueIndexCaseSensitive(key) >= 0;
        } 

        // We create a non-generic type for the debug view for each different collection type 
        // that uses DebuggerTypeProxy, instead of defining a generic debug view type and 
        // using different instantiations. The reason for this is that support for generics
        // with using DebuggerTypeProxy is limited. For C#, DebuggerTypeProxy supports only 
        // open types (from MSDN http://msdn.microsoft.com/en-us/library/d8eyd8zc.aspx).
        private sealed class KeyCollectionDebugView {
            private ICollection collection;
            public KeyCollectionDebugView(ICollection collection) { 
                Debug.Assert(collection != null);
                this.collection = collection; 
            } 

            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 
            public string[] Items {
                get {
                    string[] items = new string[collection.Count];
                    collection.CopyTo(items, 0); 
                    return items;
                } 
            } 
        }
 
        [DebuggerTypeProxy(typeof(KeyCollectionDebugView))]
        [DebuggerDisplay("Count = {Count}")]
        private class KeyCollection : ICollection {
            private readonly ExpandoObject _expando; 
            private readonly int _expandoVersion;
            private readonly int _expandoCount; 
            private readonly ExpandoData _expandoData; 

            internal KeyCollection(ExpandoObject expando) { 
                lock (expando.LockObject) {
                    _expando = expando;
                    _expandoVersion = expando._data.Version;
                    _expandoCount = expando._count; 
                    _expandoData = expando._data;
                } 
            } 

            private void CheckVersion() { 
                if (_expando._data.Version != _expandoVersion || _expandoData != _expando._data) {
                    //the underlying expando object has changed
                    throw Error.CollectionModifiedWhileEnumerating();
                } 
            }
 
            #region ICollection Members 

            public void Add(string item) { 
                throw Error.CollectionReadOnly();
            }

            public void Clear() { 
                throw Error.CollectionReadOnly();
            } 
 
            public bool Contains(string item) {
                lock (_expando.LockObject) { 
                    CheckVersion();
                    return _expando.ExpandoContainsKey(item);
                }
            } 

            public void CopyTo(string[] array, int arrayIndex) { 
                ContractUtils.RequiresNotNull(array, "array"); 
                ContractUtils.RequiresArrayRange(array, arrayIndex, _expandoCount, "arrayIndex", "Count");
                lock (_expando.LockObject) { 
                    CheckVersion();
                    ExpandoData data = _expando._data;
                    for (int i = 0; i < data.Class.Keys.Length; i++) {
                        if (data[i] != Uninitialized) { 
                            array[arrayIndex++] = data.Class.Keys[i];
                        } 
                    } 
                }
            } 

            public int Count {
                get {
                    CheckVersion(); 
                    return _expandoCount;
                } 
            } 

            public bool IsReadOnly { 
                get { return true; }
            }

            public bool Remove(string item) { 
                throw Error.CollectionReadOnly();
            } 
 
            #endregion
 
            #region IEnumerable Members

            public IEnumerator GetEnumerator() {
                for (int i = 0, n = _expandoData.Class.Keys.Length; i < n; i++) { 
                    CheckVersion();
                    if (_expandoData[i] != Uninitialized) { 
                        yield return _expandoData.Class.Keys[i]; 
                    }
                } 
            }

            #endregion
 
            #region IEnumerable Members
 
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { 
                return GetEnumerator();
            } 

            #endregion
        }
 
        // We create a non-generic type for the debug view for each different collection type
        // that uses DebuggerTypeProxy, instead of defining a generic debug view type and 
        // using different instantiations. The reason for this is that support for generics 
        // with using DebuggerTypeProxy is limited. For C#, DebuggerTypeProxy supports only
        // open types (from MSDN http://msdn.microsoft.com/en-us/library/d8eyd8zc.aspx). 
        private sealed class ValueCollectionDebugView {
            private ICollection collection;
            public ValueCollectionDebugView(ICollection collection) {
                Debug.Assert(collection != null); 
                this.collection = collection;
            } 
 
            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public object[] Items { 
                get {
                    object[] items = new object[collection.Count];
                    collection.CopyTo(items, 0);
                    return items; 
                }
            } 
        } 

        [DebuggerTypeProxy(typeof(ValueCollectionDebugView))] 
        [DebuggerDisplay("Count = {Count}")]
        private class ValueCollection : ICollection {
            private readonly ExpandoObject _expando;
            private readonly int _expandoVersion; 
            private readonly int _expandoCount;
            private readonly ExpandoData _expandoData; 
 
            internal ValueCollection(ExpandoObject expando) {
                lock (expando.LockObject) { 
                    _expando = expando;
                    _expandoVersion = expando._data.Version;
                    _expandoCount = expando._count;
                    _expandoData = expando._data; 
                }
            } 
 
            private void CheckVersion() {
                if (_expando._data.Version != _expandoVersion || _expandoData != _expando._data) { 
                    //the underlying expando object has changed
                    throw Error.CollectionModifiedWhileEnumerating();
                }
            } 

            #region ICollection Members 
 
            public void Add(object item) {
                throw Error.CollectionReadOnly(); 
            }

            public void Clear() {
                throw Error.CollectionReadOnly(); 
            }
 
            public bool Contains(object item) { 
                lock (_expando.LockObject) {
                    CheckVersion(); 

                    ExpandoData data = _expando._data;
                    for (int i = 0; i < data.Class.Keys.Length; i++) {
 
                        // See comment in TryDeleteValue; it's okay to call
                        // object.Equals with the lock held. 
                        if (object.Equals(data[i], item)) { 
                            return true;
                        } 
                    }
                    return false;
                }
            } 

            public void CopyTo(object[] array, int arrayIndex) { 
                ContractUtils.RequiresNotNull(array, "array"); 
                ContractUtils.RequiresArrayRange(array, arrayIndex, _expandoCount, "arrayIndex", "Count");
                lock (_expando.LockObject) { 
                    CheckVersion();
                    ExpandoData data = _expando._data;
                    for (int i = 0; i < data.Class.Keys.Length; i++) {
                        if (data[i] != Uninitialized) { 
                            array[arrayIndex++] = data[i];
                        } 
                    } 
                }
            } 

            public int Count {
                get {
                    CheckVersion(); 
                    return _expandoCount;
                } 
            } 

            public bool IsReadOnly { 
                get { return true; }
            }

            public bool Remove(object item) { 
                throw Error.CollectionReadOnly();
            } 
 
            #endregion
 
            #region IEnumerable Members

            public IEnumerator GetEnumerator() {
                ExpandoData data = _expando._data; 
                for (int i = 0; i < data.Class.Keys.Length; i++) {
                    CheckVersion(); 
                    // Capture the value into a temp so we don't inadvertently 
                    // return Uninitialized.
                    object temp = data[i]; 
                    if (temp != Uninitialized) {
                        yield return temp;
                    }
                } 
            }
 
            #endregion 

            #region IEnumerable Members 

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return GetEnumerator();
            } 

            #endregion 
        } 

        #endregion 

        #region IDictionary Members
        ICollection IDictionary.Keys {
            get { 
                return new KeyCollection(this);
            } 
        } 

        ICollection IDictionary.Values { 
            get {
                return new ValueCollection(this);
            }
        } 

        object IDictionary.this[string key] { 
            get { 
                object value;
                if (!TryGetValueForKey(key, out value)) { 
                    throw Error.KeyDoesNotExistInExpando(key);
                }
                return value;
            } 
            set {
                ContractUtils.RequiresNotNull(key, "key"); 
                // Pass null to the class, which forces lookup. 
                TrySetValue(null, -1, value, key, false, false);
            } 
        }

        void IDictionary.Add(string key, object value) {
            this.TryAddMember(key, value); 
        }
 
        bool IDictionary.ContainsKey(string key) { 
            ContractUtils.RequiresNotNull(key, "key");
 
            ExpandoData data = _data;
            int index = data.Class.GetValueIndexCaseSensitive(key);
            return index >= 0 && data[index] != Uninitialized;
        } 

        bool IDictionary.Remove(string key) { 
            ContractUtils.RequiresNotNull(key, "key"); 
            // Pass null to the class, which forces lookup.
            return TryDeleteValue(null, -1, key, false, Uninitialized); 
        }

        bool IDictionary.TryGetValue(string key, out object value) {
            return TryGetValueForKey(key, out value); 
        }
 
        #endregion 

        #region ICollection> Members 
        int ICollection>.Count {
            get {
                return _count;
            } 
        }
 
        bool ICollection>.IsReadOnly { 
            get { return false; }
        } 

        void ICollection>.Add(KeyValuePair item) {
            TryAddMember(item.Key, item.Value);
        } 

        void ICollection>.Clear() { 
            // We remove both class and data! 
            ExpandoData data;
            lock (LockObject) { 
                data = _data;
                _data = ExpandoData.Empty;
                _count = 0;
            } 

            // Notify property changed for all properties. 
            var propertyChanged = _propertyChanged; 
            if (propertyChanged != null) {
                for (int i = 0, n = data.Class.Keys.Length; i < n; i++) { 
                    if (data[i] != Uninitialized) {
                        propertyChanged(this, new PropertyChangedEventArgs(data.Class.Keys[i]));
                    }
                } 
            }
        } 
 
        bool ICollection>.Contains(KeyValuePair item) {
            object value; 
            if (!TryGetValueForKey(item.Key, out value)) {
                return false;
            }
 
            return object.Equals(value, item.Value);
        } 
 
        void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) {
            ContractUtils.RequiresNotNull(array, "array"); 
            ContractUtils.RequiresArrayRange(array, arrayIndex, _count, "arrayIndex", "Count");

            // We want this to be atomic and not throw
            lock (LockObject) { 
                foreach (KeyValuePair item in this) {
                    array[arrayIndex++] = item; 
                } 
            }
        } 

        bool ICollection>.Remove(KeyValuePair item) {
            return TryDeleteValue(null, -1, item.Key, false, item.Value);
        } 
        #endregion
 
        #region IEnumerable> Member 

        IEnumerator> IEnumerable>.GetEnumerator() { 
            ExpandoData data = _data;
            return GetExpandoEnumerator(data, data.Version);
        }
 
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            ExpandoData data = _data; 
            return GetExpandoEnumerator(data, data.Version); 
        }
 
        // Note: takes the data and version as parameters so they will be
        // captured before the first call to MoveNext().
        private IEnumerator> GetExpandoEnumerator(ExpandoData data, int version) {
            for (int i = 0; i < data.Class.Keys.Length; i++) { 
                if (_data.Version != version || data != _data) {
                    // The underlying expando object has changed: 
                    // 1) the version of the expando data changed 
                    // 2) the data object is changed
                    throw Error.CollectionModifiedWhileEnumerating(); 
                }
                // Capture the value into a temp so we don't inadvertently
                // return Uninitialized.
                object temp = data[i]; 
                if (temp != Uninitialized) {
                    yield return new KeyValuePair(data.Class.Keys[i], temp); 
                } 
            }
        } 
        #endregion

        #region MetaExpando
 
        private class MetaExpando : DynamicMetaObject {
            public MetaExpando(Expression expression, ExpandoObject value) 
                : base(expression, BindingRestrictions.Empty, value) { 
            }
 
            private DynamicMetaObject BindGetOrInvokeMember(DynamicMetaObjectBinder binder, string name, bool ignoreCase, DynamicMetaObject fallback, Func fallbackInvoke) {
                ExpandoClass klass = Value.Class;

                //try to find the member, including the deleted members 
                int index = klass.GetValueIndex(name, ignoreCase, Value);
 
                ParameterExpression value = Expression.Parameter(typeof(object), "value"); 

                Expression tryGetValue = Expression.Call( 
                    typeof(RuntimeOps).GetMethod("ExpandoTryGetValue"),
                    GetLimitedSelf(),
                    Expression.Constant(klass, typeof(object)),
                    Expression.Constant(index), 
                    Expression.Constant(name),
                    Expression.Constant(ignoreCase), 
                    value 
                );
 
                var result = new DynamicMetaObject(value, BindingRestrictions.Empty);
                if (fallbackInvoke != null) {
                    result = fallbackInvoke(result);
                } 

                result = new DynamicMetaObject( 
                    Expression.Block( 
                        new[] { value },
                        Expression.Condition( 
                            tryGetValue,
                            result.Expression,
                            fallback.Expression,
                            typeof(object) 
                        )
                    ), 
                    result.Restrictions.Merge(fallback.Restrictions) 
                );
 
                return AddDynamicTestAndDefer(binder, Value.Class, null, result);
            }

            public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { 
                ContractUtils.RequiresNotNull(binder, "binder");
                return BindGetOrInvokeMember( 
                    binder, 
                    binder.Name,
                    binder.IgnoreCase, 
                    binder.FallbackGetMember(this),
                    null
                );
            } 

            public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { 
                ContractUtils.RequiresNotNull(binder, "binder"); 
                return BindGetOrInvokeMember(
                    binder, 
                    binder.Name,
                    binder.IgnoreCase,
                    binder.FallbackInvokeMember(this, args),
                    value => binder.FallbackInvoke(value, args, null) 
                );
            } 
 
            public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
                ContractUtils.RequiresNotNull(binder, "binder"); 
                ContractUtils.RequiresNotNull(value, "value");

                ExpandoClass klass;
                int index; 

                ExpandoClass originalClass = GetClassEnsureIndex(binder.Name, binder.IgnoreCase, Value, out klass, out index); 
 
                return AddDynamicTestAndDefer(
                    binder, 
                    klass,
                    originalClass,
                    new DynamicMetaObject(
                        Expression.Call( 
                            typeof(RuntimeOps).GetMethod("ExpandoTrySetValue"),
                            GetLimitedSelf(), 
                            Expression.Constant(klass, typeof(object)), 
                            Expression.Constant(index),
                            Expression.Convert(value.Expression, typeof(object)), 
                            Expression.Constant(binder.Name),
                            Expression.Constant(binder.IgnoreCase)
                        ),
                        BindingRestrictions.Empty 
                    )
                ); 
            } 

            public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) { 
                ContractUtils.RequiresNotNull(binder, "binder");

                int index = Value.Class.GetValueIndex(binder.Name, binder.IgnoreCase, Value);
 
                Expression tryDelete = Expression.Call(
                    typeof(RuntimeOps).GetMethod("ExpandoTryDeleteValue"), 
                    GetLimitedSelf(), 
                    Expression.Constant(Value.Class, typeof(object)),
                    Expression.Constant(index), 
                    Expression.Constant(binder.Name),
                    Expression.Constant(binder.IgnoreCase)
                );
                DynamicMetaObject fallback = binder.FallbackDeleteMember(this); 

                DynamicMetaObject target = new DynamicMetaObject( 
                    Expression.IfThen(Expression.Not(tryDelete), fallback.Expression), 
                    fallback.Restrictions
                ); 

                return AddDynamicTestAndDefer(binder, Value.Class, null, target);
            }
 
            public override IEnumerable GetDynamicMemberNames() {
                var expandoData = Value._data; 
                var klass = expandoData.Class; 
                for (int i = 0; i < klass.Keys.Length; i++) {
                    object val = expandoData[i]; 
                    if (val != ExpandoObject.Uninitialized) {
                        yield return klass.Keys[i];
                    }
                } 
            }
 
            ///  
            /// Adds a dynamic test which checks if the version has changed.  The test is only necessary for
            /// performance as the methods will do the correct thing if called with an incorrect version. 
            /// 
            private DynamicMetaObject AddDynamicTestAndDefer(DynamicMetaObjectBinder binder, ExpandoClass klass, ExpandoClass originalClass, DynamicMetaObject succeeds) {

                Expression ifTestSucceeds = succeeds.Expression; 
                if (originalClass != null) {
                    // we are accessing a member which has not yet been defined on this class. 
                    // We force a class promotion after the type check.  If the class changes the 
                    // promotion will fail and the set/delete will do a full lookup using the new
                    // class to discover the name. 
                    Debug.Assert(originalClass != klass);

                    ifTestSucceeds = Expression.Block(
                        Expression.Call( 
                            null,
                            typeof(RuntimeOps).GetMethod("ExpandoPromoteClass"), 
                            GetLimitedSelf(), 
                            Expression.Constant(originalClass, typeof(object)),
                            Expression.Constant(klass, typeof(object)) 
                        ),
                        succeeds.Expression
                    );
                } 

                return new DynamicMetaObject( 
                    Expression.Condition( 
                        Expression.Call(
                            null, 
                            typeof(RuntimeOps).GetMethod("ExpandoCheckVersion"),
                            GetLimitedSelf(),
                            Expression.Constant(originalClass ?? klass, typeof(object))
                        ), 
                        ifTestSucceeds,
                        binder.GetUpdateExpression(ifTestSucceeds.Type) 
                    ), 
                    GetRestrictions().Merge(succeeds.Restrictions)
                ); 
            }

            /// 
            /// Gets the class and the index associated with the given name.  Does not update the expando object.  Instead 
            /// this returns both the original and desired new class.  A rule is created which includes the test for the
            /// original class, the promotion to the new class, and the set/delete based on the class post-promotion. 
            ///  
            private ExpandoClass GetClassEnsureIndex(string name, bool caseInsensitive, ExpandoObject obj, out ExpandoClass klass, out int index) {
                ExpandoClass originalClass = Value.Class; 

                index = originalClass.GetValueIndex(name, caseInsensitive, obj) ;
                if (index == ExpandoObject.AmbiguousMatchFound) {
                    klass = originalClass; 
                    return null;
                } 
                if (index == ExpandoObject.NoMatch) { 
                    // go ahead and find a new class now...
                    ExpandoClass newClass = originalClass.FindNewClass(name); 

                    klass = newClass;
                    index = newClass.GetValueIndexCaseSensitive(name);
 
                    Debug.Assert(index != ExpandoObject.NoMatch);
                    return originalClass; 
                } else { 
                    klass = originalClass;
                    return null; 
                }
            }

            ///  
            /// Returns our Expression converted to our known LimitType
            ///  
            private Expression GetLimitedSelf() { 
                if (TypeUtils.AreEquivalent(Expression.Type, LimitType)) {
                    return Expression; 
                }
                return Expression.Convert(Expression, LimitType);
            }
 
            /// 
            /// Returns a Restrictions object which includes our current restrictions merged 
            /// with a restriction limiting our type 
            /// 
            private BindingRestrictions GetRestrictions() { 
                Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty");

                return BindingRestrictions.GetTypeRestriction(this);
            } 

            public new ExpandoObject Value { 
                get { 
                    return (ExpandoObject)base.Value;
                } 
            }
        }

        #endregion 

        #region ExpandoData 
 
        /// 
        /// Stores the class and the data associated with the class as one atomic 
        /// pair.  This enables us to do a class check in a thread safe manner w/o
        /// requiring locks.
        /// 
        private class ExpandoData { 
            internal static ExpandoData Empty = new ExpandoData();
 
            ///  
            /// the dynamically assigned class associated with the Expando object
            ///  
            internal readonly ExpandoClass Class;

            /// 
            /// data stored in the expando object, key names are stored in the class. 
            ///
            /// Expando._data must be locked when mutating the value.  Otherwise a copy of it 
            /// could be made and lose values. 
            /// 
            private readonly object[] _dataArray; 

            /// 
            /// Indexer for getting/setting the data
            ///  
            internal object this[int index] {
                get { 
                    return _dataArray[index]; 
                }
                set { 
                    //when the array is updated, version increases, even the new value is the same
                    //as previous. Dictionary type has the same behavior.
                    _version++;
                    _dataArray[index] = value; 
                }
            } 
 
            internal int Version {
                get { return _version; } 
            }

            internal int Length {
                get { return _dataArray.Length; } 
            }
 
            ///  
            /// Constructs an empty ExpandoData object with the empty class and no data.
            ///  
            private ExpandoData() {
                Class = ExpandoClass.Empty;
                _dataArray = new object[0];
            } 

            ///  
            /// the version of the ExpandoObject that tracks set and delete operations 
            /// 
            private int _version; 

            /// 
            /// Constructs a new ExpandoData object with the specified class and data.
            ///  
            internal ExpandoData(ExpandoClass klass, object[] data, int version) {
                Class = klass; 
                _dataArray = data; 
                _version = version;
            } 

            /// 
            /// Update the associated class and increases the storage for the data array if needed.
            ///  
            /// 
            internal ExpandoData UpdateClass(ExpandoClass newClass) { 
                if (_dataArray.Length >= newClass.Keys.Length) { 
                    // we have extra space in our buffer, just initialize it to Uninitialized.
                    this[newClass.Keys.Length - 1] = ExpandoObject.Uninitialized; 
                    return new ExpandoData(newClass, this._dataArray, this._version);
                } else {
                    // we've grown too much - we need a new object array
                    int oldLength = _dataArray.Length; 
                    object[] arr = new object[GetAlignedSize(newClass.Keys.Length)];
                    Array.Copy(_dataArray, arr, _dataArray.Length); 
                    ExpandoData newData = new ExpandoData(newClass, arr, this._version); 
                    newData[oldLength] = ExpandoObject.Uninitialized;
                    return newData; 
                }
            }

            private static int GetAlignedSize(int len) { 
                // the alignment of the array for storage of values (must be a power of two)
                const int DataArrayAlignment = 8; 
 
                // round up and then mask off lower bits
                return (len + (DataArrayAlignment - 1)) & (~(DataArrayAlignment - 1)); 
            }
        }

        #endregion 

        #region INotifyPropertyChanged Members 
 
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged {
            add { _propertyChanged += value; } 
            remove { _propertyChanged -= value; }
        }

        #endregion 
    }
} 
 
namespace System.Runtime.CompilerServices {
 
    //
    // Note: these helpers are kept as simple wrappers so they have a better
    // chance of being inlined.
    // 
    public static partial class RuntimeOps {
 
        ///  
        /// Gets the value of an item in an expando object.
        ///  
        /// The expando object.
        /// The class of the expando object.
        /// The index of the member.
        /// The name of the member. 
        /// true if the name should be matched ignoring case; false otherwise.
        /// The out parameter containing the value of the member. 
        /// True if the member exists in the expando object, otherwise false. 
        [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]
        public static bool ExpandoTryGetValue(ExpandoObject expando, object indexClass, int index, string name, bool ignoreCase, out object value) { 
            return expando.TryGetValue(indexClass, index, name, ignoreCase, out value);
        }

        ///  
        /// Sets the value of an item in an expando object.
        ///  
        /// The expando object. 
        /// The class of the expando object.
        /// The index of the member. 
        /// The value of the member.
        /// The name of the member.
        /// true if the name should be matched ignoring case; false otherwise.
        ///  
        /// Returns the index for the set member.
        ///  
        [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] 
        public static object ExpandoTrySetValue(ExpandoObject expando, object indexClass, int index, object value, string name, bool ignoreCase) {
            expando.TrySetValue(indexClass, index, value, name, ignoreCase, false); 
            return value;
        }

        ///  
        /// Deletes the value of an item in an expando object.
        ///  
        /// The expando object. 
        /// The class of the expando object.
        /// The index of the member. 
        /// The name of the member.
        /// true if the name should be matched ignoring case; false otherwise.
        /// true if the item was successfully removed; otherwise, false.
        [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] 
        public static bool ExpandoTryDeleteValue(ExpandoObject expando, object indexClass, int index, string name, bool ignoreCase) {
            return expando.TryDeleteValue(indexClass, index, name, ignoreCase, ExpandoObject.Uninitialized); 
        } 

        ///  
        /// Checks the version of the expando object.
        /// 
        /// The expando object.
        /// The version to check. 
        /// true if the version is equal; otherwise, false.
        [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] 
        public static bool ExpandoCheckVersion(ExpandoObject expando, object version) { 
            return expando.Class == version;
        } 

        /// 
        /// Promotes an expando object from one class to a new class.
        ///  
        /// The expando object.
        /// The old class of the expando object. 
        /// The new class of the expando object. 
        [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)]
        public static void ExpandoPromoteClass(ExpandoObject expando, object oldClass, object newClass) { 
            expando.PromoteClass(oldClass, newClass);
        }
    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK