1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A
6 * copy of the license can be found in the License.html file at the root of this distribution. If
7 * you cannot locate the Apache License, Version 2.0, please send an email to
8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9 * by the terms of the Apache License, Version 2.0.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
17 using System.Collections.Generic;
18 using System.Diagnostics;
19 using System.Dynamic.Utils;
22 namespace System.Dynamic {
24 /// Represents a dynamically assigned class. Expando objects which share the same
25 /// members will share the same class. Classes are dynamically assigned as the
26 /// expando object gains members.
28 internal class ExpandoClass {
29 private readonly string[] _keys; // list of names associated with each element in the data array, sorted
30 private readonly int _hashCode; // pre-calculated hash code of all the keys the class contains
31 private Dictionary<int, List<WeakReference>> _transitions; // cached transitions
33 private const int EmptyHashCode = 6551; // hash code of the empty ExpandoClass.
35 internal static ExpandoClass Empty = new ExpandoClass(); // The empty Expando class - all Expando objects start off w/ this class.
38 /// Constructs the empty ExpandoClass. This is the class used when an
39 /// empty Expando object is initially constructed.
41 internal ExpandoClass() {
42 _hashCode = EmptyHashCode;
43 _keys = new string[0];
47 /// Constructs a new ExpandoClass that can hold onto the specified keys. The
48 /// keys must be sorted ordinally. The hash code must be precalculated for
51 internal ExpandoClass(string[] keys, int hashCode) {
57 /// Finds or creates a new ExpandoClass given the existing set of keys
58 /// in this ExpandoClass plus the new key to be added. Members in an
59 /// ExpandoClass are always stored case sensitively.
61 internal ExpandoClass FindNewClass(string newKey) {
62 // just XOR the newKey hash code
63 int hashCode = _hashCode ^ newKey.GetHashCode();
66 List<WeakReference> infos = GetTransitionList(hashCode);
68 for (int i = 0; i < infos.Count; i++) {
69 ExpandoClass klass = infos[i].Target as ExpandoClass;
76 if (string.Equals(klass._keys[klass._keys.Length - 1], newKey, StringComparison.Ordinal)) {
77 // the new key is the key we added in this transition
82 // no applicable transition, create a new one
83 string[] keys = new string[_keys.Length + 1];
84 Array.Copy(_keys, keys, _keys.Length);
85 keys[_keys.Length] = newKey;
86 ExpandoClass ec = new ExpandoClass(keys, hashCode);
88 infos.Add(new WeakReference(ec));
94 /// Gets the lists of transitions that are valid from this ExpandoClass
95 /// to an ExpandoClass whos keys hash to the apporopriate hash code.
97 private List<WeakReference> GetTransitionList(int hashCode) {
98 if (_transitions == null) {
99 _transitions = new Dictionary<int, List<WeakReference>>();
102 List<WeakReference> infos;
103 if (!_transitions.TryGetValue(hashCode, out infos)) {
104 _transitions[hashCode] = infos = new List<WeakReference>();
111 /// Gets the index at which the value should be stored for the specified name.
113 internal int GetValueIndex(string name, bool caseInsensitive, ExpandoObject obj) {
114 if (caseInsensitive) {
115 return GetValueIndexCaseInsensitive(name, obj);
117 return GetValueIndexCaseSensitive(name);
122 /// Gets the index at which the value should be stored for the specified name
123 /// case sensitively. Returns the index even if the member is marked as deleted.
125 internal int GetValueIndexCaseSensitive(string name) {
126 for (int i = 0; i < _keys.Length; i++) {
130 StringComparison.Ordinal)) {
134 return ExpandoObject.NoMatch;
138 /// Gets the index at which the value should be stored for the specified name,
139 /// the method is only used in the case-insensitive case.
141 /// <param name="name">the name of the member</param>
142 /// <param name="obj">The ExpandoObject associated with the class
143 /// that is used to check if a member has been deleted.</param>
145 /// the exact match if there is one
146 /// if there is exactly one member with case insensitive match, return it
147 /// otherwise we throw AmbiguousMatchException.
149 private int GetValueIndexCaseInsensitive(string name, ExpandoObject obj) {
150 int caseInsensitiveMatch = ExpandoObject.NoMatch; //the location of the case-insensitive matching member
151 lock (obj.LockObject) {
152 for (int i = _keys.Length - 1; i >= 0; i--) {
156 StringComparison.OrdinalIgnoreCase)) {
157 //if the matching member is deleted, continue searching
158 if (!obj.IsDeletedMember(i)) {
159 if (caseInsensitiveMatch == ExpandoObject.NoMatch) {
160 caseInsensitiveMatch = i;
162 //Ambigous match, stop searching
163 return ExpandoObject.AmbiguousMatchFound;
169 //There is exactly one member with case insensitive match.
170 return caseInsensitiveMatch;
174 /// Gets the names of the keys that can be stored in the Expando class. The
175 /// list is sorted ordinally.
177 internal string[] Keys {