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; } }
/// <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)); }
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 ) )); }
/// <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); } }
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); } }
/// <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; } }
/// <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); } }
/// <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); }
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)); }
/// <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); } }
/// <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; }
/// <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); } }
/// <summary> /// Constructs a new ExpandoData object with the specified class and data. /// </summary> internal ExpandoData(ExpandoClass klass, object[] data) { Class = klass; Data = data; }
/// <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; }
/// <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; } }
/// <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); }
/// <summary> /// Constructs an empty ExpandoData object with the empty class and no data. /// </summary> private ExpandoData() { Class = ExpandoClass.Empty; _dataArray = Array.Empty <object>(); }
/// <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; } }
/// <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]; }
/// <summary> /// Constructs an empty ExpandoData object with the empty class and no data. /// </summary> private ExpandoData() { Class = ExpandoClass.Empty; Data = new object[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) )); }
/// <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; } }
/// <summary> /// Constructs an empty ExpandoData object with the empty class and no data. /// </summary> private ExpandoData() { Class = ExpandoClass.Empty; _dataArray = ArrayReservoir <object> .EmptyArray; }
/// <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; } }
/// <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; }