/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * * ***************************************************************************/ #if !FEATURE_CORE_DLR using Microsoft.Scripting.Ast; using Microsoft.Scripting.Utils; #else using System.Linq.Expressions; #endif using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Dynamic; using System.Dynamic.Utils; using System.Runtime.CompilerServices; namespace System.Dynamic { /// /// Represents an object with members that can be dynamically added and removed at runtime. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] public sealed class ExpandoObject : IDynamicMetaObjectProvider, IDictionary, INotifyPropertyChanged { internal readonly object LockObject; // the readonly field is used for locking the Expando object private ExpandoData _data; // the data currently being held by the Expando object private int _count; // the count of available members internal readonly static object Uninitialized = new object(); // A marker object used to identify that a value is uninitialized. internal const int AmbiguousMatchFound = -2; // The value is used to indicate there exists ambiguous match in the Expando object internal const int NoMatch = -1; // The value is used to indicate there is no matching member private PropertyChangedEventHandler _propertyChanged; /// /// Creates a new ExpandoObject with no members. /// public ExpandoObject() { _data = ExpandoData.Empty; LockObject = new object(); } #region Get/Set/Delete Helpers /// /// 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. /// 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; } /// /// 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. /// 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])); } } /// /// Deletes the data stored for the specified class at the specified index. /// 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; } /// /// Returns true if the member at the specified index has been deleted, /// otherwise false. Call this function holding the lock. /// internal bool IsDeletedMember(int index) { Debug.Assert(index >= 0 && index <= _data.Length); if (index == _data.Length) { // The member is a newly added by SetMemberBinder and not in data yet return false; } return _data[index] == ExpandoObject.Uninitialized; } /// /// Exposes the ExpandoClass which we've associated with this /// Expando object. Used for type checks in rules. /// internal ExpandoClass Class { get { return _data.Class; } } /// /// Promotes the class from the old type to the new type and returns the new /// ExpandoData object. /// private ExpandoData PromoteClassCore(ExpandoClass oldClass, ExpandoClass newClass) { Debug.Assert(oldClass != newClass); lock (LockObject) { if (_data.Class == oldClass) { _data = _data.UpdateClass(newClass); } return _data; } } /// /// 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. /// internal void PromoteClass(object oldClass, object newClass) { PromoteClassCore((ExpandoClass)oldClass, (ExpandoClass)newClass); } #endregion #region IDynamicMetaObjectProvider Members DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) { return new MetaExpando(parameter, this); } #endregion #region Helper methods private void TryAddMember(string key, object value) { ContractUtils.RequiresNotNull(key, "key"); // Pass null to the class, which forces lookup. TrySetValue(null, -1, value, key, false, true); } private bool TryGetValueForKey(string key, out object value) { // Pass null to the class, which forces lookup. return TryGetValue(null, -1, key, false, out value); } private bool ExpandoContainsKey(string key) { return _data.Class.GetValueIndexCaseSensitive(key) >= 0; } // We create a non-generic type for the debug view for each different collection type // that uses DebuggerTypeProxy, instead of defining a generic debug view type and // using different instantiations. The reason for this is that support for generics // with using DebuggerTypeProxy is limited. For C#, DebuggerTypeProxy supports only // open types (from MSDN http://msdn.microsoft.com/en-us/library/d8eyd8zc.aspx). private sealed class KeyCollectionDebugView { private ICollection collection; public KeyCollectionDebugView(ICollection collection) { Debug.Assert(collection != null); this.collection = collection; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public string[] Items { get { string[] items = new string[collection.Count]; collection.CopyTo(items, 0); return items; } } } [DebuggerTypeProxy(typeof(KeyCollectionDebugView))] [DebuggerDisplay("Count = {Count}")] private class KeyCollection : ICollection { private readonly ExpandoObject _expando; private readonly int _expandoVersion; private readonly int _expandoCount; private readonly ExpandoData _expandoData; internal KeyCollection(ExpandoObject expando) { lock (expando.LockObject) { _expando = expando; _expandoVersion = expando._data.Version; _expandoCount = expando._count; _expandoData = expando._data; } } private void CheckVersion() { if (_expando._data.Version != _expandoVersion || _expandoData != _expando._data) { //the underlying expando object has changed throw Error.CollectionModifiedWhileEnumerating(); } } #region ICollection Members public void Add(string item) { throw Error.CollectionReadOnly(); } public void Clear() { throw Error.CollectionReadOnly(); } public bool Contains(string item) { lock (_expando.LockObject) { CheckVersion(); return _expando.ExpandoContainsKey(item); } } public void CopyTo(string[] array, int arrayIndex) { ContractUtils.RequiresNotNull(array, "array"); ContractUtils.RequiresArrayRange(array, arrayIndex, _expandoCount, "arrayIndex", "Count"); lock (_expando.LockObject) { CheckVersion(); ExpandoData data = _expando._data; for (int i = 0; i < data.Class.Keys.Length; i++) { if (data[i] != Uninitialized) { array[arrayIndex++] = data.Class.Keys[i]; } } } } public int Count { get { CheckVersion(); return _expandoCount; } } public bool IsReadOnly { get { return true; } } public bool Remove(string item) { throw Error.CollectionReadOnly(); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { for (int i = 0, n = _expandoData.Class.Keys.Length; i < n; i++) { CheckVersion(); if (_expandoData[i] != Uninitialized) { yield return _expandoData.Class.Keys[i]; } } } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } // We create a non-generic type for the debug view for each different collection type // that uses DebuggerTypeProxy, instead of defining a generic debug view type and // using different instantiations. The reason for this is that support for generics // with using DebuggerTypeProxy is limited. For C#, DebuggerTypeProxy supports only // open types (from MSDN http://msdn.microsoft.com/en-us/library/d8eyd8zc.aspx). private sealed class ValueCollectionDebugView { private ICollection collection; public ValueCollectionDebugView(ICollection collection) { Debug.Assert(collection != null); this.collection = collection; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public object[] Items { get { object[] items = new object[collection.Count]; collection.CopyTo(items, 0); return items; } } } [DebuggerTypeProxy(typeof(ValueCollectionDebugView))] [DebuggerDisplay("Count = {Count}")] private class ValueCollection : ICollection { private readonly ExpandoObject _expando; private readonly int _expandoVersion; private readonly int _expandoCount; private readonly ExpandoData _expandoData; internal ValueCollection(ExpandoObject expando) { lock (expando.LockObject) { _expando = expando; _expandoVersion = expando._data.Version; _expandoCount = expando._count; _expandoData = expando._data; } } private void CheckVersion() { if (_expando._data.Version != _expandoVersion || _expandoData != _expando._data) { //the underlying expando object has changed throw Error.CollectionModifiedWhileEnumerating(); } } #region ICollection Members public void Add(object item) { throw Error.CollectionReadOnly(); } public void Clear() { throw Error.CollectionReadOnly(); } public bool Contains(object item) { lock (_expando.LockObject) { CheckVersion(); ExpandoData data = _expando._data; for (int i = 0; i < data.Class.Keys.Length; i++) { // See comment in TryDeleteValue; it's okay to call // object.Equals with the lock held. if (object.Equals(data[i], item)) { return true; } } return false; } } public void CopyTo(object[] array, int arrayIndex) { ContractUtils.RequiresNotNull(array, "array"); ContractUtils.RequiresArrayRange(array, arrayIndex, _expandoCount, "arrayIndex", "Count"); lock (_expando.LockObject) { CheckVersion(); ExpandoData data = _expando._data; for (int i = 0; i < data.Class.Keys.Length; i++) { if (data[i] != Uninitialized) { array[arrayIndex++] = data[i]; } } } } public int Count { get { CheckVersion(); return _expandoCount; } } public bool IsReadOnly { get { return true; } } public bool Remove(object item) { throw Error.CollectionReadOnly(); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { ExpandoData data = _expando._data; for (int i = 0; i < data.Class.Keys.Length; i++) { CheckVersion(); // Capture the value into a temp so we don't inadvertently // return Uninitialized. object temp = data[i]; if (temp != Uninitialized) { yield return temp; } } } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } #endregion #region IDictionary Members ICollection IDictionary.Keys { get { return new KeyCollection(this); } } ICollection IDictionary.Values { get { return new ValueCollection(this); } } object IDictionary.this[string key] { get { object value; if (!TryGetValueForKey(key, out value)) { throw Error.KeyDoesNotExistInExpando(key); } return value; } set { ContractUtils.RequiresNotNull(key, "key"); // Pass null to the class, which forces lookup. TrySetValue(null, -1, value, key, false, false); } } void IDictionary.Add(string key, object value) { this.TryAddMember(key, value); } bool IDictionary.ContainsKey(string key) { ContractUtils.RequiresNotNull(key, "key"); ExpandoData data = _data; int index = data.Class.GetValueIndexCaseSensitive(key); return index >= 0 && data[index] != Uninitialized; } bool IDictionary.Remove(string key) { ContractUtils.RequiresNotNull(key, "key"); // Pass null to the class, which forces lookup. return TryDeleteValue(null, -1, key, false, Uninitialized); } bool IDictionary.TryGetValue(string key, out object value) { return TryGetValueForKey(key, out value); } #endregion #region ICollection> Members int ICollection>.Count { get { return _count; } } bool ICollection>.IsReadOnly { get { return false; } } void ICollection>.Add(KeyValuePair item) { TryAddMember(item.Key, item.Value); } void ICollection>.Clear() { // We remove both class and data! ExpandoData data; lock (LockObject) { data = _data; _data = ExpandoData.Empty; _count = 0; } // Notify property changed for all properties. var propertyChanged = _propertyChanged; if (propertyChanged != null) { for (int i = 0, n = data.Class.Keys.Length; i < n; i++) { if (data[i] != Uninitialized) { propertyChanged(this, new PropertyChangedEventArgs(data.Class.Keys[i])); } } } } bool ICollection>.Contains(KeyValuePair item) { object value; if (!TryGetValueForKey(item.Key, out value)) { return false; } return object.Equals(value, item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { ContractUtils.RequiresNotNull(array, "array"); ContractUtils.RequiresArrayRange(array, arrayIndex, _count, "arrayIndex", "Count"); // We want this to be atomic and not throw lock (LockObject) { foreach (KeyValuePair item in this) { array[arrayIndex++] = item; } } } bool ICollection>.Remove(KeyValuePair item) { return TryDeleteValue(null, -1, item.Key, false, item.Value); } #endregion #region IEnumerable> Member IEnumerator> IEnumerable>.GetEnumerator() { ExpandoData data = _data; return GetExpandoEnumerator(data, data.Version); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { ExpandoData data = _data; return GetExpandoEnumerator(data, data.Version); } // Note: takes the data and version as parameters so they will be // captured before the first call to MoveNext(). private IEnumerator> GetExpandoEnumerator(ExpandoData data, int version) { for (int i = 0; i < data.Class.Keys.Length; i++) { if (_data.Version != version || data != _data) { // The underlying expando object has changed: // 1) the version of the expando data changed // 2) the data object is changed throw Error.CollectionModifiedWhileEnumerating(); } // Capture the value into a temp so we don't inadvertently // return Uninitialized. object temp = data[i]; if (temp != Uninitialized) { yield return new KeyValuePair(data.Class.Keys[i], temp); } } } #endregion #region MetaExpando private class MetaExpando : DynamicMetaObject { public MetaExpando(Expression expression, ExpandoObject value) : base(expression, BindingRestrictions.Empty, value) { } private DynamicMetaObject BindGetOrInvokeMember(DynamicMetaObjectBinder binder, string name, bool ignoreCase, DynamicMetaObject fallback, Func 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); } public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { ContractUtils.RequiresNotNull(binder, "binder"); return BindGetOrInvokeMember( binder, binder.Name, binder.IgnoreCase, binder.FallbackGetMember(this), null ); } public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { ContractUtils.RequiresNotNull(binder, "binder"); return BindGetOrInvokeMember( binder, binder.Name, binder.IgnoreCase, binder.FallbackInvokeMember(this, args), value => binder.FallbackInvoke(value, args, null) ); } 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 ) ); } public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) { ContractUtils.RequiresNotNull(binder, "binder"); int index = Value.Class.GetValueIndex(binder.Name, binder.IgnoreCase, Value); Expression tryDelete = Expression.Call( typeof(RuntimeOps).GetMethod("ExpandoTryDeleteValue"), GetLimitedSelf(), Expression.Constant(Value.Class, typeof(object)), Expression.Constant(index), Expression.Constant(binder.Name), Expression.Constant(binder.IgnoreCase) ); DynamicMetaObject fallback = binder.FallbackDeleteMember(this); DynamicMetaObject target = new DynamicMetaObject( Expression.IfThen(Expression.Not(tryDelete), fallback.Expression), fallback.Restrictions ); return AddDynamicTestAndDefer(binder, Value.Class, null, target); } public override IEnumerable GetDynamicMemberNames() { var expandoData = Value._data; var klass = expandoData.Class; for (int i = 0; i < klass.Keys.Length; i++) { object val = expandoData[i]; if (val != ExpandoObject.Uninitialized) { yield return klass.Keys[i]; } } } /// /// 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. /// 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) ); } /// /// 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. /// 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; } } /// /// Returns our Expression converted to our known LimitType /// private Expression GetLimitedSelf() { if (TypeUtils.AreEquivalent(Expression.Type, LimitType)) { return Expression; } return Expression.Convert(Expression, LimitType); } /// /// Returns a Restrictions object which includes our current restrictions merged /// with a restriction limiting our type /// private BindingRestrictions GetRestrictions() { Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty"); return BindingRestrictions.GetTypeRestriction(this); } public new ExpandoObject Value { get { return (ExpandoObject)base.Value; } } } #endregion #region ExpandoData /// /// Stores the class and the data associated with the class as one atomic /// pair. This enables us to do a class check in a thread safe manner w/o /// requiring locks. /// private class ExpandoData { internal static ExpandoData Empty = new ExpandoData(); /// /// the dynamically assigned class associated with the Expando object /// internal readonly ExpandoClass Class; /// /// data stored in the expando object, key names are stored in the class. /// /// Expando._data must be locked when mutating the value. Otherwise a copy of it /// could be made and lose values. /// private readonly object[] _dataArray; /// /// Indexer for getting/setting the data /// internal object this[int index] { get { return _dataArray[index]; } set { //when the array is updated, version increases, even the new value is the same //as previous. Dictionary type has the same behavior. _version++; _dataArray[index] = value; } } internal int Version { get { return _version; } } internal int Length { get { return _dataArray.Length; } } /// /// Constructs an empty ExpandoData object with the empty class and no data. /// private ExpandoData() { Class = ExpandoClass.Empty; _dataArray = new object[0]; } /// /// the version of the ExpandoObject that tracks set and delete operations /// private int _version; /// /// Constructs a new ExpandoData object with the specified class and data. /// internal ExpandoData(ExpandoClass klass, object[] data, int version) { Class = klass; _dataArray = data; _version = version; } /// /// Update the associated class and increases the storage for the data array if needed. /// /// 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 static int GetAlignedSize(int len) { // the alignment of the array for storage of values (must be a power of two) const int DataArrayAlignment = 8; // round up and then mask off lower bits return (len + (DataArrayAlignment - 1)) & (~(DataArrayAlignment - 1)); } } #endregion #region INotifyPropertyChanged Members event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add { _propertyChanged += value; } remove { _propertyChanged -= value; } } #endregion } } namespace System.Runtime.CompilerServices { // // Note: these helpers are kept as simple wrappers so they have a better // chance of being inlined. // public static partial class RuntimeOps { /// /// Gets the value of an item in an expando object. /// /// The expando object. /// The class of the expando object. /// The index of the member. /// The name of the member. /// true if the name should be matched ignoring case; false otherwise. /// The out parameter containing the value of the member. /// True if the member exists in the expando object, otherwise false. [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] public static bool ExpandoTryGetValue(ExpandoObject expando, object indexClass, int index, string name, bool ignoreCase, out object value) { return expando.TryGetValue(indexClass, index, name, ignoreCase, out value); } /// /// Sets the value of an item in an expando object. /// /// The expando object. /// The class of the expando object. /// The index of the member. /// The value of the member. /// The name of the member. /// true if the name should be matched ignoring case; false otherwise. /// /// Returns the index for the set member. /// [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] public static object ExpandoTrySetValue(ExpandoObject expando, object indexClass, int index, object value, string name, bool ignoreCase) { expando.TrySetValue(indexClass, index, value, name, ignoreCase, false); return value; } /// /// Deletes the value of an item in an expando object. /// /// The expando object. /// The class of the expando object. /// The index of the member. /// The name of the member. /// true if the name should be matched ignoring case; false otherwise. /// true if the item was successfully removed; otherwise, false. [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] public static bool ExpandoTryDeleteValue(ExpandoObject expando, object indexClass, int index, string name, bool ignoreCase) { return expando.TryDeleteValue(indexClass, index, name, ignoreCase, ExpandoObject.Uninitialized); } /// /// Checks the version of the expando object. /// /// The expando object. /// The version to check. /// true if the version is equal; otherwise, false. [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] public static bool ExpandoCheckVersion(ExpandoObject expando, object version) { return expando.Class == version; } /// /// Promotes an expando object from one class to a new class. /// /// The expando object. /// The old class of the expando object. /// The new class of the expando object. [Obsolete("do not use this method", true), EditorBrowsable(EditorBrowsableState.Never)] public static void ExpandoPromoteClass(ExpandoObject expando, object oldClass, object newClass) { expando.PromoteClass(oldClass, newClass); } } }