/// <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)); }
/// <summary> /// Deletes the data stored for the specified class at the specified index. /// </summary> 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); }
/// <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 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 race 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); }