Represents a dynamically assigned class. Expando objects which share the same members will share the same class. Classes are dynamically assigned as the expando object gains members.
 internal ExpandoClass FindNewClass(string newKey)
 {
     int hashCode = this._hashCode ^ newKey.GetHashCode();
     lock (this)
     {
         List<WeakReference> transitionList = this.GetTransitionList(hashCode);
         for (int i = 0; i < transitionList.Count; i++)
         {
             ExpandoClass class2 = transitionList[i].Target as ExpandoClass;
             if (class2 == null)
             {
                 transitionList.RemoveAt(i);
                 i--;
             }
             else if (string.Equals(class2._keys[class2._keys.Length - 1], newKey, StringComparison.Ordinal))
             {
                 return class2;
             }
         }
         string[] destinationArray = new string[this._keys.Length + 1];
         Array.Copy(this._keys, destinationArray, this._keys.Length);
         destinationArray[this._keys.Length] = newKey;
         ExpandoClass target = new ExpandoClass(destinationArray, hashCode);
         transitionList.Add(new WeakReference(target));
         return target;
     }
 }
Exemple #2
0
        /// <summary>
        /// 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.
        /// </summary>
        internal void TrySetValue(object indexClass, int index, object value, string name, bool ignoreCase, bool add)
        {
            string propertyName;

            lock (LockObject)
            {
                var 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 == AmbiguousMatchFound)
                    {
                        throw Error.AmbiguousMatchInExpandoObject(name);
                    }
                    if (index == 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, LockObject) :
                                         index;
                        if (exactMatch != NoMatch)
                        {
                            Debug.Assert(data[exactMatch] == Uninitialized);
                            index = exactMatch;
                        }
                        else
                        {
                            ExpandoClass newClass = data.Class.FindNewClass(name, LockObject);
                            _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, LockObject);
                            Debug.Assert(index != NoMatch);
                        }
                    }
                }

                // Setting an uninitialized member increases the count of available members
                var oldValue = data[index];
                if (oldValue == Uninitialized)
                {
                    _count++;
                }
                else if (add)
                {
                    throw Error.SameKeyExistsInExpando(name);
                }
                data[index]  = value;
                propertyName = data.Class.Keys[index];
            }

            // Notify property changed outside the lock
            _propertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
Exemple #3
0
            public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
            {
                ContractUtils.RequiresNotNull(binder, nameof(binder));
                ContractUtils.RequiresNotNull(value, nameof(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
                               )
                           ));
            }
Exemple #4
0
            /// <summary>
            /// 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.
            /// </summary>
            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);
                }
            }
Exemple #5
0
        internal ExpandoClass FindNewClass(string newKey)
        {
            int hashCode = this._hashCode ^ newKey.GetHashCode();

            lock (this)
            {
                List <WeakReference> transitionList = this.GetTransitionList(hashCode);
                for (int i = 0; i < transitionList.Count; i++)
                {
                    ExpandoClass class2 = transitionList[i].Target as ExpandoClass;
                    if (class2 == null)
                    {
                        transitionList.RemoveAt(i);
                        i--;
                    }
                    else if (string.Equals(class2._keys[class2._keys.Length - 1], newKey, StringComparison.Ordinal))
                    {
                        return(class2);
                    }
                }
                string[] destinationArray = new string[this._keys.Length + 1];
                Array.Copy(this._keys, destinationArray, this._keys.Length);
                destinationArray[this._keys.Length] = newKey;
                ExpandoClass target = new ExpandoClass(destinationArray, hashCode);
                transitionList.Add(new WeakReference(target));
                return(target);
            }
        }
Exemple #6
0
        /// <summary>
        /// Finds or creates a new ExpandoClass given the existing set of keys
        /// in this ExpandoClass plus the new key to be added.
        /// </summary>
        internal ExpandoClass FindNewClass(string newKey, bool ignoreCase) {
            // just XOR the newKey hash code 
            int hashCode = _hashCode ^ newKey.GetHashCode();

            lock (this) {
                List<WeakReference> infos = GetTransitionList(hashCode);

                for(int i = 0; i<infos.Count; i++) {
                    ExpandoClass klass = infos[i].Target as ExpandoClass;
                    if (klass == null) {
                        infos.RemoveAt(i);
                        i--;
                        continue;
                    }

                    if (string.Equals(klass._keys[klass._keys.Length - 1], newKey, GetStringComparison(ignoreCase))) {
                        // the new key is the key we added in this transition
                        return klass;
                    }
                }

                // no applicable transition, create a new one
                string[] keys = new string[_keys.Length + 1];
                Array.Copy(_keys, keys, _keys.Length);
                keys[_keys.Length] = newKey;
                ExpandoClass ec = new ExpandoClass(keys, hashCode);

                infos.Add(new WeakReference(ec));
                return ec;
            }
        }
Exemple #7
0
        /// <summary>
        /// Finds or creates a new ExpandoClass given the existing set of keys
        /// in this ExpandoClass plus the new key to be added. Members in an
        /// ExpandoClass are always stored case sensitively.
        /// </summary>
        internal ExpandoClass FindNewClass(string newKey)
        {
            // just XOR the newKey hash code
            int hashCode = _hashCode ^ newKey.GetHashCode();

            lock (this) {
                List <WeakReference> infos = GetTransitionList(hashCode);

                for (int i = 0; i < infos.Count; i++)
                {
                    ExpandoClass klass = infos[i].Target as ExpandoClass;
                    if (klass == null)
                    {
                        infos.RemoveAt(i);
                        i--;
                        continue;
                    }

                    if (string.Equals(klass._keys[klass._keys.Length - 1], newKey, StringComparison.Ordinal))
                    {
                        // the new key is the key we added in this transition
                        return(klass);
                    }
                }

                // no applicable transition, create a new one
                string[] keys = new string[_keys.Length + 1];
                Array.Copy(_keys, keys, _keys.Length);
                keys[_keys.Length] = newKey;
                ExpandoClass ec = new ExpandoClass(keys, hashCode);

                infos.Add(new WeakReference(ec));
                return(ec);
            }
        }
        /// <summary>
        /// Promotes the class from the old type to the new type and returns the new
        /// ExpandoData object.
        /// </summary>
        private ExpandoData PromoteClassCore(ExpandoClass oldClass, ExpandoClass newClass)
        {
            Debug.Assert(oldClass != newClass);

            lock (LockObject) {
                if (_data.Class == oldClass)
                {
                    _data = _data.UpdateClass(newClass);
                }
                return(_data);
            }
        }
Exemple #9
0
        /// <summary>
        /// Promotes the class from the old type to the new type and returns the new
        /// ExpandoData object.
        /// </summary>
        private ExpandoData PromoteClassCore(ExpandoClass oldClass, ExpandoClass newClass)
        {
            Debug.Assert(oldClass != newClass);
            ContractUtils.AssertLockHeld(LockObject);

            if (_data.Class == oldClass)
            {
                _data = _data.UpdateClass(newClass);
            }

            return(_data);
        }
Exemple #10
0
            private DynamicMetaObject BindGetOrInvokeMember(DynamicMetaObjectBinder binder, string name, bool ignoreCase, DynamicMetaObject fallback, Func <DynamicMetaObject, DynamicMetaObject>?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(
                    s_expandoTryGetValue,
                    GetLimitedSelf(),
                    Expression.Constant(klass, typeof(object)),
                    AstUtils.Constant(index),
                    Expression.Constant(name),
                    AstUtils.Constant(ignoreCase),
                    value
                    );

                var result = new DynamicMetaObject(value, BindingRestrictions.Empty);

                if (fallbackInvoke != null)
                {
                    result = fallbackInvoke(result);
                }

                result = new DynamicMetaObject(
                    Expression.Block(
                        new TrueReadOnlyCollection <ParameterExpression>(value),
                        new TrueReadOnlyCollection <Expression>(
                            Expression.Condition(
                                tryGetValue,
                                result.Expression,
                                fallback.Expression,
                                typeof(object)
                                )
                            )
                        ),
                    result.Restrictions.Merge(fallback.Restrictions)
                    );

                return(AddDynamicTestAndDefer(binder, Value.Class, null, result));
            }
Exemple #11
0
 /// <summary>
 /// Update the associated class and increases the storage for the data array if needed.
 /// </summary>
 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, _dataArray, _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, 0, arr, 0, _dataArray.Length);
         ExpandoData newData = new ExpandoData(newClass, arr, _version);
         newData[oldLength] = ExpandoObject.Uninitialized;
         return(newData);
     }
 }
Exemple #12
0
        /// <summary>
        /// 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.
        /// </summary>
        internal int TryGetValue(ExpandoClass klass, int index, bool caseInsensitive, string name, out object value) {
            if (index == ExpandoObject.NoMatch) {
                value = null;
                return index;
            }

            // 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 race
            ExpandoData data = _data;
            if (data.Class != klass || caseInsensitive) {
                /* 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, caseInsensitive, this);
            }

            if (index < 0) {
                value = null;
                return index;
            }

            if (data.Data[index] == Uninitialized) {
                value = null;
                return NoMatch;
            }

            // index is now known to be correct
            value = data.Data[index];
            return index;
        }
Exemple #13
0
        /// <summary>
        /// Finds or creates a new ExpandoClass given the existing set of keys
        /// in this ExpandoClass plus the new key to be added. Members in an
        /// ExpandoClass are always stored case sensitively.
        /// </summary>
        internal ExpandoClass FindNewClass(string newKey, object lockObject)
        {
            // just XOR the newKey hash code
            int hashCode = _hashCode ^ newKey.GetHashCode();

            lock (lockObject)
            {
                List <WeakReference> infos = GetTransitionList(hashCode);

                for (int i = 0; i < infos.Count; i++)
                {
                    if (!(infos[i].Target is ExpandoClass @class))
                    {
                        infos.RemoveAt(i);
                        i--;
                        continue;
                    }

                    var classKeys = @class.Keys;
                    if (string.Equals(classKeys[classKeys.Length - 1], newKey, StringComparison.Ordinal))
                    {
                        // the new key is the key we added in this transition
                        return(@class);
                    }
                }

                // no applicable transition, create a new one
                var      keys    = Keys;
                string[] newKeys = new string[keys.Length + 1];
                Array.Copy(keys, 0, newKeys, 0, keys.Length);
                newKeys[keys.Length] = newKey;
                ExpandoClass ec = new ExpandoClass(newKeys, hashCode);

                infos.Add(new WeakReference(ec));
                return(ec);
            }
        }
Exemple #14
0
 /// <summary>
 /// Constructs a new ExpandoData object with the specified class and data.
 /// </summary>
 internal ExpandoData(ExpandoClass klass, object[] data) {
     Class = klass;
     Data = data;
 }
Exemple #15
0
 /// <summary>
 /// Constructs a new ExpandoData object with the specified class and data.
 /// </summary>
 internal ExpandoData(ExpandoClass klass, object[] data, int version)
 {
     Class      = klass;
     _dataArray = data;
     _version   = version;
 }
Exemple #16
0
        /// <summary>
        /// Promotes the class from the old type to the new type and returns the new
        /// ExpandoData object.
        /// </summary>
        private ExpandoData PromoteClassWorker(ExpandoClass oldClass, ExpandoClass newClass) {
            Debug.Assert(oldClass != newClass);

            lock (LockObject) {
                if (_data.Class == oldClass) {
                    _data = new ExpandoData(newClass, newClass.GetNewKeys(_data.Data));
                }
                return _data;
            }
        }
Exemple #17
0
 /// <summary>
 /// 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.
 /// </summary>
 internal void PromoteClass(ExpandoClass oldClass, ExpandoClass newClass) {
     PromoteClassWorker(oldClass, newClass);
 }
Exemple #18
0
 /// <summary>
 /// Constructs an empty ExpandoData object with the empty class and no data.
 /// </summary>
 private ExpandoData()
 {
     Class      = ExpandoClass.Empty;
     _dataArray = Array.Empty <object>();
 }
Exemple #19
0
            /// <summary>
            /// 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.
            /// </summary>
            private ExpandoClass GetClassEnsureIndex(string name, out ExpandoClass klass, out int index) {
                ExpandoClass originalClass = Value.Class;

                index = originalClass.GetValueIndexCaseSensitive(name);
                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;
                }                
            }
Exemple #20
0
            /// <summary>
            /// 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.
            /// </summary>
            private DynamicMetaObject AddDynamicTestAndDefer(
                DynamicMetaObjectBinder binder, 
                DynamicMetaObject[] args, 
                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 = Helpers.Convert(
                        Expression.Block(
                            Expression.Call(
                                null,
                                typeof(RuntimeOps).GetMethod("ExpandoPromoteClass"),
                                GetLimitedSelf(),
                                Expression.Constant(originalClass),
                                Expression.Constant(klass)
                            ),
                            succeeds.Expression
                        ),
                        typeof(object)
                    );
                }

                return new DynamicMetaObject(
                    Expression.Condition(
                        Expression.Call(
                            null,
                            typeof(RuntimeOps).GetMethod("ExpandoCheckVersion"),
                            GetLimitedSelf(),
                            Expression.Constant(originalClass ?? klass)
                        ),
                        Helpers.Convert(ifTestSucceeds, typeof(object)),
                        Helpers.Convert(binder.Defer(args).Expression, typeof(object))
                    ),
                    GetRestrictions().Merge(succeeds.Restrictions)
                );
            }
 /// <summary>
 /// Constructs an empty ExpandoData object with the empty class and no data.
 /// </summary>
 private ExpandoData()
 {
     Class      = ExpandoClass.Empty;
     _dataArray = new object[0];
 }
Exemple #22
0
 /// <summary>
 /// Constructs an empty ExpandoData object with the empty class and no data.
 /// </summary>
 private ExpandoData() {
     Class = ExpandoClass.Empty;
     Data = new object[0];
 }
Exemple #23
0
            /// <summary>
            /// 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.
            /// </summary>
            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)
                           ));
            }
Exemple #24
0
        /// <summary>
        /// Deletes the data stored for the specified class at the specified index.
        /// </summary>
        internal int TryDeleteValue(ExpandoClass klass, int index, bool caseInsensitive) {
            if (index == ExpandoObject.NoMatch) {
                return index;
            }

            lock (LockObject) {
                ExpandoData data = _data;

                if (data.Class != klass || caseInsensitive) {
                    // 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(klass.GetIndexName(index), caseInsensitive, this);
                    if (index < 0) {
                        return index;
                    }
                }

                object oldValue = data.Data[index];
                data.Data[index] = Uninitialized;
                return oldValue == Uninitialized ? ExpandoObject.NoMatch : index;
            }
        }
Exemple #25
0
 /// <summary>
 /// Constructs an empty ExpandoData object with the empty class and no data.
 /// </summary>
 private ExpandoData()
 {
     Class      = ExpandoClass.Empty;
     _dataArray = ArrayReservoir <object> .EmptyArray;
 }
Exemple #26
0
        /// <summary>
        /// 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.
        /// </summary>
        internal int TrySetValue(ExpandoClass klass, int index, object value, bool caseInsensitive) {
            Debug.Assert(index >= 0);

            lock (LockObject) {
                ExpandoData data = _data;

                if (data.Class != klass || caseInsensitive) {
                    //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.
                    string name = klass.GetIndexName(index);
                    index = data.Class.GetValueIndex(name, caseInsensitive, this);
                    if (index == ExpandoObject.AmbiguousMatchFound) {
                        return index;
                    }
                    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 = caseInsensitive ? 
                            data.Class.GetValueIndexCaseSensitive(name) :
                            index;
                        if (exactMatch != ExpandoObject.NoMatch) {
                            Debug.Assert(data.Data[exactMatch] == Uninitialized);
                            index = exactMatch;
                        } else {
                            ExpandoClass newClass = data.Class.FindNewClass(name);
                            data = PromoteClassWorker(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);
                        }
                    }
                }

                data.Data[index] = value;
                return index;
            }           
        }              
Exemple #27
0
 /// <summary>
 /// Constructs a new ExpandoData object with the specified class and data.
 /// </summary>
 private ExpandoData(ExpandoClass @class, object[] data, int version)
 {
     Class      = @class;
     _dataArray = data;
     _version   = version;
 }