/// <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); } }
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 ) )); }
/// <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); } }
/// <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> /// Update the associated class and increases the storage for the data array if needed. /// </summary> /// <returns></returns> 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 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( 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)); }
/// <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> /// 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) { 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])); } }
/// <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> /// Constructs an empty ExpandoData object with the empty class and no data. /// </summary> private ExpandoData() { Class = ExpandoClass.Empty; _dataArray = new object[0]; }