/// <summary> /// Sets a backing field for a list of identifiable objects that should have unique <see cref="System.Int32"/> /// keys. Use this method when you need to serialize something that may be deserialized as a dictionary. /// </summary> /// <returns> /// <see langword="true"/> if the new value differs from the old one; otherwise, <see langword="false"/>. /// </returns> /// <param name="backingField">Backing field of identifiable objects.</param> /// <param name="value">Value.</param> /// <param name="mutateIdentifier">A method to mutate the identifier on an object if it is not unique.</param> /// <param name="mode">Mode.</param> /// <typeparam name="T">An integer-keyed backing field compatible object wrapper type.</typeparam> public static bool SetKeyedListBackingFieldFromIntKeyedArray <T>( List <T> backingField, IList <T> value, System.Func <int, T, T> mutateIdentifier, IntKeyMode mode = IntKeyMode.Increment ) where T : IIdentifiable <int> { return(SetKeyedListBackingFieldFromArray <T, int>(backingField, value, mutateIdentifier, false, mode)); }
/// <summary> /// Sets a backing field for a set of <see cref="System.Int32"/>s. /// </summary> /// <returns> /// <see langword="true"/> if the new value differs from the old one; otherwise, <see langword="false"/>. /// </returns> /// <param name="backingField">Backing field of <see cref="System.Int32"/> elements.</param> /// <param name="value">Value.</param> /// <param name="mode">Mode.</param> public static bool SetHashedListBackingFieldFromIntArray( List <int> backingField, IList <int> value, IntKeyMode mode = IntKeyMode.Increment ) { return(SetHashedListBackingFieldFromArray <int>(backingField, value, false, mode)); }
/// <summary> /// Sets a backing field for a list of identifiable objects that should have unique keys. Use this method when /// you need to serialize something that may be deserialized as a dictionary. /// </summary> /// <returns> /// <see langword="true"/> if the new value differs from the old one; otherwise, <see langword="false"/>. /// </returns> /// <param name="backingField">Backing field of identifiable objects.</param> /// <param name="value">Value.</param> /// <param name="mutateIdentifier">A method to mutate the identifier on an object if it is not unique.</param> /// <param name="ignoreCase"> /// If set to <see langword="true"/>, then all string-based keys will be made lowercase. /// </param> /// <param name="intKeyMode">Mode for handling duplicate <see cref="System.Int32"/> keys.</param> /// <typeparam name="T"> /// A type with a <see cref="System.Int32"/>, <see cref="System.String"/>, or <see cref="UnityEngine.Object"/> /// identifier. /// </typeparam> /// <typeparam name="TId"> /// The identifier type (either <see cref="UnityEngine.Object"/>-derived, <see cref="System.Int32"/>, or /// <see cref="System.String"/>). /// </typeparam> private static bool SetKeyedListBackingFieldFromArray <T, TId>( List <T> backingField, IList <T> value, System.Func <TId, T, T> mutateIdentifier, bool ignoreCase, IntKeyMode intKeyMode = IntKeyMode.Increment ) where T : IIdentifiable <TId> { if (value == null || value.Count == 0) { int oldCount = backingField.Count; backingField.Clear(); return(oldCount != 0); } using (ListPool <T> .Scope oldValue = new ListPool <T> .Scope()) { oldValue.List.AddRange(backingField); backingField.Clear(); bool isString = typeof(TId) == typeof(string); bool isInt = !isString && typeof(TId) == typeof(int); if (!isInt && !isString) // NOTE: WinRT cannot use Type.IsAssignableFrom() { int nullHash = 0; int nullIndex = backingField.FindIndex(item => item.Identifier.GetHashCode() == nullHash); foreach (T newValue in value) { if (newValue == null) { if (nullIndex < 0) { backingField.Add(newValue); nullIndex = backingField.Count - 1; } } else if ( !object.ReferenceEquals(newValue.Identifier, default(TId)) && backingField.FindIndex( item => item.Identifier.GetHashCode() == newValue.Identifier.GetHashCode() ) < 0 ) { backingField.Add(newValue); if (newValue.Identifier.GetHashCode() == nullHash) { nullIndex = backingField.Count - 1; } } else if (nullIndex < 0) { backingField.Add(mutateIdentifier(default(TId), newValue)); nullIndex = backingField.Count - 1; } } } else { using (var keyedValues = new DictPool <TId, T> .Scope()) { using (var stringKeys = new HashPool <string> .Scope()) { TId id; for (int i = 0; i < value.Count; ++i) { id = value[i] == null ? default(TId) : value[i].Identifier; if (isString) { string key = (string)(object)id ?? string.Empty; if (ignoreCase && key.ContainsUppercase()) { key = key.ToLower(); } key = key.GetUniqueName(stringKeys.HashSet); stringKeys.HashSet.Add(key); id = (TId)(object)key; } else if (isInt) { switch (intKeyMode) { case IntKeyMode.Increment: while (keyedValues.Dict.ContainsKey(id)) { id = (TId)(object)((int)(object)id + 1); } break; case IntKeyMode.SetToZero: if (keyedValues.Dict.ContainsKey(id)) { id = (TId)(object)0; if (keyedValues.Dict.ContainsKey(id)) { continue; } } break; } } keyedValues.Dict[id] = value[i]; } foreach (KeyValuePair <TId, T> kv in keyedValues.Dict) { backingField.Add(mutateIdentifier(kv.Key, kv.Value)); } } } } return(!oldValue.List.SequenceEqual(backingField)); } }
/// <summary> /// Sets a backing field for a set of objects. /// </summary> /// <returns> /// <see langword="true"/> if the new value differs from the old one; otherwise, <see langword="false"/>. /// </returns> /// <param name="backingField">Backing field.</param> /// <param name="value">Value.</param> /// <param name="ignoreCase"> /// If set to <see langword="true"/>, then all string elements keys will be made lowercase. /// </param> /// <param name="intKeyMode">Mode for handling duplicate <see cref="System.Int32"/> keys.</param> /// <typeparam name="T"> /// The element type (either <see cref="UnityEngine.Object"/>-derived, <see cref="System.Int32"/>, or /// <see cref="System.String"/>). /// </typeparam> private static bool SetHashedListBackingFieldFromArray <T>( List <T> backingField, IList <T> value, bool ignoreCase, IntKeyMode intKeyMode = IntKeyMode.Increment ) { if (value == null || value.Count == 0) { int oldCount = backingField.Count; backingField.Clear(); return(oldCount != 0); } T[] oldValue = backingField.ToArray(); backingField.Clear(); bool isString = typeof(T) == typeof(string); bool isInt = !isString && typeof(T) == typeof(int); if (!isInt && !isString) // NOTE: WinRT cannot use Type.IsAssignableFrom() { int nullHash = 0; int nullIndex = backingField.FindIndex(item => item.GetHashCode() == nullHash); foreach (T newValue in value) { if ( !object.ReferenceEquals(newValue, default(T)) && backingField.FindIndex(item => item.GetHashCode() == newValue.GetHashCode()) < 0 ) { backingField.Add(newValue); if (newValue.GetHashCode() == nullHash) { nullIndex = backingField.Count - 1; } } else if (nullIndex < 0) { backingField.Add(default(T)); nullIndex = backingField.Count - 1; } } } else { using (var hashedValues = new HashPool <T> .Scope()) { using (var stringKeys = new HashPool <string> .Scope()) { for (int i = 0; i < value.Count; ++i) { T id = value[i]; if (isString) { string key = (string)(object)id ?? string.Empty; if (ignoreCase && key.ContainsUppercase()) { key = key.ToLower(); } key = key.GetUniqueName(stringKeys.HashSet); stringKeys.HashSet.Add(key); id = (T)(object)key; } else if (isInt) { switch (intKeyMode) { case IntKeyMode.Increment: while (hashedValues.HashSet.Contains(id)) { id = (T)(object)((int)(object)id + 1); } break; case IntKeyMode.SetToZero: if (hashedValues.HashSet.Contains(id)) { id = (T)(object)0; if (hashedValues.HashSet.Contains(id)) { continue; } } break; } } hashedValues.HashSet.Add(id); } foreach (T hashedValue in hashedValues.HashSet) { backingField.Add(hashedValue); } } } } return(!oldValue.SequenceEqual(backingField)); }