/// <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> /// 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. /// </summary> internal void SetValue(ExpandoClass klass, int index, bool caseInsensitive, object value) { Debug.Assert(index != -1); lock (this) { ExpandoData data = _data; if (data.Class != klass) { // the class has changed, 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); if (index == -1) { ExpandoClass newClass = data.Class.FindNewClass(name, caseInsensitive); data = PromoteClassWorker(data.Class, newClass); index = data.Class.GetValueIndex(name, caseInsensitive); Debug.Assert(index != -1); } } data.Data[index] = value; } }
public override MetaObject BindDeleteMember(DeleteMemberBinder binder) { ContractUtils.RequiresNotNull(binder, "binder"); ExpandoClass klass; int index; ExpandoClass originalClass = GetClassEnsureIndex(binder.Name, binder.IgnoreCase, out klass, out index); string methodName = binder.IgnoreCase ? "ExpandoDeleteValueIgnoreCase" : "ExpandoDeleteValue"; return(new MetaObject( AddDynamicTestAndDefer( binder, new MetaObject[] { this }, klass, originalClass, Expression.Convert( Expression.Call( typeof(RuntimeOps).GetMethod(methodName), GetLimitedSelf(), Expression.Constant(klass), Expression.Constant(index) ), typeof(object) ) ), GetRestrictions() )); }
/// <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> /// Gets 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 bool DeleteValue(ExpandoClass klass, int index, bool caseInsensitive) { Debug.Assert(index != -1); lock (this) { ExpandoData data = _data; if (data.Class != klass) { // the class has changed, 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); if (index == -1) { return(false); } } object curValue = data.Data[index]; data.Data[index] = Uninitialized; return(curValue != Uninitialized); } }
/// <summary> /// Gets 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 object GetValue(ExpandoClass klass, int index, bool caseInsensitive) { Debug.Assert(index != -1); // 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; object res = Uninitialized; if (data.Class != klass) { // the class has changed, we need to get the correct index and return // the value there. index = data.Class.GetValueIndex(klass.GetIndexName(index), caseInsensitive); } // index is now known to be correct res = data.Data[index]; if (res == Uninitialized) { throw new MissingMemberException(klass.GetIndexName(index)); } return(res); }
/// <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 (this) { if (_data.Class == oldClass) { _data = new ExpandoData(newClass, newClass.GetNewKeys(_data.Data)); } return(_data); } }
public override MetaObject BindGetMember(GetMemberBinder binder) { ContractUtils.RequiresNotNull(binder, "binder"); ExpandoClass klass = Value.Class; int index = klass.GetValueIndex(binder.Name, binder.IgnoreCase); string methodName = binder.IgnoreCase ? "ExpandoGetValueIgnoreCase" : "ExpandoGetValue"; Expression target; if (index == -1) { // the key does not exist, report a MissingMemberException target = Expression.Convert( Expression.Throw( Expression.New( typeof(MissingMemberException).GetConstructor(new Type[] { typeof(string) }), Expression.Constant(binder.Name) ) ), typeof(object) ); } else { target = Expression.Call( typeof(RuntimeOps).GetMethod(methodName), GetLimitedSelf(), Expression.Constant(klass), Expression.Constant(index) ); } // add the dynamic test for the target return(new MetaObject( AddDynamicTestAndDefer( binder, new MetaObject[] { this }, klass, null, target ), GetRestrictions() )); }
/// <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 ignoreCase, out ExpandoClass klass, out int index) { ExpandoClass originalClass = Value.Class; index = originalClass.GetValueIndex(name, ignoreCase); if (index == -1) { // go ahead and find a new class now... ExpandoClass newClass = originalClass.FindNewClass(name, ignoreCase); klass = newClass; index = newClass.GetValueIndex(name, ignoreCase); Debug.Assert(index != -1); return(originalClass); } else { klass = originalClass; return(null); } }
/// <summary> /// Gets 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 object GetValue(ExpandoClass klass, int index, bool caseInsensitive) { Debug.Assert(index != -1); // 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; object res = Uninitialized; if (data.Class != klass) { // the class has changed, we need to get the correct index and return // the value there. index = data.Class.GetValueIndex(klass.GetIndexName(index), caseInsensitive); } // index is now known to be correct res = data.Data[index]; if (res == Uninitialized) { throw new MissingMemberException(klass.GetIndexName(index)); } return res; }
/// <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 an empty ExpandoData object with the empty class and no data. /// </summary> private ExpandoData() { Class = ExpandoClass.Empty; Data = new object[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 ignoreCase, out ExpandoClass klass, out int index) { ExpandoClass originalClass = Value.Class; index = originalClass.GetValueIndex(name, ignoreCase); if (index == -1) { // go ahead and find a new class now... ExpandoClass newClass = originalClass.FindNewClass(name, ignoreCase); klass = newClass; index = newClass.GetValueIndex(name, ignoreCase); Debug.Assert(index != -1); 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 Expression AddDynamicTestAndDefer(MetaObjectBinder binder, MetaObject[] args, ExpandoClass klass, ExpandoClass originalClass, Expression ifTestSucceeds) { 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), Expression.Constant(klass) ), ifTestSucceeds ); } return Expression.Condition( Expression.Call( null, typeof(RuntimeOps).GetMethod("ExpandoCheckVersion"), GetLimitedSelf(), Expression.Constant(originalClass ?? klass) ), ifTestSucceeds, binder.Defer(args).Expression ); }
/// <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> /// 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 (this) { if (_data.Class == oldClass) { _data = new ExpandoData(newClass, newClass.GetNewKeys(_data.Data)); } return _data; } }
/// <summary> /// Gets 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 bool DeleteValue(ExpandoClass klass, int index, bool caseInsensitive) { Debug.Assert(index != -1); lock (this) { ExpandoData data = _data; if (data.Class != klass) { // the class has changed, 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); if (index == -1) { return false; } } object curValue = data.Data[index]; data.Data[index] = Uninitialized; return curValue != Uninitialized; } }
/// <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 Expression AddDynamicTestAndDefer(MetaObjectBinder binder, MetaObject[] args, ExpandoClass klass, ExpandoClass originalClass, Expression ifTestSucceeds) { 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), Expression.Constant(klass) ), ifTestSucceeds ); } return(Expression.Condition( Expression.Call( null, typeof(RuntimeOps).GetMethod("ExpandoCheckVersion"), GetLimitedSelf(), Expression.Constant(originalClass ?? klass) ), ifTestSucceeds, binder.Defer(args).Expression )); }