< Summary

Information
Class: Hertzole.ScriptableValues.ScriptableDictionary
Assembly: Hertzole.ScriptableValues
File(s): ./Packages/se.hertzole.scriptable-values/Runtime/Collections/Core/ScriptableDictionary.cs
Tag: 52_28197466029
Line coverage
100%
Covered lines: 30
Uncovered lines: 0
Coverable lines: 30
Total lines: 1082
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ScriptableDictionary()0%110100%
add_CollectionChanged(...)0%110100%
remove_CollectionChanged(...)0%110100%
IsValid()0%110100%
IsIndexUnique(...)0%110100%
IsCollectionChangedNull()0%110100%
InvokeCollectionChangedBase(...)0%110100%
ClearBaseSubscribers()0%110100%
WarnIfLeftOverSubscribers()0%110100%

File(s)

./Packages/se.hertzole.scriptable-values/Runtime/Collections/Core/ScriptableDictionary.cs

#LineLine coverage
 1#nullable enable
 2
 3using System;
 4using System.Collections;
 5using System.Collections.Generic;
 6using System.Collections.Specialized;
 7using System.ComponentModel;
 8using System.Diagnostics;
 9using System.Diagnostics.CodeAnalysis;
 10using System.Runtime.CompilerServices;
 11using Hertzole.ScriptableValues.Helpers;
 12using UnityEngine;
 13using UnityEngine.Assertions;
 14using Debug = UnityEngine.Debug;
 15#if SCRIPTABLE_VALUES_PROPERTIES
 16using Unity.Properties;
 17#endif
 18
 19namespace Hertzole.ScriptableValues
 20{
 21    /// <summary>
 22    ///     Base class for all <see cref="ScriptableDictionary{TKey,TValue}" />.
 23    /// </summary>
 24    /// <remarks>You should probably inherit from <see cref="ScriptableDictionary{TKey,TValue}" /> instead.</remarks>
 25#if UNITY_EDITOR
 26    [HelpURL(Documentation.SCRIPTABLE_DICTIONARY_URL)]
 27#endif
 28    public abstract class ScriptableDictionary : RuntimeScriptableObject, ICanBeReadOnly, INotifyCollectionChanged
 29    {
 630        public static readonly PropertyChangedEventArgs isReadOnlyChangedEventArgs = new PropertyChangedEventArgs(nameof
 631        public static readonly PropertyChangingEventArgs isReadOnlyChangingEventArgs = new PropertyChangingEventArgs(nam
 32
 633        public static readonly PropertyChangedEventArgs setEqualityCheckChangedEventArgs = new PropertyChangedEventArgs(
 634        public static readonly PropertyChangingEventArgs setEqualityCheckChangingEventArgs = new PropertyChangingEventAr
 35
 636        public static readonly PropertyChangedEventArgs clearOnStartChangedEventArgs = new PropertyChangedEventArgs(name
 637        public static readonly PropertyChangingEventArgs clearOnStartChangingEventArgs = new PropertyChangingEventArgs(n
 38
 639        public static readonly PropertyChangedEventArgs countChangedEventArgs = new PropertyChangedEventArgs(nameof(Coun
 640        public static readonly PropertyChangingEventArgs countChangingEventArgs = new PropertyChangingEventArgs(nameof(C
 41
 42        // Internal event for the INotifyCollectionChanged interface as we don't want to expose that event directly.
 43        private event NotifyCollectionChangedEventHandler? OnInternalCollectionChanged;
 44
 45        /// <inheritdoc />
 46        event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
 47        {
 66648            add { OnInternalCollectionChanged += value; }
 63049            remove { OnInternalCollectionChanged -= value; }
 50        }
 51
 52        /// <summary>
 53        ///     If <c>true</c>, an equality check will be run before setting an item through the indexer to make sure th
 54        ///     object is not the same as the old one.
 55        /// </summary>
 56#if SCRIPTABLE_VALUES_PROPERTIES
 57        [CreateProperty]
 58#endif
 59        public abstract bool SetEqualityCheck { get; set; }
 60
 61        /// <summary>
 62        ///     If <c>true</c>, the <see cref="ScriptableDictionary{TKey,TValue}" /> will be cleared on play mode start/
 63        /// </summary>
 64#if SCRIPTABLE_VALUES_PROPERTIES
 65        [CreateProperty]
 66#endif
 67        public abstract bool ClearOnStart { get; set; }
 68
 69        /// <summary>
 70        ///     Gets the number of key/value pairs contained in the <see cref="ScriptableDictionary{TKey,TValue}" />.
 71        /// </summary>
 72#if SCRIPTABLE_VALUES_PROPERTIES
 73#if SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY
 74        [CreateProperty(ReadOnly = true)]
 75#else
 76        [CreateProperty]
 77#endif // SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY
 78#endif // SCRIPTABLE_VALUES_PROPERTIES
 79        public abstract int Count { get; protected set; }
 80
 81        /// <summary>
 82        ///     If read only, the <see cref="ScriptableDictionary{TKey,TValue}" /> cannot be changed at runtime and won'
 83        ///     cleared on start.
 84        /// </summary>
 85#if SCRIPTABLE_VALUES_PROPERTIES
 86        [CreateProperty]
 87#endif
 88        public abstract bool IsReadOnly { get; set; }
 89
 90        internal virtual bool IsValid()
 691        {
 692            return false;
 693        }
 94
 95        internal virtual bool IsIndexUnique(int index)
 696        {
 697            return false;
 698        }
 99
 100        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 101        internal bool IsCollectionChangedNull()
 2010102        {
 2010103            return OnInternalCollectionChanged == null;
 2010104        }
 105
 106        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 107        internal void InvokeCollectionChangedBase(NotifyCollectionChangedEventArgs args)
 84108        {
 84109            Assert.IsNotNull(OnInternalCollectionChanged, "Need to check for null before invoking the event using " + na
 110
 111            // The event should already be checked for null here.
 84112            OnInternalCollectionChanged!.Invoke(this, args);
 84113        }
 114
 115        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 116        internal void ClearBaseSubscribers()
 1276117        {
 1276118            OnInternalCollectionChanged = null;
 1276119        }
 120
 121        /// <inheritdoc />
 122        protected override void WarnIfLeftOverSubscribers()
 1258123        {
 1258124            base.WarnIfLeftOverSubscribers();
 1258125            EventHelper.WarnIfLeftOverSubscribers(OnInternalCollectionChanged, "INotifyCollectionChanged.CollectionChang
 1258126        }
 127
 128        /// <summary>
 129        ///     Removes all keys and values from the <see cref="ScriptableDictionary{TKey,TValue}" />.
 130        /// </summary>
 131        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 132        public abstract void Clear();
 133
 134        /// <summary>
 135        ///     Ensures that the dictionary can hold up to a specified number of entries without any further expansion o
 136        ///     backing storage.
 137        /// </summary>
 138        /// <param name="capacity">The number of entries.</param>
 139        /// <returns>The current capacity of the <see cref="ScriptableDictionary{TKey,TValue}" />.</returns>
 140        /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity" /> is less than 0.</exception>
 141        public abstract int EnsureCapacity(int capacity);
 142
 143        /// <summary>
 144        ///     Sets the capacity of this <see cref="ScriptableDictionary{TKey,TValue}" /> to what it would be if it had
 145        ///     originally initialized with all its entries.
 146        /// </summary>
 147        public abstract void TrimExcess();
 148
 149        /// <summary>
 150        ///     Sets the capacity of this <see cref="ScriptableDictionary{TKey,TValue}" /> to hold up a specified number
 151        ///     without any further expansion of its
 152        ///     backing storage.
 153        /// </summary>
 154        /// <param name="capacity">The new capacity.</param>
 155        /// <exception cref="ArgumentOutOfRangeException">
 156        ///     <paramref name="capacity" /> is less than the number of items in the
 157        ///     <see cref="ScriptableDictionary{TKey,TValue}" />.
 158        /// </exception>
 159        public abstract void TrimExcess(int capacity);
 160    }
 161
 162    /// <summary>
 163    ///     A <see cref="ScriptableObject" /> that holds a <see cref="System.Collections.Generic.Dictionary{TKey, TValue
 164    /// </summary>
 165    /// <typeparam name="TKey">The key of the dictionary.</typeparam>
 166    /// <typeparam name="TValue">The value of the dictionary.</typeparam>
 167    public abstract partial class ScriptableDictionary<TKey, TValue> : ScriptableDictionary,
 168        ISerializationCallbackReceiver,
 169        IDictionary<TKey, TValue>,
 170        IReadOnlyDictionary<TKey, TValue>,
 171        INotifyScriptableCollectionChanged<KeyValuePair<TKey, TValue>>,
 172        IDictionary where TKey : notnull
 173    {
 174        [SerializeField]
 175        [EditorTooltip("If read only, the dictionary cannot be changed at runtime and won't be cleared on start.")]
 176#if SCRIPTABLE_VALUES_PROPERTIES
 177        [DontCreateProperty]
 178#endif
 179        internal bool isReadOnly = false;
 180        [SerializeField]
 181        [EditorTooltip(
 182            "If true, an equality check will be run before setting an item through the indexer to make sure the new obje
 183#if SCRIPTABLE_VALUES_PROPERTIES
 184        [DontCreateProperty]
 185#endif
 186        internal bool setEqualityCheck = true;
 187        [SerializeField]
 188        [EditorTooltip("If true, the dictionary will be cleared on play mode start/game boot.")]
 189#if SCRIPTABLE_VALUES_PROPERTIES
 190        [DontCreateProperty]
 191#endif
 192        internal bool clearOnStart = true;
 193
 194        [SerializeField]
 195#if SCRIPTABLE_VALUES_PROPERTIES
 196        [DontCreateProperty]
 197#endif
 198        internal List<TKey> keys = new List<TKey>();
 199        [SerializeField]
 200#if SCRIPTABLE_VALUES_PROPERTIES
 201        [DontCreateProperty]
 202#endif
 203        internal List<TValue> values = new List<TValue>();
 204
 205        private int count = 0;
 206
 207        internal Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();
 208
 209        object? IDictionary.this[object key]
 210        {
 211            get { return EqualityHelper.IsSameType(key, out TKey? newKey) ? dictionary[newKey] : null; }
 212            set
 213            {
 214                Guard.IsNotNull(key, nameof(key));
 215                Guard.ThrowIfNullAndNullsAreIllegal<TValue>(value, nameof(value));
 216
 217                try
 218                {
 219                    TKey tempKey = (TKey) key;
 220                    try
 221                    {
 222                        SetValue(tempKey, (TValue) value!);
 223                    }
 224                    catch (InvalidCastException)
 225                    {
 226                        ThrowHelper.ThrowWrongExpectedValueType<TValue>(value);
 227                    }
 228                }
 229                catch (InvalidCastException)
 230                {
 231                    ThrowHelper.ThrowWrongExpectedValueType<TKey>(key);
 232                }
 233            }
 234        }
 235
 236        public TValue this[TKey key]
 237        {
 238            get { return dictionary[key]; }
 239            set { SetValue(key, value); }
 240        }
 241
 242        /// <summary>
 243        ///     Gets or sets the <see cref="IEqualityComparer{T}" /> that is used to determine equality of keys for the
 244        ///     <see cref="ScriptableDictionary{TKey,TValue}" />.
 245        /// </summary>
 246        public IEqualityComparer<TKey> Comparer
 247        {
 248            get { return dictionary.Comparer; }
 249            set
 250            {
 251                // If you update the comparer, we need to re-create the whole dictionary.
 252                // Make sure it's a new comparer than the old one.
 253                if (!EqualityComparer<IEqualityComparer<TKey>>.Default.Equals(dictionary.Comparer, value))
 254                {
 255                    Dictionary<TKey, TValue> newDictionary = new Dictionary<TKey, TValue>(dictionary, value);
 256
 257                    dictionary = newDictionary;
 258                }
 259            }
 260        }
 261
 262        /// <inheritdoc cref="ScriptableDictionary.Count" />
 263        public sealed override int Count
 264        {
 265            get
 266            {
 267                Assert.AreEqual(dictionary.Count, count, "Dictionary count is not the same as the internal count.");
 268                return count;
 269            }
 270            protected set
 271            {
 272                SetField(ref count, value, countChangingEventArgs, countChangedEventArgs);
 273                Assert.AreEqual(dictionary.Count, count);
 274            }
 275        }
 276
 277        /// <inheritdoc cref="ScriptableDictionary.IsReadOnly" />
 278        public override bool IsReadOnly
 279        {
 280            get { return isReadOnly; }
 281            set { SetField(ref isReadOnly, value, isReadOnlyChangingEventArgs, isReadOnlyChangedEventArgs); }
 282        }
 283
 284        /// <inheritdoc />
 285        public override bool SetEqualityCheck
 286        {
 287            get { return setEqualityCheck; }
 288            set { SetField(ref setEqualityCheck, value, setEqualityCheckChangingEventArgs, setEqualityCheckChangedEventA
 289        }
 290
 291        /// <inheritdoc />
 292        public override bool ClearOnStart
 293        {
 294            get { return clearOnStart; }
 295            set { SetField(ref clearOnStart, value, clearOnStartChangingEventArgs, clearOnStartChangedEventArgs); }
 296        }
 297
 298        /// <summary>
 299        ///     Gets a collection containing the keys in the dictionary.
 300        /// </summary>
 301        public Dictionary<TKey, TValue>.KeyCollection Keys
 302        {
 303            get { return dictionary.Keys; }
 304        }
 305
 306        /// <summary>
 307        ///     Gets a collection containing the values in the <see cref="ScriptableDictionary{TKey,TValue}" />.
 308        /// </summary>
 309        public Dictionary<TKey, TValue>.ValueCollection Values
 310        {
 311            get { return dictionary.Values; }
 312        }
 313
 314        bool IDictionary.IsFixedSize
 315        {
 316            get { return isReadOnly; }
 317        }
 318        bool ICollection.IsSynchronized
 319        {
 320            get { return false; }
 321        }
 322        object ICollection.SyncRoot
 323        {
 324            get { return this; }
 325        }
 326        ICollection IDictionary.Values
 327        {
 328            get { return dictionary.Values; }
 329        }
 330        ICollection IDictionary.Keys
 331        {
 332            get { return dictionary.Keys; }
 333        }
 334        ICollection<TKey> IDictionary<TKey, TValue>.Keys
 335        {
 336            get { return dictionary.Keys; }
 337        }
 338
 339        ICollection<TValue> IDictionary<TKey, TValue>.Values
 340        {
 341            get { return dictionary.Values; }
 342        }
 343
 344        IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
 345        {
 346            get { return dictionary.Keys; }
 347        }
 348        IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
 349        {
 350            get { return dictionary.Values; }
 351        }
 352
 353        /// <summary>
 354        ///     Occurs when an item is added, removed, replaced, or the entire <see cref="ScriptableDictionary{TKey, TVa
 355        ///     refreshed.
 356        /// </summary>
 357        public event CollectionChangedEventHandler<KeyValuePair<TKey, TValue>>? OnCollectionChanged;
 358
 359        /// <summary>
 360        ///     Checks if the dictionary is a valid dictionary by checking the keys and values.
 361        /// </summary>
 362        /// <returns><c>true</c> if the dictionary is valid; otherwise, <c>false</c>.</returns>
 363        internal override bool IsValid()
 364        {
 365            // If the keys and values are not the same length, the dictionary is invalid.
 366            if (keys.Count != values.Count)
 367            {
 368                return false;
 369            }
 370
 371            // Check for duplicate keys.
 372            for (int i = 0; i < keys.Count; i++)
 373            {
 374                for (int j = i; j < keys.Count; j++)
 375                {
 376                    // Ignore the same index.
 377                    if (i == j)
 378                    {
 379                        continue;
 380                    }
 381
 382                    // There's a duplicate key, the dictionary is invalid.
 383                    if (Comparer.Equals(keys[i], keys[j]))
 384                    {
 385                        return false;
 386                    }
 387                }
 388            }
 389
 390            // The dictionary was valid.
 391            return true;
 392        }
 393
 394        /// <summary>
 395        ///     Checks if a key at a specific index is unique.
 396        /// </summary>
 397        /// <param name="index">The index to check.</param>
 398        /// <returns><c>true</c> if the index is unique; otherwise, <c>false</c>.</returns>
 399        internal override bool IsIndexUnique(int index)
 400        {
 401            // Check for duplicate keys with the key at index.
 402            for (int i = 0; i < keys.Count; i++)
 403            {
 404                // Ignore the same index.
 405                if (i == index)
 406                {
 407                    continue;
 408                }
 409
 410                // A duplicate key was found, the index is invalid.
 411                if (Comparer.Equals(keys[i], keys[index]))
 412                {
 413                    return false;
 414                }
 415            }
 416
 417            // The index was valid.
 418            return true;
 419        }
 420
 421        /// <summary>
 422        ///     Adds the specified key and value to the <see cref="ScriptableDictionary{TKey, TValue}" />.
 423        /// </summary>
 424        /// <param name="key">The key of the element to add.</param>
 425        /// <param name="value">The value of the element to add. The value can be <c>null</c> for reference types.</para
 426        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 427        /// <exception cref="ArgumentException">
 428        ///     An element with the same key already exists in the
 429        ///     <see cref="ScriptableDictionary{TKey,TValue}" />
 430        /// </exception>
 431        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 432        public void Add(TKey key, TValue value)
 433        {
 434            AddInternal(key, value);
 435        }
 436
 437        /// <summary>
 438        ///     Adds the specified key and value to the <see cref="ScriptableDictionary{TKey, TValue}" />.
 439        /// </summary>
 440        /// <param name="key">The key of the element to add.</param>
 441        /// <param name="value">The value of the element to add.</param>
 442        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 443        /// <exception cref="ArgumentException">
 444        ///     An element with the same key already exists in the
 445        ///     <see cref="ScriptableDictionary{TKey,TValue}" />
 446        /// </exception>
 447        /// <exception cref="ArgumentException">
 448        ///     <paramref name="key" /> does not match <typeparamref name="TKey" /> or
 449        ///     <paramref name="value" /> does not match <typeparamref name="TValue" />
 450        /// </exception>
 451        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 452        void IDictionary.Add(object key, object? value)
 453        {
 454            Guard.ThrowIfNullAndNullsAreIllegal<TKey>(key, nameof(key));
 455            Guard.ThrowIfNullAndNullsAreIllegal<TValue>(value, nameof(value));
 456
 457            try
 458            {
 459                TKey tempKey = (TKey) key;
 460
 461                try
 462                {
 463                    AddInternal(tempKey, (TValue) value!);
 464                }
 465                catch (InvalidCastException)
 466                {
 467                    ThrowHelper.ThrowWrongExpectedValueType<TValue>(value);
 468                }
 469            }
 470            catch (InvalidCastException)
 471            {
 472                ThrowHelper.ThrowWrongExpectedValueType<TKey>(key);
 473            }
 474        }
 475
 476        /// <summary>
 477        ///     Adds a key/value pair to the <see cref="ScriptableDictionary{TKey, TValue}" />.
 478        /// </summary>
 479        /// <param name="keyValuePair">The key/value pair to add.</param>
 480        /// <exception cref="ArgumentNullException">The key of <paramref name="keyValuePair" /> is <c>null</c>.</excepti
 481        /// <exception cref="ArgumentException">
 482        ///     An element with the same key already exists in the
 483        ///     <see cref="ScriptableDictionary{TKey,TValue}" />
 484        /// </exception>
 485        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 486        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> keyValuePair)
 487        {
 488            AddInternal(keyValuePair.Key, keyValuePair.Value);
 489        }
 490
 491        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 492        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 493        /// <exception cref="ArgumentException">
 494        ///     An element with the same key already exists in the
 495        ///     <see cref="ScriptableDictionary{TKey,TValue}" />
 496        /// </exception>
 497        private void AddInternal(TKey key, TValue value)
 498        {
 499            Guard.IsNotReadOnly(this, name);
 500
 501            dictionary.Add(key, value);
 502
 503            keys.Add(key);
 504            values.Add(value);
 505
 506            Count = dictionary.Count;
 507
 508            Assert.AreEqual(dictionary.Keys.Count, keys.Count);
 509            Assert.AreEqual(dictionary.Values.Count, values.Count);
 510
 511            InvokeCollectionChanged(CollectionChangedArgs<KeyValuePair<TKey, TValue>>.Add(new KeyValuePair<TKey, TValue>
 512
 513            AddStackTrace(1);
 514        }
 515
 516        /// <inheritdoc cref="ScriptableDictionary.Clear" />
 517        public sealed override void Clear()
 518        {
 519            Guard.IsNotReadOnly(this, name);
 520
 521            if (Count == 0)
 522            {
 523                return;
 524            }
 525
 526            using (CollectionScope<KeyValuePair<TKey, TValue>> scope = new CollectionScope<KeyValuePair<TKey, TValue>>(d
 527            {
 528                dictionary.Clear();
 529
 530                keys.Clear();
 531                values.Clear();
 532
 533                Count = 0;
 534
 535                InvokeCollectionChanged(CollectionChangedArgs<KeyValuePair<TKey, TValue>>.Clear(scope.Span, -1, -1));
 536
 537                AddStackTrace();
 538            }
 539        }
 540
 541        /// <summary>
 542        ///     Determines whether the <see cref="ScriptableDictionary{TKey,TValue}" /> contains the specified key.
 543        /// </summary>
 544        /// <param name="key">The key to locate in the <see cref="ScriptableDictionary{TKey,TValue}" />.</param>
 545        /// <returns>
 546        ///     <c>true</c> if the <see cref="ScriptableDictionary{TKey,TValue}" /> contains an element with the specifi
 547        ///     key; otherwise, <c>false</c>.
 548        /// </returns>
 549        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 550        public bool ContainsKey(TKey key)
 551        {
 552            return dictionary.ContainsKey(key);
 553        }
 554
 555        /// <summary>
 556        ///     Determines whether the <see cref="ScriptableDictionary{TKey,TValue}" /> contains a specific value.
 557        /// </summary>
 558        /// <param name="value">
 559        ///     The value to locate in the <see cref="ScriptableDictionary{TKey,TValue}" />. The value can be
 560        ///     <c>null</c> for reference types.
 561        /// </param>
 562        /// <returns>
 563        ///     <c>true</c> if the <see cref="ScriptableDictionary{TKey,TValue}" /> contains the value; otherwise,
 564        ///     <c>false</c>.
 565        /// </returns>
 566        public bool ContainsValue(TValue value)
 567        {
 568            return dictionary.ContainsValue(value);
 569        }
 570
 571        /// <summary>
 572        ///     Determines whether the <see cref="ScriptableDictionary{TKey,TValue}" /> contains an element with the spe
 573        /// </summary>
 574        /// <param name="key">The key to locate in the <see cref="ScriptableDictionary{TKey,TValue}" />.</param>
 575        /// <returns>
 576        ///     <c>true</c> if the key type is the same as <typeparamref name="TKey" /> and the
 577        ///     <see cref="ScriptableDictionary{TKey,TValue}" /> contains the key; otherwise, <c>false</c>.
 578        /// </returns>
 579        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 580        bool IDictionary.Contains(object key)
 581        {
 582            return EqualityHelper.IsSameType(key, out TKey? newKey) && ContainsKey(newKey);
 583        }
 584
 585        /// <summary>
 586        ///     Determines whether the <see cref="ScriptableDictionary{TKey,TValue}" /> contains a specific key and valu
 587        /// </summary>
 588        /// <param name="item">
 589        ///     The <see cref="KeyValuePair{TKey,TValue}" /> structure to locate in the
 590        ///     <see cref="ScriptableDictionary{TKey,TValue}" />
 591        /// </param>
 592        /// <returns><c>true</c> if the key and value both exist in the dictionary; otherwise, <c>false</c>.</returns>
 593        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
 594        {
 595            return dictionary.ContainsKey(item.Key) && dictionary.ContainsValue(item.Value);
 596        }
 597
 598        /// <summary>
 599        ///     Copies the elements of the <see cref="ScriptableDictionary{TKey,TValue}" /> to an <see cref="Array" />, 
 600        ///     the specified array index.
 601        /// </summary>
 602        /// <param name="array">The destination array.</param>
 603        /// <param name="index">The index at which copying beings.</param>
 604        void ICollection.CopyTo(Array array, int index)
 605        {
 606            Guard.IsNotNull(array, nameof(array));
 607
 608            ((ICollection) dictionary).CopyTo(array, index);
 609        }
 610
 611        /// <summary>
 612        ///     Copies the elements of the dictionary to an <see cref="Array" />, starting at a particular array index.
 613        /// </summary>
 614        /// <param name="array">The destination array.</param>
 615        /// <param name="arrayIndex">The index at which copying beings.</param>
 616        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
 617        {
 618            Guard.IsNotNull(array, nameof(array));
 619
 620            ((ICollection<KeyValuePair<TKey, TValue>>) dictionary).CopyTo(array, arrayIndex);
 621        }
 622
 623        /// <inheritdoc />
 624        public sealed override int EnsureCapacity(int capacity)
 625        {
 626            int result = dictionary.EnsureCapacity(capacity);
 627            if (keys.Capacity < capacity)
 628            {
 629                keys.Capacity = capacity;
 630            }
 631
 632            if (values.Capacity < capacity)
 633            {
 634                values.Capacity = capacity;
 635            }
 636
 637            return result;
 638        }
 639
 640        /// <summary>
 641        ///     Returns an enumerator that iterates through the <see cref="ScriptableDictionary{TKey,TValue}" />.
 642        /// </summary>
 643        /// <returns>
 644        ///     A <see cref="Dictionary{TKey, TValue}.Enumerator" /> structure for the
 645        ///     <see cref="ScriptableDictionary{TKey,TValue}" />.
 646        /// </returns>
 647        public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
 648        {
 649            return dictionary.GetEnumerator();
 650        }
 651
 652        /// <summary>
 653        ///     Returns an enumerator that iterates through the dictionary.
 654        /// </summary>
 655        /// <returns>A <see cref="Dictionary{TKey, TValue}.Enumerator" /> for the dictionary.</returns>
 656        IDictionaryEnumerator IDictionary.GetEnumerator()
 657        {
 658            return ((IDictionary) dictionary).GetEnumerator();
 659        }
 660
 661        /// <summary>
 662        ///     Returns an enumerator that iterates through the dictionary.
 663        /// </summary>
 664        /// <returns>A <see cref="Dictionary{TKey, TValue}.Enumerator" /> for the dictionary.</returns>
 665        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
 666        {
 667            return ((IEnumerable<KeyValuePair<TKey, TValue>>) dictionary).GetEnumerator();
 668        }
 669
 670        /// <summary>
 671        ///     Returns an enumerator that iterates through the dictionary.
 672        /// </summary>
 673        /// <returns>A <see cref="Dictionary{TKey, TValue}.Enumerator" /> for the dictionary.</returns>
 674        IEnumerator IEnumerable.GetEnumerator()
 675        {
 676            return ((IEnumerable) dictionary).GetEnumerator();
 677        }
 678
 679        /// <summary>
 680        ///     Removes the value with the specified key from the <see cref="ScriptableDictionary{TKey,TValue}" />.
 681        /// </summary>
 682        /// <param name="key">The key of the element to remove.</param>
 683        /// <returns>
 684        ///     <c>true</c> if the element is successfully found and removed; otherwise, <c>false</c>.
 685        ///     This method returns <c>false</c> if the key is not found in the <see cref="ScriptableDictionary{TKey,TVa
 686        /// </returns>
 687        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 688        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 689        public bool Remove(TKey key)
 690        {
 691            return RemoveInternal(key, out _);
 692        }
 693
 694        /// <summary>
 695        ///     Removes the value with the specified key from the <see cref="ScriptableDictionary{TKey,TValue}" />, and 
 696        ///     element to the <paramref name="value" /> parameter.
 697        /// </summary>
 698        /// <param name="key">The key of the element to remove.</param>
 699        /// <param name="value">
 700        ///     When this method returns, contains the value associated with the specified key, if the key is found;
 701        ///     otherwise, the default value for the type of the <paramref name="value" /> parameter. This parameter is 
 702        ///     uninitialized.
 703        /// </param>
 704        /// <returns><c>true</c> if the element is successfully found and removed; otherwise, <c>false</c>.</returns>
 705        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 706        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 707        public bool Remove(TKey key, out TValue value)
 708        {
 709            return RemoveInternal(key, out value);
 710        }
 711
 712        /// <summary>
 713        ///     Removes the value with the specified key from the dictionary.
 714        /// </summary>
 715        /// <param name="key">The key of the element to remove.</param>
 716        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 717        void IDictionary.Remove(object key)
 718        {
 719            if (EqualityHelper.IsSameType(key, out TKey? newKey))
 720            {
 721                RemoveInternal(newKey, out _);
 722            }
 723        }
 724
 725        /// <summary>
 726        ///     Removes a key/value pair from the dictionary.
 727        /// </summary>
 728        /// <param name="item">The key/value pair to remove.</param>
 729        /// <returns><c>true</c> if the key/value pair was found and removed; otherwise, <c>false</c>.</returns>
 730        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 731        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
 732        {
 733            if (dictionary.TryGetValue(item.Key, out TValue value) && EqualityHelper.Equals(item.Value, value))
 734            {
 735                return RemoveInternal(item.Key, out _);
 736            }
 737
 738            return false;
 739        }
 740
 741        /// <exception cref="ArgumentNullException"><c>key</c> is <c>null</c>.</exception>
 742        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 743        private bool RemoveInternal(TKey key, out TValue value)
 744        {
 745            Guard.IsNotReadOnly(this, name);
 746
 747            bool removed = dictionary.Remove(key, out value);
 748            if (removed)
 749            {
 750                keys.Remove(key);
 751                values.Remove(value);
 752
 753                Count = dictionary.Count;
 754
 755                Assert.AreEqual(dictionary.Keys.Count, keys.Count);
 756                Assert.AreEqual(dictionary.Values.Count, values.Count);
 757
 758                InvokeCollectionChanged(CollectionChangedArgs<KeyValuePair<TKey, TValue>>.Remove(new KeyValuePair<TKey, 
 759
 760                AddStackTrace(1);
 761            }
 762
 763            return removed;
 764        }
 765
 766        /// <summary>
 767        ///     Sets or adds a value to the dictionary.
 768        /// </summary>
 769        /// <param name="key">The key of the element to set.</param>
 770        /// <param name="value">The new value to set.</param>
 771        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 772        private void SetValue(TKey key, TValue value)
 773        {
 774            Guard.IsNotReadOnly(this, name);
 775
 776            // Try to add it first. If it fails, we update the value.
 777            if (!TryAddInternal(key, value))
 778            {
 779                // Get the old value from the dictionary.
 780                TValue? oldValue = dictionary[key];
 781
 782                // If the equality check is enabled, we don't want to set the value if it's the same as the current valu
 783                if (setEqualityCheck && EqualityHelper.Equals(oldValue, value))
 784                {
 785                    return;
 786                }
 787
 788                // Get the index of the value.
 789                int valueIndex = values.IndexOf(oldValue);
 790                Assert.AreNotEqual(-1, valueIndex, "The value was not found in the values list.");
 791
 792                // Update the value in the values list.
 793                values[valueIndex] = value;
 794
 795                // Update the value in the dictionary.
 796                dictionary[key] = value;
 797
 798                KeyValuePair<TKey, TValue> oldItem = new KeyValuePair<TKey, TValue>(key, oldValue);
 799                KeyValuePair<TKey, TValue> newItem = new KeyValuePair<TKey, TValue>(key, value);
 800
 801                InvokeCollectionChanged(CollectionChangedArgs<KeyValuePair<TKey, TValue>>.Replace(oldItem, newItem, -1))
 802
 803                AddStackTrace();
 804            }
 805        }
 806
 807        /// <inheritdoc />
 808        public sealed override void TrimExcess()
 809        {
 810            dictionary.TrimExcess();
 811
 812            keys.TrimExcess();
 813            values.TrimExcess();
 814
 815            AddStackTrace();
 816        }
 817
 818        /// <inheritdoc />
 819        public sealed override void TrimExcess(int capacity)
 820        {
 821            dictionary.TrimExcess(capacity);
 822
 823            keys.TrimExcess();
 824            values.TrimExcess();
 825
 826            AddStackTrace();
 827        }
 828
 829        /// <summary>
 830        ///     Attempts to add the specified key and value to the <see cref="ScriptableDictionary{TKey,TValue}" />.
 831        /// </summary>
 832        /// <param name="key">The key of the element to add.</param>
 833        /// <param name="value">The value of the element to add. The value can be <c>null</c> for reference types.</para
 834        /// <returns>
 835        ///     <c>true</c> if the key/value pair was added to the <see cref="ScriptableDictionary{TKey,TValue}" />
 836        ///     successfully; <c>false</c> if the key already exists.
 837        /// </returns>
 838        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 839        /// <exception cref="System.Data.ReadOnlyException">If the object is read-only and the application is playing.</
 840        public bool TryAdd(TKey key, TValue value)
 841        {
 842            Guard.IsNotReadOnly(this, name);
 843
 844            return TryAddInternal(key, value);
 845        }
 846
 847        private bool TryAddInternal(TKey key, TValue value)
 848        {
 849            Assert.IsFalse(isReadOnly, "Trying to add to a read-only dictionary.");
 850
 851            bool result = dictionary.TryAdd(key, value);
 852
 853            if (result)
 854            {
 855                // We must update the lists too so they are in sync with the dictionary.
 856                keys.Add(key);
 857                values.Add(value);
 858
 859                Count = dictionary.Count;
 860
 861                Assert.AreEqual(dictionary.Keys.Count, keys.Count);
 862                Assert.AreEqual(dictionary.Values.Count, values.Count);
 863
 864                InvokeCollectionChanged(CollectionChangedArgs<KeyValuePair<TKey, TValue>>.Add(new KeyValuePair<TKey, TVa
 865
 866                AddStackTrace(1);
 867            }
 868
 869            return result;
 870        }
 871
 872        /// <summary>
 873        ///     Tries to find a key in the <see cref="ScriptableDictionary{TKey,TValue}" /> that matches the specified p
 874        /// </summary>
 875        /// <param name="predicate">The <see cref="Predicate{T}" /> to check against.</param>
 876        /// <param name="key">
 877        ///     When this method returns, contains the key if it was found. Will be the default value if it wasn't
 878        ///     found.
 879        /// </param>
 880        /// <returns><c>true</c> if a key matching the predicate was found; otherwise, <c>false</c>.</returns>
 881        /// <exception cref="ArgumentNullException"><paramref name="predicate" /> is <c>null</c>.</exception>
 882        public bool TryFindKey(Predicate<TKey> predicate, [NotNullWhen(true)] out TKey? key)
 883        {
 884            Guard.IsNotNull(predicate, nameof(predicate));
 885
 886            foreach (TKey dictionaryKey in dictionary.Keys)
 887            {
 888                if (predicate(dictionaryKey))
 889                {
 890                    key = dictionaryKey;
 891                    return true;
 892                }
 893            }
 894
 895            key = default;
 896            return false;
 897        }
 898
 899        /// <summary>
 900        ///     Tries to find a value in the <see cref="ScriptableDictionary{TKey,TValue}" /> that matches the specified
 901        /// </summary>
 902        /// <param name="predicate">The <see cref="Predicate{T}" /> to check against.</param>
 903        /// <param name="value">
 904        ///     When this method returns, contains the value if it was found. Will be the default value if it
 905        ///     wasn't found.
 906        /// </param>
 907        /// <returns><c>true</c> if a value matching the predicate was found; otherwise, <c>false</c>.</returns>
 908        /// <exception cref="ArgumentNullException"><paramref name="predicate" /> is <c>null</c>.</exception>
 909        public bool TryFindValue(Predicate<TValue> predicate, out TValue? value)
 910        {
 911            Guard.IsNotNull(predicate, nameof(predicate));
 912
 913            foreach (TValue dictionaryValue in dictionary.Values)
 914            {
 915                if (predicate(dictionaryValue))
 916                {
 917                    value = dictionaryValue;
 918                    return true;
 919                }
 920            }
 921
 922            value = default;
 923            return false;
 924        }
 925
 926        /// <summary>
 927        ///     Gets the value associated with the specified key.
 928        /// </summary>
 929        /// <param name="key">The key of the value to get.</param>
 930        /// <param name="value">
 931        ///     When this method returns, contains the value associated with the specified key, if the key is
 932        ///     found; otherwise, the default value for the type of the <paramref name="value" /> parameter. This parame
 933        ///     passed uninitialized.
 934        /// </param>
 935        /// <returns>
 936        ///     <c>true</c> if the <see cref="ScriptableDictionary{TKey,TValue}" /> contains an element with the specifi
 937        ///     key; otherwise, <c>false</c>.
 938        /// </returns>
 939        /// <exception cref="ArgumentNullException"><paramref name="key" /> is <c>null</c>.</exception>
 940        public bool TryGetValue(TKey key, out TValue value)
 941        {
 942            return dictionary.TryGetValue(key, out value);
 943        }
 944
 945        private void InvokeCollectionChanged(CollectionChangedArgs<KeyValuePair<TKey, TValue>> args)
 946        {
 947            OnCollectionChanged?.Invoke(args);
 948            if (!IsCollectionChangedNull())
 949            {
 950                InvokeCollectionChangedBase(args.ToNotifyCollectionChangedEventArgs());
 951            }
 952        }
 953
 954        void ISerializationCallbackReceiver.OnBeforeSerialize()
 955        {
 956            // Does nothing.
 957        }
 958
 959        /// <summary>
 960        ///     Called after Unity has deserialized this type.
 961        /// </summary>
 962        void ISerializationCallbackReceiver.OnAfterDeserialize()
 963        {
 964#if DEBUG
 965            // If the dictionary is invalid, stop here.
 966            if (!IsValid())
 967            {
 968                return;
 969            }
 970#endif
 971
 972            // Update the dictionary with the serialized keys and values.
 973            dictionary.Clear();
 974            dictionary.EnsureCapacity(keys.Count);
 975
 976            for (int i = 0; i < keys.Count; i++)
 977            {
 978                dictionary.Add(keys[i], values[i]);
 979            }
 980
 981            Count = dictionary.Count;
 982        }
 983
 984        /// <inheritdoc />
 985        protected override void OnPreStart()
 986        {
 987            ClearSubscribers();
 988
 989            if (!isReadOnly && clearOnStart)
 990            {
 991                dictionary.Clear();
 992                keys.Clear();
 993                values.Clear();
 994                Count = 0;
 995            }
 996        }
 997
 998        /// <summary>
 999        ///     Warns if there are any left-over subscribers to the events.
 1000        /// </summary>
 1001        /// <remarks>This will only be called in the Unity editor and builds with the DEBUG flag.</remarks>
 1002        protected override void WarnIfLeftOverSubscribers()
 1003        {
 1004            base.WarnIfLeftOverSubscribers();
 1005            EventHelper.WarnIfLeftOverSubscribers(OnCollectionChanged, nameof(OnCollectionChanged), this);
 1006        }
 1007
 1008        /// <summary>
 1009        ///     Warns if there are any left-over objects in the dictionary.
 1010        /// </summary>
 1011        /// <remarks>This will only be called in the Unity editor and builds with the DEBUG flag.</remarks>
 1012        [Conditional("DEBUG")]
 1013        protected void WarnIfLeftOverObjects()
 1014        {
 1015            if (!isReadOnly && clearOnStart && dictionary.Count > 0)
 1016            {
 1017                Debug.LogWarning($"There are left over objects in the scriptable dictionary {name}. You should clear the
 1018            }
 1019        }
 1020
 1021        /// <summary>
 1022        ///     Removes any subscribers from the event.
 1023        /// </summary>
 1024        /// <param name="warnIfLeftOver">
 1025        ///     If true, a warning will be printed in the console if there are any subscribers.
 1026        ///     The warning will only be printed in the editor and debug builds.
 1027        /// </param>
 1028        public void ClearSubscribers(bool warnIfLeftOver = false)
 1029        {
 1030#if DEBUG
 1031            if (warnIfLeftOver)
 1032            {
 1033                WarnIfLeftOverSubscribers();
 1034            }
 1035#endif
 1036
 1037            OnCollectionChanged = null;
 1038            ClearBaseSubscribers();
 1039        }
 1040
 1041        /// <inheritdoc />
 1042        protected override void OnDisabled()
 1043        {
 1044            WarnIfLeftOverObjects();
 1045            WarnIfLeftOverSubscribers();
 1046
 1047            dictionary.TrimExcess();
 1048        }
 1049
 1050        #region Obsolete
 1051#if UNITY_EDITOR // Don't include in builds.
 1052        /// <summary>
 1053        ///     Called when an item was added. Gives you the key and value of the newly added item.
 1054        /// </summary>
 1055        [Obsolete("Use 'OnCollectionChanged' instead. This will be removed in build.", true)]
 1056#pragma warning disable CS0067 // Event is never used
 1057        public event Action<TKey, TValue>? OnAdded;
 1058        /// <summary>
 1059        ///     Called when an item was set. Gives you the key, the old value, and the new value of the item.
 1060        /// </summary>
 1061        [Obsolete("Use 'OnCollectionChanged' instead. This will be removed in build.", true)]
 1062        public event Action<TKey, TValue, TValue>? OnSet;
 1063        /// <summary>
 1064        ///     Called when an item was removed. Gives you the key and value of the removed item.
 1065        /// </summary>
 1066        [Obsolete("Use 'OnCollectionChanged' instead. This will be removed in build.", true)]
 1067        public event Action<TKey, TValue>? OnRemoved;
 1068        /// <summary>
 1069        ///     Called when the dictionary is cleared.
 1070        /// </summary>
 1071        [Obsolete("Use 'OnCollectionChanged' instead. This will be removed in build.", true)]
 1072        public event Action? OnCleared;
 1073        /// <summary>
 1074        ///     Called when the dictionary is changed in any way.
 1075        /// </summary>
 1076        [Obsolete("Use 'OnCollectionChanged' instead. This will be removed in build.", true)]
 1077        public event Action<DictionaryChangeType>? OnChanged;
 1078#pragma warning restore CS0067 // Event is never used
 1079#endif // UNITY_EDITOR
 1080        #endregion
 1081    }
 1082}