// Lucene version compatibility level 4.8.1
using J2N;
using J2N.Globalization;
using J2N.Text;
using Lucene.Net.Diagnostics;
using Lucene.Net.Support;
using Lucene.Net.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using JCG = J2N.Collections.Generic;
#nullable enable

// LUCENENET specific - this class was significantly refactored from its Java counterpart to look and act more like collections in .NET.

namespace Lucene.Net.Analysis.Util
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /// <summary>
    /// A simple class that stores text <see cref="string"/>s as <see cref="T:char[]"/>'s in a
    /// hash table. Note that this is not a general purpose
    /// class.  For example, it cannot remove items from the
    /// dictionary, nor does it resize its hash table to be smaller,
    /// etc.  It is designed to be quick to retrieve items
    /// by <see cref="T:char[]"/> keys without the necessity of converting
    /// to a <see cref="string"/> first.
    ///
    /// <a name="version"></a>
    /// <para>You must specify the required <see cref="LuceneVersion"/>
    /// compatibility when creating <see cref="CharArrayDictionary{TValue}"/>:
    /// <list type="bullet">
    ///   <item><description> As of 3.1, supplementary characters are
    ///       properly lowercased.</description></item>
    /// </list>
    /// Before 3.1 supplementary characters could not be
    /// lowercased correctly due to the lack of Unicode 4
    /// support in JDK 1.4. To use instances of
    /// <see cref="CharArrayDictionary{TValue}"/> with the behavior before Lucene
    /// 3.1 pass a <see cref="LuceneVersion"/> &lt; 3.1 to the constructors.
    /// </para>
    /// </summary>
    [DebuggerDisplay("Count = {Count}, Values = {ToString()}")]
    [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "This is a SonarCloud issue")]
    [SuppressMessage("CodeQuality", "S3218:Inner class members should not shadow outer class \"static\" or type members", Justification = "Following Microsoft's code style for collections")]
    [SuppressMessage("CodeQuality", "S1939:Inheritance list should not be redundant", Justification = "Following Microsoft's code style for collections")]
    public class CharArrayDictionary<TValue> : ICharArrayDictionary, IDictionary<string, TValue>, IDictionary, IReadOnlyDictionary<string, TValue>
    {
        // LUCENENET: Made public, renamed Empty
        /// <summary>
        /// Returns an empty, read-only dictionary. </summary>
        [SuppressMessage("Performance", "IDE0079:Remove unnecessary suppression", Justification = "This is a SonarCloud issue")]
        [SuppressMessage("Performance", "S3887:Use an immutable collection or reduce the accessibility of the non-private readonly field", Justification = "Collection is immutable")]
        [SuppressMessage("Performance", "S2386:Use an immutable collection or reduce the accessibility of the public static field", Justification = "Collection is immutable")]
        public static readonly CharArrayDictionary<TValue> Empty = new CharArrayDictionary.EmptyCharArrayDictionary<TValue>();

        private const int INIT_SIZE = 8;
        private readonly CharacterUtils charUtils;
        private readonly bool ignoreCase;
        private int count;
        private readonly LuceneVersion matchVersion; // package private because used in CharArraySet
        internal char[][] keys; // package private because used in CharArraySet's non Set-conform CharArraySetIterator
        internal MapValue[] values; // package private because used in CharArraySet's non Set-conform CharArraySetIterator

        private int version; // LUCENENET specific - protection so mutating the state of the collection causes enumerators to throw exceptions.

        /// <summary>
        /// LUCENENET: Moved this from CharArraySet so it doesn't need to know the generic type of CharArrayDictionary
        /// </summary>
        internal static readonly MapValue PLACEHOLDER = new MapValue();

        bool ICharArrayDictionary.IgnoreCase => ignoreCase;

        /// <summary>
        /// LUCENENET SPECIFIC type used to act as a placeholder. Since <c>null</c>
        /// means that our value is not populated, we need an instance of something
        /// to indicate it is. Using an instance of <typeparamref name="TValue"/> would only work if
        /// we could constrain it with the new() constraint, which isn't possible because
        /// some types such as <see cref="string"/> don't have a default constructor.
        /// So, this is a workaround that allows any type regardless of the type of constructor.
        ///
        /// <para>
        /// Note also that we gain the ability to use value types for <typeparamref name="TValue"/>, but
        /// also create a difference in behavior from Java Lucene where the actual values
        /// returned could be <c>null</c>.
        /// </para>
        /// </summary>
        internal class MapValue
        {
            [AllowNull]
            private TValue value = default;

            [AllowNull]
            public TValue Value
            {
                get => value!; // We are lying here - if this is a reference type, it could be null. But we don't care because IDictionary<TKey, TValue> doesn't care.
                set => this.value = value;
            }

            public MapValue()
            { }

            public MapValue([AllowNull] TValue value)
            {
                this.value = value;
            }
        }

        /// <summary>
        /// Create dictionary with enough capacity to hold <paramref name="capacity"/> terms.
        /// </summary>
        /// <param name="matchVersion">
        ///          lucene compatibility version - see <see cref="CharArrayDictionary{TValue}"/> for details. </param>
        /// <param name="capacity">
        ///          the initial capacity </param>
        /// <param name="ignoreCase">
        ///          <c>false</c> if and only if the set should be case sensitive;
        ///          otherwise <c>true</c>. </param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than zero.</exception>
        public CharArrayDictionary(LuceneVersion matchVersion, int capacity, bool ignoreCase)
        {
            // LUCENENET: Added guard clause
            if (capacity < 0)
                throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum);

            this.ignoreCase = ignoreCase;
            var size = INIT_SIZE;
            while (capacity + (capacity >> 2) > size)
            {
                size <<= 1;
            }
            keys = new char[size][];
            values = new MapValue[size];
            this.charUtils = CharacterUtils.GetInstance(matchVersion);
            this.matchVersion = matchVersion;
        }

        /// <summary>
        /// Creates a dictionary from the mappings in another dictionary.
        /// </summary>
        /// <param name="matchVersion">
        ///          compatibility match version see <see cref="CharArrayDictionary{TValue}"/> for details. </param>
        /// <param name="collection">
        ///          a dictionary (<see cref="T:IDictionary{string,V}"/>) whose mappings to be copied. </param>
        /// <param name="ignoreCase">
        ///          <c>false</c> if and only if the set should be case sensitive;
        ///          otherwise <c>true</c>. </param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
        public CharArrayDictionary(LuceneVersion matchVersion, IDictionary<string, TValue> collection, bool ignoreCase)
            : this(matchVersion, collection?.Count ?? 0, ignoreCase)
        {
            // LUCENENET: Added guard clause
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var v in collection)
            {
                // LUCENENET: S1699: Don't call call protected members in the constructor
                if (keys[GetSlot(v.Key)] != null) // ContainsKey
                {
                    throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, v.Key));
                }
                SetImpl(v.Key, new MapValue(v.Value));
            }
        }

        /// <summary>
        /// Creates a dictionary from the mappings in another dictionary.
        /// </summary>
        /// <param name="matchVersion">
        ///          compatibility match version see <see cref="CharArrayDictionary{TValue}"/> for details. </param>
        /// <param name="collection">
        ///          a dictionary (<see cref="T:IDictionary{char[],V}"/>) whose mappings to be copied. </param>
        /// <param name="ignoreCase">
        ///          <c>false</c> if and only if the set should be case sensitive;
        ///          otherwise <c>true</c>. </param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
        public CharArrayDictionary(LuceneVersion matchVersion, IDictionary<char[], TValue> collection, bool ignoreCase)
            : this(matchVersion, collection?.Count ?? 0, ignoreCase)
        {
            // LUCENENET: Added guard clause
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var v in collection)
            {
                // LUCENENET: S1699: Don't call call protected members in the constructor
                if (keys[GetSlot(v.Key!, 0, v.Key?.Length ?? 0)] != null) // ContainsKey
                {
                    throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, v.Key));
                }
                SetImpl(v.Key!, new MapValue(v.Value));
            }
        }

        /// <summary>
        /// Creates a dictionary from the mappings in another dictionary.
        /// </summary>
        /// <param name="matchVersion">
        ///          compatibility match version see <see cref="CharArrayDictionary{TValue}"/> for details. </param>
        /// <param name="collection">
        ///          a dictionary (<see cref="T:IDictionary{ICharSequence,V}"/>) whose mappings to be copied. </param>
        /// <param name="ignoreCase">
        ///          <c>false</c> if and only if the set should be case sensitive;
        ///          otherwise <c>true</c>. </param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
        public CharArrayDictionary(LuceneVersion matchVersion, IDictionary<ICharSequence, TValue> collection, bool ignoreCase)
            : this(matchVersion, collection?.Count ?? 0, ignoreCase)
        {
            // LUCENENET: Added guard clause
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var v in collection)
            {
                // LUCENENET: S1699: Don't call call protected members in the constructor
                if (keys[GetSlot(v.Key)] != null) // ContainsKey
                {
                    throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, v.Key));
                }
                SetImpl(v.Key, new MapValue(v.Value));
            }
        }

        /// <summary>
        /// Create set from the supplied dictionary (used internally for readonly maps...)
        /// </summary>
        internal CharArrayDictionary(CharArrayDictionary<TValue> toCopy)
        {
            this.keys = toCopy.keys;
            this.values = toCopy.values;
            this.ignoreCase = toCopy.ignoreCase;
            this.count = toCopy.count;
            this.charUtils = toCopy.charUtils;
            this.matchVersion = toCopy.matchVersion;
        }

        /// <summary>
        /// Adds the <see cref="T:KeyValuePair{string, V}.Value"/> for the passed in <see cref="T:KeyValuePair{string, V}.Key"/>.
        /// Note that the <see cref="T:KeyValuePair{string, V}"/> instance is not added to the dictionary.
        /// </summary>
        /// <param name="item">A <see cref="T:KeyValuePair{string, V}"/> whose <see cref="T:KeyValuePair{string, V}.Value"/>
        /// will be added for the corresponding <see cref="T:KeyValuePair{string, V}.Key"/>. </param>
        void ICollection<KeyValuePair<string, TValue>>.Add(KeyValuePair<string, TValue> item)
        {
            Add(item.Key, item.Value);
        }

        /// <summary>
        /// Adds the <paramref name="value"/> for the passed in <paramref name="text"/>.
        /// </summary>
        /// <param name="text">The string-able type to be added/updated in the dictionary.</param>
        /// <param name="value">The corresponding value for the given <paramref name="text"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">An element with <paramref name="text"/> already exists in the dictionary.</exception>
        public virtual void Add(string text, TValue value)
        {
            if (ContainsKey(text))
            {
                throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, text), nameof(text));
            }
            Set(text, value);
        }

        /// <summary>
        /// Adds the <paramref name="value"/> for the passed in <paramref name="text"/>.
        /// </summary>
        /// <param name="text">The string-able type to be added/updated in the dictionary.</param>
        /// <param name="value">The corresponding value for the given <paramref name="text"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">An element with <paramref name="text"/> already exists in the dictionary.</exception>
        public virtual void Add(char[] text, TValue value)
        {
            if (ContainsKey(text))
            {
                throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, text), nameof(text));
            }
            Set(text, value);
        }

        /// <summary>
        /// Adds the <paramref name="value"/> for the passed in <paramref name="text"/>.
        /// </summary>
        /// <param name="text">The string-able type to be added/updated in the dictionary.</param>
        /// <param name="value">The corresponding value for the given <paramref name="text"/>.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        /// <exception cref="ArgumentException">An element with <paramref name="text"/> already exists in the dictionary.</exception>
        public virtual void Add(ICharSequence text, TValue value)
        {
            if (ContainsKey(text))
            {
                throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, text), nameof(text));
            }
            Set(text, value);
        }

        /// <summary>
        /// Adds the <paramref name="value"/> for the passed in <paramref name="text"/>.
        /// </summary>
        /// <param name="text">The string-able type to be added/updated in the dictionary.</param>
        /// <param name="value">The corresponding value for the given <paramref name="text"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentException">An element with <paramref name="text"/> already exists in the dictionary.</exception>
        public virtual void Add<T>(T text, TValue value)
        {
            if (ContainsKey(text))
            {
                throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, text), nameof(text));
            }
            Set(text, value);
        }

        /// <summary>
        /// Returns an unmodifiable <see cref="CharArrayDictionary{TValue}"/>. This allows to provide
        /// unmodifiable views of internal dictionary for "read-only" use.
        /// </summary>
        /// <returns> an new unmodifiable <see cref="CharArrayDictionary{TValue}"/>. </returns>
        // LUCENENET specific - allow .NET-like syntax for creating immutable collections
        public CharArrayDictionary<TValue> AsReadOnly() => AsReadOnlyImpl();

        private protected virtual CharArrayDictionary<TValue> AsReadOnlyImpl() // Hack so we can make it virtual
        {
            return new CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>(this);
        }

        /// <summary>
        /// Clears all entries in this dictionary. This method is supported for reusing, but not
        /// <see cref="IDictionary{TKey, TValue}.Remove(TKey)"/>.
        /// </summary>
        public virtual void Clear()
        {
            version++;
            count = 0;
            Arrays.Fill(keys, null);
            Arrays.Fill(values, null);
        }

        /// <summary>
        /// Not supported.
        /// </summary>
        bool ICollection<KeyValuePair<string, TValue>>.Contains(KeyValuePair<string, TValue> item)
        {
            throw UnsupportedOperationException.Create();
        }

        /// <summary>
        /// Copies all items in the current dictionary the <paramref name="array"/> starting at the <paramref name="arrayIndex"/>.
        /// The array is assumed to already be dimensioned to fit the elements in this dictionary; otherwise a <see cref="ArgumentOutOfRangeException"/>
        /// will be thrown.
        /// </summary>
        /// <param name="array">The array to copy the items into.</param>
        /// <param name="arrayIndex">A 32-bit integer that represents the index in <paramref name="array"/> at which copying begins.</param>
        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than zero.</exception>
        /// <exception cref="ArgumentException">The number of elements in the source is greater
        /// than the available space from <paramref name="arrayIndex"/> to the end of the destination array.</exception>
        // LUCENENET: Generally, it makes more sense to use the Enuerator explicitly so we have access to the underling char[] and so
        // we don't have to new up a KeyValuePair<string, TValue> just for the sake of reading data.
        private void CopyTo(KeyValuePair<string, TValue>[] array, int arrayIndex)
        {
            if (array is null)
                throw new ArgumentNullException(nameof(array));
            if (arrayIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, SR.ArgumentOutOfRange_NeedNonNegNum);
            if (count > array.Length - arrayIndex)
                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);

            using var iter = GetEnumerator();
            for (int i = arrayIndex; iter.MoveNext(); i++)
            {
                array[i] = new KeyValuePair<string, TValue>(iter.CurrentKeyString, iter.CurrentValue!);
            }
        }

        void ICollection<KeyValuePair<string, TValue>>.CopyTo(KeyValuePair<string, TValue>[] array, int arrayIndex) => CopyTo(array, arrayIndex);


        /// <summary>
        /// Copies all items in the current dictionary the <paramref name="array"/> starting at the <paramref name="index"/>.
        /// The array is assumed to already be dimensioned to fit the elements in this dictionary; otherwise a <see cref="ArgumentOutOfRangeException"/>
        /// will be thrown.
        /// </summary>
        /// <param name="array">The array to copy the items into.</param>
        /// <param name="index">A 32-bit integer that represents the index in <paramref name="array"/> at which copying begins.</param>
        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.</exception>
        /// <exception cref="ArgumentException">The number of elements in the source is greater
        /// than the available space from <paramref name="index"/> to the end of the destination array.</exception>
        internal void CopyTo(KeyValuePair<char[], TValue>[] array, int index) // internal for testing
        {
            if (array is null)
                throw new ArgumentNullException(nameof(array));
            if (index < 0)
                throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
            if (count > array.Length - index)
                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);

            using var iter = GetEnumerator();
            for (int i = index; iter.MoveNext(); i++)
            {
                array[i] = new KeyValuePair<char[], TValue>((char[])iter.CurrentKey.Clone(), iter.CurrentValue!);
            }
        }

        /// <summary>
        /// Copies all items in the current dictionary the <paramref name="array"/> starting at the <paramref name="index"/>.
        /// The array is assumed to already be dimensioned to fit the elements in this dictionary; otherwise a <see cref="ArgumentOutOfRangeException"/>
        /// will be thrown.
        /// </summary>
        /// <param name="array">The array to copy the items into.</param>
        /// <param name="index">A 32-bit integer that represents the index in <paramref name="array"/> at which copying begins.</param>
        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.</exception>
        /// <exception cref="ArgumentException">The number of elements in the source is greater
        /// than the available space from <paramref name="index"/> to the end of the destination array.</exception>
        internal void CopyTo(KeyValuePair<ICharSequence, TValue>[] array, int index) // internal for testing
        {
            if (array is null)
                throw new ArgumentNullException(nameof(array));
            if (index < 0)
                throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
            if (count > array.Length - index)
                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);

            using var iter = GetEnumerator();
            for (int i = index; iter.MoveNext(); i++)
            {
                array[i] = new KeyValuePair<ICharSequence, TValue>(((char[])iter.CurrentKey.Clone()).AsCharSequence(), iter.CurrentValue!);
            }
        }

        /// <summary>
        /// <c>true</c> if the <paramref name="length"/> chars of <paramref name="text"/> starting at <paramref name="startIndex"/>
        /// are in the <see cref="Keys"/>
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        public virtual bool ContainsKey(char[] text, int startIndex, int length)
        {
            return keys[GetSlot(text, startIndex, length)] != null;
        }

        /// <summary>
        /// <c>true</c> if the entire <see cref="Keys"/> is the same as the
        /// <paramref name="text"/> <see cref="T:char[]"/> being passed in;
        /// otherwise <c>false</c>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool ContainsKey(char[] text)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            return keys[GetSlot(text, 0, text.Length)] != null;
        }

        /// <summary>
        /// <c>true</c> if the <paramref name="text"/> <see cref="string"/> is in the <see cref="Keys"/>;
        /// otherwise <c>false</c>
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool ContainsKey(string text)
        {
            return keys[GetSlot(text)] != null;
        }

        /// <summary>
        /// <c>true</c> if the <paramref name="text"/> <see cref="ICharSequence"/> is in the <see cref="Keys"/>;
        /// otherwise <c>false</c>
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        public virtual bool ContainsKey(ICharSequence text)
        {
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text));

            if (text is CharArrayCharSequence charArrayCs)
                return ContainsKey(charArrayCs.Value!);
            if (text is StringBuilderCharSequence stringBuilderCs)
                return ContainsKey(stringBuilderCs.Value!.ToString()); // LUCENENET: Indexing into a StringBuilder is slow, so materialize

            return keys[GetSlot(text)] != null;
        }


        /// <summary>
        /// <c>true</c> if the <paramref name="text"/> <see cref="object.ToString()"/> (in the invariant culture)
        /// is in the <see cref="Keys"/>;  otherwise <c>false</c>
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool ContainsKey<T>(T text)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)

            if (text is string str)
                return ContainsKey(str);
            if (text is char[] charArray)
                return ContainsKey(charArray, 0, charArray.Length);
            if (text is ICharSequence cs)
                return ContainsKey(cs);

            var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
            if (returnType == CharArrayDictionary.CharReturnType.String)
                return ContainsKey(s);
            else
                return ContainsKey(chars);
        }

        #region Get

        /// <summary>
        /// Returns the value of the mapping of <paramref name="length"/> chars of <paramref name="text"/>
        /// starting at <paramref name="startIndex"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        /// <exception cref="KeyNotFoundException">The effective text is not found in the dictionary.</exception>
        internal virtual TValue Get(char[] text, int startIndex, int length, bool throwIfNotFound = true)
        {
            MapValue? value = values[GetSlot(text, startIndex, length)];
            if (value is not null)
            {
                return value.Value;
            }
            if (throwIfNotFound)
                throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, new string(text, startIndex, length)));
            return default!;
        }

        /// <summary>
        /// Returns the value of the mapping of the chars inside this <paramref name="text"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="KeyNotFoundException"><paramref name="text"/> is not found in the dictionary.</exception>
        internal virtual TValue Get(char[] text, bool throwIfNotFound = true)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            MapValue? value = values[GetSlot(text, 0, text.Length)];
            if (value is not null)
            {
                return value.Value;
            }
            if (throwIfNotFound)
                throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, new string(text)));
            return default!;
        }

        /// <summary>
        /// Returns the value of the mapping of the chars inside this <see cref="ICharSequence"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        /// <exception cref="KeyNotFoundException"><paramref name="text"/> is not found in the dictionary.</exception>
        internal virtual TValue Get(ICharSequence text, bool throwIfNotFound = true)
        {
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text));

            if (text is StringCharSequence strCs)
                return Get(strCs.Value!, throwIfNotFound);
            if (text is CharArrayCharSequence charArrayCs)
                return Get(charArrayCs.Value!, throwIfNotFound);
            if (text is StringBuilderCharSequence stringBuilderCs)
                return Get(stringBuilderCs.Value!.ToString(), throwIfNotFound); // LUCENENET: Indexing into a StringBuilder is slow, so materialize

            var value = values[GetSlot(text)];
            if (value is not null)
            {
                return value.Value;
            }
            if (throwIfNotFound)
                throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, text));
            return default!;
        }

        /// <summary>
        /// Returns the value of the mapping of the chars inside this <see cref="string"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="KeyNotFoundException"><paramref name="text"/> is not found in the dictionary.</exception>
        internal virtual TValue Get(string text, bool throwIfNotFound = true)
        {
            var value = values[GetSlot(text)];
            if (value is not null)
            {
                return value.Value;
            }
            if (throwIfNotFound)
                throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, text));
            return default!;
        }

        /// <summary>
        /// Returns the value of the mapping of the chars inside this <see cref="object.ToString()"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="KeyNotFoundException"><paramref name="text"/> is not found in the dictionary.</exception>
        internal virtual TValue Get<T>(T text, bool throwIfNotFound = true)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            // LUCENENET NOTE: Testing for *is* is at least 10x faster
            // than casting using *as* and then checking for null.
            // http://stackoverflow.com/q/1583050/181087
            if (text is string str)
                return Get(str, throwIfNotFound);
            if (text is char[] charArray)
                return Get(charArray, 0, charArray.Length, throwIfNotFound);
            if (text is ICharSequence cs)
                return Get(cs, throwIfNotFound);

            var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
            if (returnType == CharArrayDictionary.CharReturnType.String)
                return Get(s, throwIfNotFound);
            else
                return Get(chars, throwIfNotFound);
        }

        #endregion Get

        #region GetSlot

        private int GetSlot(char[] text, int startIndex, int length)
        {
            int code = GetHashCode(text, startIndex, length);
            int pos = code & (keys.Length - 1);
            char[] text2 = keys[pos];
            if (text2 != null && !Equals(text, startIndex, length, text2))
            {
                int inc = ((code >> 8) + code) | 1;
                do
                {
                    code += inc;
                    pos = code & (keys.Length - 1);
                    text2 = keys[pos];
                } while (text2 != null && !Equals(text, startIndex, length, text2));
            }
            return pos;
        }

        /// <summary>
        /// Returns <c>true</c> if the <see cref="ICharSequence"/> is in the set.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        private int GetSlot(ICharSequence text)
        {
            int code = GetHashCode(text);
            int pos = code & (keys.Length - 1);
            char[] text2 = keys[pos];
            if (text2 != null && !Equals(text, text2))
            {
                int inc = ((code >> 8) + code) | 1;
                do
                {
                    code += inc;
                    pos = code & (keys.Length - 1);
                    text2 = keys[pos];
                } while (text2 != null && !Equals(text, text2));
            }
            return pos;
        }

        /// <summary>
        /// Returns <c>true</c> if the <see cref="string"/> is in the set.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        private int GetSlot(string text)
        {
            int code = GetHashCode(text);
            int pos = code & (keys.Length - 1);
            char[] text2 = keys[pos];
            if (text2 != null && !Equals(text, text2))
            {
                int inc = ((code >> 8) + code) | 1;
                do
                {
                    code += inc;
                    pos = code & (keys.Length - 1);
                    text2 = keys[pos];
                } while (text2 != null && !Equals(text, text2));
            }
            return pos;
        }

        #endregion GetSlot

        #region Put (value)

        /// <summary>
        /// Add the given mapping.
        /// If ignoreCase is <c>true</c> for this dictionary, the text array will be directly modified.
        /// <para/>
        /// <b>Note:</b> The <see cref="this[char[]]"/> setter is more efficient than this method if
        /// the <paramref name="previousValue"/> is not required.
        /// </summary>
        /// <param name="text">A text with which the specified <paramref name="value"/> is associated.</param>
        /// <param name="startIndex">The position of the <paramref name="text"/> where the target text begins.</param>
        /// <param name="length">The total length of the <paramref name="text"/>.</param>
        /// <param name="value">The value to be associated with the specified <paramref name="text"/>.</param>
        /// <param name="previousValue">The previous value associated with the text, or the default for the type of <paramref name="value"/>
        /// parameter if there was no mapping for <paramref name="text"/>.</param>
        /// <returns><c>true</c> if the mapping was added, <c>false</c> if the text already existed. The <paramref name="previousValue"/>
        /// will be populated if the result is <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        public virtual bool Put(char[] text, int startIndex, int length, TValue value, [MaybeNullWhen(returnValue: true)] out TValue previousValue) // LUCENENET: Refactored to use out value to support value types
        {
            MapValue? oldValue = PutImpl(text, startIndex, length, new MapValue(value));
            if (oldValue is not null)
            {
                previousValue = oldValue.Value;
                return false;
            }
            previousValue = default;
            return true;
        }

        /// <summary>
        /// Add the given mapping.
        /// If ignoreCase is <c>true</c> for this dictionary, the text array will be directly modified.
        /// The user should never modify this text array after calling this method.
        /// <para/>
        /// <b>Note:</b> The <see cref="this[char[]]"/> setter is more efficient than this method if
        /// the <paramref name="previousValue"/> is not required.
        /// </summary>
        /// <param name="text">A text with which the specified <paramref name="value"/> is associated.</param>
        /// <param name="value">The value to be associated with the specified <paramref name="text"/>.</param>
        /// <param name="previousValue">The previous value associated with the text, or the default for the type of <paramref name="value"/>
        /// parameter if there was no mapping for <paramref name="text"/>.</param>
        /// <returns><c>true</c> if the mapping was added, <c>false</c> if the text already existed. The <paramref name="previousValue"/>
        /// will be populated if the result is <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool Put(char[] text, TValue value, [MaybeNullWhen(returnValue: true)] out TValue previousValue) // LUCENENET: Refactored to use out value to support value types
        {
            MapValue? oldValue = PutImpl(text, new MapValue(value));
            if (oldValue is not null)
            {
                previousValue = oldValue.Value;
                return false;
            }
            previousValue = default;
            return true;
        }

        /// <summary>
        /// Add the given mapping.
        /// <para/>
        /// <b>Note:</b> The <see cref="this[string]"/> setter is more efficient than this method if
        /// the <paramref name="previousValue"/> is not required.
        /// </summary>
        /// <param name="text">A text with which the specified <paramref name="value"/> is associated.</param>
        /// <param name="value">The value to be associated with the specified <paramref name="text"/>.</param>
        /// <param name="previousValue">The previous value associated with the text, or the default for the type of <paramref name="value"/>
        /// parameter if there was no mapping for <paramref name="text"/>.</param>
        /// <returns><c>true</c> if the mapping was added, <c>false</c> if the text already existed. The <paramref name="previousValue"/>
        /// will be populated if the result is <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool Put(string text, TValue value, [MaybeNullWhen(returnValue: true)] out TValue previousValue) // LUCENENET: Refactored to use out value to support value types
        {
            MapValue? oldValue = PutImpl(text, new MapValue(value));
            if (oldValue is not null)
            {
                previousValue = oldValue.Value;
                return false;
            }
            previousValue = default;
            return true;
        }

        /// <summary>
        /// Add the given mapping.
        /// <para/>
        /// <b>Note:</b> The <see cref="this[ICharSequence]"/> setter is more efficient than this method if
        /// the <paramref name="previousValue"/> is not required.
        /// </summary>
        /// <param name="text">A text with which the specified <paramref name="value"/> is associated.</param>
        /// <param name="value">The value to be associated with the specified <paramref name="text"/>.</param>
        /// <param name="previousValue">The previous value associated with the text, or the default for the type of <paramref name="value"/>
        /// parameter if there was no mapping for <paramref name="text"/>.</param>
        /// <returns><c>true</c> if the mapping was added, <c>false</c> if the text already existed. The <paramref name="previousValue"/>
        /// will be populated if the result is <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        public virtual bool Put(ICharSequence text, TValue value, [MaybeNullWhen(returnValue: true)] out TValue previousValue) // LUCENENET: Refactored to use out value to support value types
        {
            MapValue? oldValue = PutImpl(text, new MapValue(value));
            if (oldValue is not null)
            {
                previousValue = oldValue.Value;
                return false;
            }
            previousValue = default;
            return true;
        }

        /// <summary>
        /// Add the given mapping using the <see cref="object.ToString()"/> representation
        /// of <paramref name="text"/> in the <see cref="CultureInfo.InvariantCulture"/>.
        /// <para/>
        /// <b>Note:</b> The <see cref="this[object]"/> setter is more efficient than this method if
        /// the <paramref name="previousValue"/> is not required.
        /// </summary>
        /// <param name="text">A text with which the specified <paramref name="value"/> is associated.</param>
        /// <param name="value">The value to be associated with the specified <paramref name="text"/>.</param>
        /// <param name="previousValue">The previous value associated with the text, or the default for the type of <paramref name="value"/>
        /// parameter if there was no mapping for <paramref name="text"/>.</param>
        /// <returns><c>true</c> if the mapping was added, <c>false</c> if the text already existed. The <paramref name="previousValue"/>
        /// will be populated if the result is <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool Put<T>(T text, TValue value, [MaybeNullWhen(returnValue: true)] out TValue previousValue) // LUCENENET: Refactored to use out value to support value types
        {
            MapValue? oldValue = PutImpl(text, new MapValue(value));
            if (oldValue is not null)
            {
                previousValue = oldValue.Value;
                return false;
            }
            previousValue = default;
            return true;
        }

        #endregion Put (value)

        #region PutImpl

        /// <summary>
        /// Add the given mapping.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private MapValue? PutImpl(ICharSequence text, MapValue value)
        {
            // LUCENENET: Added guard clause
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text));

            if (text is CharArrayCharSequence charArrayCs)
                return PutImpl(charArrayCs.Value ?? Array.Empty<char>(), value);
            if (text is StringBuilderCharSequence stringBuilderCs) // LUCENENET: Indexing into a StringBuilder is slow, so materialize
            {
                var sb = stringBuilderCs.Value!;
                char[] result = new char[sb.Length];
                sb.CopyTo(sourceIndex: 0, result, destinationIndex: 0, sb.Length);
                return PutImpl(result, value);
            }

            int length = text.Length;
            char[] buffer = new char[length];
            for (int i = 0; i < length; i++)
            {
                buffer[i] = text[i];
            }
            return PutImpl(buffer, value);
        }

        /// <summary>
        /// Add the given mapping.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private MapValue? PutImpl<T>(T text, MapValue value)
        {
            // LUCENENET: Added guard clause
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            // LUCENENET NOTE: Testing for *is* is at least 10x faster
            // than casting using *as* and then checking for null.
            // http://stackoverflow.com/q/1583050/181087
            if (text is string str)
                return PutImpl(str, value);
            if (text is char[] charArray)
                return PutImpl(charArray, value);
            if (text is ICharSequence cs)
                return PutImpl(cs, value);

            var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
            if (returnType == CharArrayDictionary.CharReturnType.String)
                return PutImpl(s, value);
            else
                return PutImpl(chars, value);
        }

        /// <summary>
        /// Add the given mapping.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private MapValue? PutImpl(string text, MapValue value)
        {
            // LUCENENET: Added guard clause
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            // LUCENENET specific - only allocate char array if it is required.
            if (ignoreCase)
            {
                return PutImpl(text.ToCharArray(), value);
            }
            version++;
            int slot = GetSlot(text);
            if (keys[slot] != null)
            {
                MapValue oldValue = values[slot];
                values[slot] = value;
                return oldValue;
            }
            keys[slot] = text.ToCharArray();
            values[slot] = value;
            count++;

            if (count + (count >> 2) > keys.Length)
            {
                Rehash();
            }

            return null;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private MapValue? PutImpl(char[] text, int startIndex, int length, MapValue value)
        {
            // LUCENENET: Added guard clause
            if (text is null)
                throw new ArgumentNullException(nameof(text));
            if (startIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_NeedNonNegNum);
            if (length < 0)
                throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
            if (startIndex > text.Length - length) // Checks for int overflow
                throw new ArgumentException(SR.ArgumentOutOfRange_IndexLength);

            version++;

            if (ignoreCase)
            {
                charUtils.ToLower(text, startIndex, length);
            }
            int slot = GetSlot(text, startIndex, length);
            if (keys[slot] != null)
            {
                MapValue oldValue = values[slot];
                values[slot] = value;
                return oldValue;
            }
            keys[slot] = text.AsSpan(startIndex, length).ToArray();
            values[slot] = value;
            count++;

            if (count + (count >> 2) > keys.Length)
            {
                Rehash();
            }

            return null;
        }

        /// <summary>
        /// LUCENENET specific. Centralizes the logic between Put()
        /// implementations that accept a value and those that don't. This value is
        /// so we know whether or not the value was set, since we can't reliably do
        /// a check for <c>null</c> on the <typeparamref name="TValue"/> type.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private MapValue? PutImpl(char[] text, MapValue value)
        {
            // LUCENENET: Added guard clause
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            version++;

            if (ignoreCase)
            {
                charUtils.ToLower(text, 0, text.Length);
            }
            int slot = GetSlot(text, 0, text.Length);
            if (keys[slot] != null)
            {
                MapValue oldValue = values[slot];
                values[slot] = value;
                return oldValue;
            }
            keys[slot] = text;
            values[slot] = value;
            count++;

            if (count + (count >> 2) > keys.Length)
            {
                Rehash();
            }

            return null;
        }

        #endregion PutImpl

        #region Set

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <paramref name="text"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(char[] text, int startIndex, int length)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, startIndex, length, PLACEHOLDER);
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <paramref name="text"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(char[] text)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, PLACEHOLDER);
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <see cref="ICharSequence"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(ICharSequence text)
        {
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, PLACEHOLDER);
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <see cref="string"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(string text)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, PLACEHOLDER);
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <see cref="object.ToString()"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        internal virtual void Set<T>(T text)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            // LUCENENET NOTE: Testing for *is* is at least 10x faster
            // than casting using *as* and then checking for null.
            // http://stackoverflow.com/q/1583050/181087
            if (text is string str)
            {
                Set(str);
                return;
            }
            if (text is char[] charArray)
            {
                Set(charArray);
                return;
            }
            if (text is ICharSequence cs)
            {
                Set(cs);
                return;
            }

            var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
            if (returnType == CharArrayDictionary.CharReturnType.String)
            {
                Set(s);
                return;
            }

            Set(chars);
        }

        void ICharArrayDictionary.Set(char[] text, int startIndex, int length) => Set(text, startIndex, length);
        void ICharArrayDictionary.Set(char[] text) => Set(text);
        void ICharArrayDictionary.Set(ICharSequence text) => Set(text);
        void ICharArrayDictionary.Set<T>(T text) => Set(text);
        void ICharArrayDictionary.Set(string text) => Set(text);

        #endregion Set

        #region Set (value)

        /// <summary>
        /// Sets the value of the mapping of <paramref name="length"/> chars of <paramref name="text"/>
        /// starting at <paramref name="startIndex"/>.
        /// <para/>
        /// If ignoreCase is <c>true</c> for this dictionary, the text array will be directly modified.
        /// </summary>
        /// <param name="text">A text with which the specified <paramref name="value"/> is associated.</param>
        /// <param name="startIndex">The position of the <paramref name="text"/> where the target text begins.</param>
        /// <param name="length">The total length of the <paramref name="text"/>.</param>
        /// <param name="value">The value to be associated with the specified <paramref name="text"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(char[] text, int startIndex, int length, TValue? value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, startIndex, length, new MapValue(value));
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <paramref name="text"/>.
        /// <para/>
        /// If ignoreCase is <c>true</c> for this dictionary, the text array will be directly modified.
        /// The user should never modify this text array after calling this method.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(char[] text, TValue? value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, new MapValue(value));
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <see cref="ICharSequence"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(ICharSequence text, TValue? value)
        {
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, new MapValue(value));
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <see cref="string"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set(string text, TValue? value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            SetImpl(text, new MapValue(value));
        }

        /// <summary>
        /// Sets the value of the mapping of the chars inside this <see cref="object.ToString()"/>.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Set<T>(T text, TValue? value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            // LUCENENET NOTE: Testing for *is* is at least 10x faster
            // than casting using *as* and then checking for null.
            // http://stackoverflow.com/q/1583050/181087
            if (text is string str)
            {
                Set(str, value);
                return;
            }
            if (text is char[] charArray)
            {
                Set(charArray, 0, charArray.Length, value);
                return;
            }
            if (text is ICharSequence cs)
            {
                Set(cs, value);
                return;
            }

            var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
            if (returnType == CharArrayDictionary.CharReturnType.String)
            {
                Set(s, value);
                return;
            }

            Set(chars, value);
        }

        #endregion Set (value)

        #region SetImpl

        /// <summary>
        /// LUCENENET specific. Like PutImpl, but doesn't have a return value or lookup to get the old value.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SetImpl(ICharSequence text, MapValue value)
        {
            // LUCENENET: Added guard clause
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text));

            if (text is CharArrayCharSequence charArrayCs)
            {
                SetImpl(charArrayCs.Value ?? Array.Empty<char>(), value);
                return;
            }
            if (text is StringBuilderCharSequence stringBuilderCs) // LUCENENET: Indexing into a StringBuilder is slow, so materialize
            {
                var sb = stringBuilderCs.Value!;
                char[] result = new char[sb.Length];
                sb.CopyTo(sourceIndex: 0, result, destinationIndex: 0, sb.Length);
                SetImpl(result, value);
                return;
            }

            int length = text.Length;
            char[] buffer = new char[length];
            for (int i = 0; i < length; i++)
            {
                buffer[i] = text[i];
            }

            SetImpl(buffer, value);
        }

        /// <summary>
        /// LUCENENET specific. Like PutImpl, but doesn't have a return value or lookup to get the old value.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SetImpl(string text, MapValue value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            // LUCENENET specific - only allocate char array if it is required.
            if (ignoreCase)
            {
                SetImpl(text.ToCharArray(), value);
                return;
            }
            version++;
            int slot = GetSlot(text);
            if (keys[slot] != null)
            {
                values[slot] = value;
                return;
            }
            keys[slot] = text.ToCharArray();
            values[slot] = value;
            count++;

            if (count + (count >> 2) > keys.Length)
            {
                Rehash();
            }
        }

        /// <summary>
        /// LUCENENET specific. Like PutImpl, but doesn't have a return value or lookup to get the old value.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SetImpl(char[] text, MapValue value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            version++;
            if (ignoreCase)
            {
                charUtils.ToLower(text, 0, text.Length);
            }
            int slot = GetSlot(text, 0, text.Length);
            if (keys[slot] != null)
            {
                values[slot] = value;
                return;
            }
            keys[slot] = text;
            values[slot] = value;
            count++;

            if (count + (count >> 2) > keys.Length)
            {
                Rehash();
            }
        }

        /// <summary>
        /// LUCENENET specific. Like PutImpl, but doesn't have a return value or lookup to get the old value.
        /// </summary>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void SetImpl(char[] text, int startIndex, int length, MapValue value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));
            if (startIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_NeedNonNegNum);
            if (length < 0)
                throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
            if (startIndex > text.Length - length) // Checks for int overflow
                throw new ArgumentException(SR.ArgumentOutOfRange_IndexLength);

            version++;
            if (ignoreCase)
            {
                charUtils.ToLower(text, startIndex, length);
            }
            int slot = GetSlot(text, startIndex, length);
            if (keys[slot] != null)
            {
                values[slot] = value;
                return;
            }
            keys[slot] = text.AsSpan(startIndex, length).ToArray();
            values[slot] = value;
            count++;

            if (count + (count >> 2) > keys.Length)
            {
                Rehash();
            }
        }

        #endregion SetImpl

        #region PutAll

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IDictionary{char[],TValue}"/>'s
        /// entries, and calls this dictionary's <see cref="Set(char[], TValue?)"/> operation once for each entry.
        /// <para/>
        /// If ignoreCase is <c>true</c> for this dictionary, the text arrays will be directly modified.
        /// The user should never modify the text arrays after calling this method.
        /// </summary>
        /// <param name="collection">A dictionary of values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection is <c>null</c>.
        /// </exception>
        public virtual void PutAll(IDictionary<char[], TValue> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var kvp in collection)
            {
                Set(kvp.Key, kvp.Value);
            }
        }

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IDictionary{string,TValue}"/>'s
        /// entries, and calls this dictionary's <see cref="Set(string, TValue?)"/> operation once for each entry.
        /// </summary>
        /// <param name="collection">A dictionary of values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection is <c>null</c>.
        /// </exception>
        public virtual void PutAll(IDictionary<string, TValue> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var kvp in collection)
            {
                Set(kvp.Key, kvp.Value);
            }
        }

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IDictionary{ICharSequence,TValue}"/>'s
        /// entries, and calls this dictionary's <see cref="Set(ICharSequence, TValue?)"/> operation once for each entry.
        /// </summary>
        /// <param name="collection">A dictionary of values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection has a <c>null</c> text.
        /// <para/>
        /// -or-
        /// <para/>
        /// The text's <see cref="ICharSequence.HasValue"/> property for a given element in the collection returns <c>false</c>.
        /// </exception>
        public virtual void PutAll(IDictionary<ICharSequence, TValue> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var kvp in collection)
            {
                Set(kvp.Key, kvp.Value);
            }
        }

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IDictionary{T,TValue}"/>'s
        /// entries, and calls this dictionary's <see cref="Set{T}(T, TValue?)"/> operation once for each entry.
        /// </summary>
        /// <param name="collection">A dictionary of values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection is <c>null</c>.
        /// </exception>
        public virtual void PutAll<T>(IDictionary<T, TValue> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var kvp in collection)
            {
                Set(kvp.Key, kvp.Value);
            }
        }

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{char[],TValue}}"/>'s
        /// entries, and calls this dictionary's <see cref="Set(char[], TValue?)"/> operation once for each entry.
        /// </summary>
        /// <param name="collection">The values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection is <c>null</c>.
        /// </exception>
        public virtual void PutAll(IEnumerable<KeyValuePair<char[], TValue>> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var kvp in collection)
            {
                Set(kvp.Key, kvp.Value);
            }
        }

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{string,TValue}}"/>'s
        /// entries, and calls this dictionary's <see cref="Set(string, TValue)"/> operation once for each entry.
        /// </summary>
        /// <param name="collection">The values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection is <c>null</c>.
        /// </exception>
        public virtual void PutAll(IEnumerable<KeyValuePair<string, TValue>> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var kvp in collection)
            {
                Set(kvp.Key, kvp.Value);
            }
        }

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{ICharSequence,TValue}}"/>'s
        /// entries, and calls this dictionary's <see cref="Set(ICharSequence, TValue)"/> operation once for each entry.
        /// </summary>
        /// <param name="collection">The values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection has a <c>null</c> text.
        /// <para/>
        /// -or-
        /// <para/>
        /// The text's <see cref="ICharSequence.HasValue"/> property for a given element in the collection returns <c>false</c>.
        /// </exception>
        public virtual void PutAll(IEnumerable<KeyValuePair<ICharSequence, TValue>> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

            foreach (var kvp in collection)
            {
                Set(kvp.Key, kvp.Value);
            }
        }

        /// <summary>
        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{TKey,TValue}}"/>'s
        /// entries, and calls this dictionary's <see cref="Set{T}(T, TValue?)"/> operation once for each entry.
        /// </summary>
        /// <param name="collection">The values to add/update in the current dictionary.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="collection"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// An element in the collection is <c>null</c>.
        /// </exception>
        public virtual void PutAll<T>(IEnumerable<KeyValuePair<T, TValue>> collection)
        {
            if (collection is null)
                throw new ArgumentNullException(nameof(collection));

#if FEATURE_SPANFORMATTABLE
            Span<char> buffer = stackalloc char[256];
#else
            Span<char> buffer = stackalloc char[1];
#endif

            foreach (var kvp in collection)
            {
                // Convert the item to chars in the invariant culture
                var returnType = CharArrayDictionary.ConvertObjectToChars(kvp.Key, out char[] chars, out string s, buffer);
                if (returnType == CharArrayDictionary.CharReturnType.String)
                    Set(s, kvp.Value);
                else
                    Set(chars, kvp.Value);
            }
        }

        #endregion

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Rehash()
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(keys.Length == values.Length);
            int newSize = 2 * keys.Length;
            char[][] oldkeys = keys;
            MapValue[] oldvalues = values;
            keys = new char[newSize][];
            values = new MapValue[newSize];

            for (int i = 0; i < oldkeys.Length; i++)
            {
                char[] text = oldkeys[i];
                if (text != null)
                {
                    // todo: could be faster... no need to compare strings on collision
                    int slot = GetSlot(text, 0, text.Length);
                    keys[slot] = text;
                    values[slot] = oldvalues[i];
                }
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool Equals(char[] text1, int startIndex, int length, char[] text2)
        {
            if (length != text2.Length)
            {
                return false;
            }
            int limit = startIndex + length;
            if (ignoreCase)
            {
                for (int i = 0; i < length;)
                {
                    var codePointAt = charUtils.CodePointAt(text1, startIndex + i, limit);
                    if (Character.ToLower(codePointAt, CultureInfo.InvariantCulture) != charUtils.CodePointAt(text2, i, text2.Length)) // LUCENENET specific - need to use invariant culture to match Java
                    {
                        return false;
                    }
                    i += Character.CharCount(codePointAt);
                }
            }
            else
            {
                for (int i = 0; i < length; i++)
                {
                    if (text1[startIndex + i] != text2[i])
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool Equals(ICharSequence text1, char[] text2)
        {
            int length = text1.Length;
            if (length != text2.Length)
            {
                return false;
            }
            if (ignoreCase)
            {
                for (int i = 0; i < length;)
                {
                    int codePointAt = charUtils.CodePointAt(text1, i);
                    if (Character.ToLower(codePointAt, CultureInfo.InvariantCulture) != charUtils.CodePointAt(text2, i, text2.Length)) // LUCENENET specific - need to use invariant culture to match Java
                    {
                        return false;
                    }
                    i += Character.CharCount(codePointAt);
                }
            }
            else
            {
                for (int i = 0; i < length; i++)
                {
                    if (text1[i] != text2[i])
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool Equals(string text1, char[] text2)
        {
            int length = text1.Length;
            if (length != text2.Length)
            {
                return false;
            }
            if (ignoreCase)
            {
                for (int i = 0; i < length;)
                {
                    int codePointAt = charUtils.CodePointAt(text1, i);
                    if (Character.ToLower(codePointAt, CultureInfo.InvariantCulture) != charUtils.CodePointAt(text2, i, text2.Length)) // LUCENENET specific - need to use invariant culture to match Java
                    {
                        return false;
                    }
                    i += Character.CharCount(codePointAt);
                }
            }
            else
            {
                for (int i = 0; i < length; i++)
                {
                    if (text1[i] != text2[i])
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// LUCENENET Specific - test for value equality similar to how it is done in Java
        /// </summary>
        /// <param name="obj">Another dictionary to test the values of</param>
        /// <returns><c>true</c> if the given object is an <see cref="T:IDictionary{object,V}"/> that contains
        /// the same text value pairs as the current dictionary</returns>
        public override bool Equals(object? obj)
        {
            if (obj is null)
                return false;
            if (obj is not IDictionary<string, TValue> other)
                return false;
            if (this.Count != other.Count)
                return false;

            if (obj is CharArrayDictionary<TValue> charArrayDictionary)
            {
                using var iter = charArrayDictionary.GetEnumerator();
                while (iter.MoveNext())
                {
                    if (!this.TryGetValue(iter.CurrentKey, out TValue? value))
                        return false;

                    if (!JCG.EqualityComparer<TValue>.Default.Equals(value!, iter.Current.Value))
                        return false;
                }
            }
            else
            {
                using var iter = other.GetEnumerator();
                while (iter.MoveNext())
                {
                    if (!this.TryGetValue(iter.Current.Key, out TValue? value))
                        return false;

                    if (!JCG.EqualityComparer<TValue>.Default.Equals(value!, iter.Current.Value))
                        return false;
                }
            }

            return true;
        }

        /// <summary>
        /// LUCENENET Specific - override required by .NET because we override Equals
        /// to simulate Java's value equality checking.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            const int PRIME = 31; // arbitrary prime
            int hash = PRIME;
            using (var iter = GetEnumerator())
            {
                while (iter.MoveNext())
                {
                    hash = (hash * PRIME) ^ iter.CurrentKeyString.GetHashCode();
                    TValue? value = iter.CurrentValue;
                    hash = (hash * PRIME) ^ (value is null ? 0 : JCG.EqualityComparer<TValue>.Default.GetHashCode(value));
                }
            }
            return hash;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int GetHashCode(char[] text, int startIndex, int length)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
            if (startIndex < 0)
                throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_NeedNonNegNum);
            if (length < 0)
                throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
            if (startIndex > text.Length - length) // Checks for int overflow
                throw new ArgumentException(SR.ArgumentOutOfRange_IndexLength);

            int code = 0;
            int stop = startIndex + length;
            if (ignoreCase)
            {
                for (int i = startIndex; i < stop;)
                {
                    int codePointAt = charUtils.CodePointAt(text, i, stop);
                    code = code * 31 + Character.ToLower(codePointAt, CultureInfo.InvariantCulture); // LUCENENET specific - need to use invariant culture to match Java
                    i += Character.CharCount(codePointAt);
                }
            }
            else
            {
                for (int i = startIndex; i < stop; i++)
                {
                    code = code * 31 + text[i];
                }
            }
            return code;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int GetHashCode(ICharSequence text)
        {
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)

            int code = 0;
            int length = text.Length;
            if (ignoreCase)
            {
                for (int i = 0; i < length;)
                {
                    int codePointAt = charUtils.CodePointAt(text, i);
                    code = code * 31 + Character.ToLower(codePointAt, CultureInfo.InvariantCulture); // LUCENENET specific - need to use invariant culture to match Java
                    i += Character.CharCount(codePointAt);
                }
            }
            else
            {
                for (int i = 0; i < length; i++)
                {
                    code = code * 31 + text[i];
                }
            }
            return code;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int GetHashCode(string text)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)

            int code = 0;
            int length = text.Length;
            if (ignoreCase)
            {
                for (int i = 0; i < length;)
                {
                    int codePointAt = charUtils.CodePointAt(text, i);
                    code = code * 31 + Character.ToLower(codePointAt, CultureInfo.InvariantCulture); // LUCENENET specific - need to use invariant culture to match Java
                    i += Character.CharCount(codePointAt);
                }
            }
            else
            {
                for (int i = 0; i < length; i++)
                {
                    code = code * 31 + text[i];
                }
            }
            return code;
        }

        #region For .NET Support LUCENENET

        /// <summary>
        /// The Lucene version corresponding to the compatibility behavior
        /// that this instance emulates
        /// </summary>
        public virtual LuceneVersion MatchVersion => matchVersion;

        /// <summary>
        /// Adds a placeholder with the given <paramref name="text"/> as the text.
        /// Primarily for internal use by <see cref="CharArraySet"/>.
        /// <para/>
        /// <b>NOTE:</b> If <c>ignoreCase</c> is <c>true</c> for this <see cref="CharArrayDictionary{TValue}"/>, the text array will be directly modified.
        /// </summary>
        /// <param name="text">A key with which the placeholder is associated.</param>
        /// <param name="startIndex">The position of the <paramref name="text"/> where the target text begins.</param>
        /// <param name="length">The total length of the <paramref name="text"/>.</param>
        /// <returns><c>true</c> if the text was added, <c>false</c> if the text already existed.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        internal virtual bool Put(char[] text, int startIndex, int length)
        {
            return PutImpl(text, startIndex, length, PLACEHOLDER) is null;
        }

        /// <summary>
        /// Adds a placeholder with the given <paramref name="text"/> as the text.
        /// Primarily for internal use by <see cref="CharArraySet"/>.
        /// <para/>
        /// <b>NOTE:</b> If <c>ignoreCase</c> is <c>true</c> for this <see cref="CharArrayDictionary{TValue}"/>, the text array will be directly modified.
        /// The user should never modify this text array after calling this method.
        /// </summary>
        /// <returns><c>true</c> if the text was added, <c>false</c> if the text already existed.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        internal virtual bool Put(char[] text)
        {
            return PutImpl(text, PLACEHOLDER) is null;
        }

        /// <summary>
        /// Adds a placeholder with the given <paramref name="text"/> as the text.
        /// Primarily for internal use by <see cref="CharArraySet"/>.
        /// </summary>
        /// <returns><c>true</c> if the text was added, <c>false</c> if the text already existed.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.</exception>
        internal virtual bool Put(ICharSequence text)
        {
            return PutImpl(text, PLACEHOLDER) is null;
        }

        /// <summary>
        /// Adds a placeholder with the given <paramref name="text"/> as the text.
        /// Primarily for internal use by <see cref="CharArraySet"/>.
        /// </summary>
        /// <returns><c>true</c> if the text was added, <c>false</c> if the text already existed.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        internal virtual bool Put(string text)
        {
            return PutImpl(text, PLACEHOLDER) is null;
        }

        /// <summary>
        /// Adds a placeholder with the given <paramref name="text"/> as the text.
        /// Primarily for internal use by <see cref="CharArraySet"/>.
        /// </summary>
        /// <returns><c>true</c> if the text was added, <c>false</c> if the text already existed.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        internal virtual bool Put<T>(T text)
        {
            return PutImpl(text, PLACEHOLDER) is null;
        }

        bool ICharArrayDictionary.Put(char[] text, int startIndex, int length) => Put(text, startIndex, length);
        bool ICharArrayDictionary.Put(char[] text) => Put(text);
        bool ICharArrayDictionary.Put(string text) => Put(text);
        bool ICharArrayDictionary.Put(ICharSequence text) => Put(text);
        bool ICharArrayDictionary.Put<T>(T text) => Put(text);

        /// <summary>
        /// Returns a copy of the current <see cref="CharArrayDictionary{TValue}"/> as a new instance of <see cref="CharArrayDictionary{TValue}"/>.
        /// Preserves the value of <c>matchVersion</c> and <c>ignoreCase</c> from the current instance.
        /// </summary>
        /// <returns> A copy of the current <see cref="CharArrayDictionary{TValue}"/> as a <see cref="CharArrayDictionary{TValue}"/>. </returns>
        // LUCENENET specific - allow .NET-like syntax for copying CharArrayDictionary
        public virtual CharArrayDictionary<TValue> ToCharArrayDictionary()
        {
            return CharArrayDictionary.Copy(this.matchVersion, (IDictionary<string, TValue>)this);
        }

        /// <summary>
        /// Returns a copy of the current <see cref="CharArrayDictionary{TValue}"/> as a new instance of <see cref="CharArrayDictionary{TValue}"/>
        /// using the specified <paramref name="matchVersion"/> value. Preserves the value of <c>ignoreCase</c> from the current instance.
        /// </summary>
        /// <param name="matchVersion">
        ///          compatibility match version see <a href="#version">Version
        ///          note</a> above for details. </param>
        /// <returns> A copy of the current <see cref="CharArrayDictionary{TValue}"/> as a <see cref="CharArrayDictionary{TValue}"/>. </returns>
        // LUCENENET specific - allow .NET-like syntax for copying CharArrayDictionary
        public virtual CharArrayDictionary<TValue> ToCharArrayDictionary(LuceneVersion matchVersion)
        {
            return CharArrayDictionary.Copy(matchVersion, (IDictionary<string, TValue>)this);
        }

        /// <summary>
        /// Returns a copy of the current <see cref="CharArrayDictionary{TValue}"/> as a new instance of <see cref="CharArrayDictionary{TValue}"/>
        /// using the specified <paramref name="matchVersion"/> and <paramref name="ignoreCase"/> values.
        /// </summary>
        /// <param name="matchVersion">
        ///          compatibility match version see <a href="#version">Version
        ///          note</a> above for details. </param>
        /// <param name="ignoreCase"><c>false</c> if and only if the set should be case sensitive otherwise <c>true</c>.</param>
        /// <returns> A copy of the current <see cref="CharArrayDictionary{TValue}"/> as a <see cref="CharArrayDictionary{TValue}"/>. </returns>
        // LUCENENET specific - allow .NET-like syntax for copying CharArrayDictionary
        public virtual CharArrayDictionary<TValue> ToCharArrayDictionary(LuceneVersion matchVersion, bool ignoreCase)
        {
            return new CharArrayDictionary<TValue>(matchVersion, this, ignoreCase);
        }

        /// <summary>
        /// Gets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get.</param>
        /// <param name="startIndex">The position of the <paramref name="text"/> where the target text begins.</param>
        /// <param name="length">The total length of the <paramref name="text"/>.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified text,
        /// if the text is found; otherwise, the default value for the type of the value parameter.
        /// This parameter is passed uninitialized.</param>
        /// <returns><c>true</c> if the <see cref="CharArrayDictionary{TValue}"/> contains an element with the specified text; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        public virtual bool TryGetValue(char[] text, int startIndex, int length, [MaybeNullWhen(returnValue: false)] out TValue value)
        {
            var val = values[GetSlot(text, startIndex, length)];
            if (val != null)
            {
                value = val.Value;
                return true;
            }
            value = default;
            return false;
        }

        /// <summary>
        /// Gets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified text,
        /// if the text is found; otherwise, the default value for the type of the value parameter.
        /// This parameter is passed uninitialized.</param>
        /// <returns><c>true</c> if the <see cref="CharArrayDictionary{TValue}"/> contains an element with the specified text; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool TryGetValue(char[] text, [MaybeNullWhen(returnValue: false)] out TValue value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            var val = values[GetSlot(text, 0, text.Length)];
            if (val != null)
            {
                value = val.Value;
                return true;
            }
            value = default;
            return false;
        }

        /// <summary>
        /// Gets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified text,
        /// if the text is found; otherwise, the default value for the type of the value parameter.
        /// This parameter is passed uninitialized.</param>
        /// <returns><c>true</c> if the <see cref="CharArrayDictionary{TValue}"/> contains an element with the specified text; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        public virtual bool TryGetValue(ICharSequence text, [MaybeNullWhen(returnValue: false)] out TValue value)
        {
            if (text is null || !text.HasValue)
                throw new ArgumentNullException(nameof(text));

            if (text is StringCharSequence strCs)
                return TryGetValue(strCs.Value!, out value);
            if (text is CharArrayCharSequence charArrayCs)
                return TryGetValue(charArrayCs.Value!, out value);
            if (text is StringBuilderCharSequence stringBuilderCs) // LUCENENET: Indexing into a StringBuilder is slow, so materialize
                return TryGetValue(stringBuilderCs.Value!.ToString(), out value);

            var val = values[GetSlot(text)];
            if (val != null)
            {
                value = val.Value;
                return true;
            }
            value = default;
            return false;
        }

        /// <summary>
        /// Gets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified text,
        /// if the text is found; otherwise, the default value for the type of the value parameter.
        /// This parameter is passed uninitialized.</param>
        /// <returns><c>true</c> if the <see cref="CharArrayDictionary{TValue}"/> contains an element with the specified text; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool TryGetValue(string text, [NotNullWhen(returnValue: false)] out TValue value)
        {
            var val = values[GetSlot(text)];
            if (val != null)
            {
                value = val.Value!;
                return true;
            }
            value = default!;
            return false;
        }

        /// <summary>
        /// Gets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified text,
        /// if the text is found; otherwise, the default value for the type of the value parameter.
        /// This parameter is passed uninitialized.</param>
        /// <returns><c>true</c> if the <see cref="CharArrayDictionary{TValue}"/> contains an element with the specified text; otherwise, <c>false</c>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual bool TryGetValue<T>(T text, [MaybeNullWhen(returnValue: false)] out TValue value)
        {
            if (text is null)
                throw new ArgumentNullException(nameof(text));

            // LUCENENET NOTE: Testing for *is* is at least 10x faster
            // than casting using *as* and then checking for null.
            // http://stackoverflow.com/q/1583050/181087
            if (text is string str)
                return TryGetValue(str, out value);
            if (text is char[] charArray)
                return TryGetValue(charArray, 0, charArray.Length, out value);
            if (text is ICharSequence cs)
                return TryGetValue(cs, out value);

            var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
            if (returnType == CharArrayDictionary.CharReturnType.String)
                return TryGetValue(s, out value);
            else
                return TryGetValue(chars, out value);
        }

        /// <summary>
        /// Gets or sets the value associated with the specified text.
        /// <para/>
        /// <b>Note:</b> If ignoreCase is <c>true</c> for this dictionary, the text array will be directly modified.
        /// </summary>
        /// <param name="text">The text of the value to get or set.</param>
        /// <param name="startIndex">The position of the <paramref name="text"/> where the target text begins.</param>
        /// <param name="length">The total length of the <paramref name="text"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> or <paramref name="length"/> is less than zero.</exception>
        /// <exception cref="ArgumentException"><paramref name="startIndex"/> and <paramref name="length"/> refer to a position outside of <paramref name="text"/>.</exception>
        public virtual TValue this[char[] text, int startIndex, int length]
        {
            get => Get(text, startIndex, length, throwIfNotFound: true);
            set => Set(text, startIndex, length, value);
        }

        /// <summary>
        /// Gets or sets the value associated with the specified text.
        /// <para/>
        /// <b>Note:</b> If ignoreCase is <c>true</c> for this dictionary, the text array will be directly modified.
        /// The user should never modify this text array after calling this setter.
        /// </summary>
        /// <param name="text">The text of the value to get or set.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual TValue this[char[] text]
        {
            get => Get(text, throwIfNotFound: true);
            set => Set(text, value);
        }

        /// <summary>
        /// Gets or sets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get or set.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="text"/> is <c>null</c>.
        /// <para/>
        /// -or-
        /// <para/>
        /// The <paramref name="text"/>'s <see cref="ICharSequence.HasValue"/> property returns <c>false</c>.
        /// </exception>
        public virtual TValue this[ICharSequence text]
        {
            get => Get(text, throwIfNotFound: true);
            set => Set(text, value);
        }

        /// <summary>
        /// Gets or sets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get or set.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual TValue this[string text]
        {
            get => Get(text, throwIfNotFound: true);
            set => Set(text, value);
        }

        /// <summary>
        /// Gets or sets the value associated with the specified text.
        /// </summary>
        /// <param name="text">The text of the value to get or set.</param>
        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
        public virtual TValue this[object text]
        {
            get => Get(text, throwIfNotFound: true);
            set => Set(text, value);
        }

        /// <summary>
        /// Gets a collection containing the keys in the <see cref="CharArrayDictionary{TValue}"/>.
        /// </summary>
        public virtual CharArraySet Keys => KeySet;

        ICollection<string> IDictionary<string, TValue>.Keys => KeySet;

        IEnumerable<string> IReadOnlyDictionary<string, TValue>.Keys => KeySet;


        private volatile ValueCollection? valueSet;

        /// <summary>
        /// Gets a collection containing the values in the <see cref="CharArrayDictionary{TValue}"/>.
        /// This specialized collection can be enumerated in order to read its values and
        /// overrides <see cref="object.ToString()"/> in order to display a string
        /// representation of the values.
        /// </summary>
        public ValueCollection Values
        {
            get
            {
                if (valueSet is null)
                {
                    valueSet = new ValueCollection(this);
                }
                return valueSet;
            }
        }

        ICollection<TValue> IDictionary<string, TValue>.Values => Values;

        IEnumerable<TValue> IReadOnlyDictionary<string, TValue>.Values => Values;

        #region Nested Class: ValueCollection

        /// <summary>
        /// Class that represents the values in the <see cref="CharArrayDictionary{TValue}"/>.
        /// </summary>
        // LUCENENET specific
        [DebuggerDisplay("Count = {Count}, Values = {ToString()}")]
        public sealed class ValueCollection : ICollection<TValue>, ICollection, IReadOnlyCollection<TValue>
        {
            private readonly CharArrayDictionary<TValue> dictionary;

            /// <summary>
            /// Initializes a new instance of <see cref="ValueCollection"/> for the provided <see cref="CharArrayDictionary{TValue}"/>.
            /// </summary>
            /// <param name="dictionary">The dictionary to read the values from.</param>
            /// <exception cref="ArgumentNullException"><paramref name="dictionary"/> is <c>null</c>.</exception>
            public ValueCollection(CharArrayDictionary<TValue> dictionary)
            {
                this.dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
            }

            /// <summary>
            /// Gets the number of elements contained in the <see cref="ValueCollection"/>.
            /// </summary>
            /// <remarks>
            /// Retrieving the value of this property is an O(1) operation.
            /// </remarks>
            public int Count => dictionary.Count;

            bool ICollection<TValue>.IsReadOnly => true;

            bool ICollection.IsSynchronized => false;

            object ICollection.SyncRoot => ((ICollection)dictionary).SyncRoot;

            void ICollection<TValue>.Add(TValue item)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ValueCollectionSet);
            }

            void ICollection<TValue>.Clear()
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ValueCollectionSet);
            }

            /// <summary>
            /// Determines whether the set contains a specific element.
            /// </summary>
            /// <param name="item">The element to locate in the set.</param>
            /// <returns><c>true</c> if the set contains item; otherwise, <c>false</c>.</returns>
            [SuppressMessage("Style", "IDE0002:Name can be simplified", Justification = "This is a false warning.")]
            public bool Contains(TValue item)
            {
                for (int i = 0; i < dictionary.values.Length; i++)
                {
                    var value = dictionary.values[i];
                    if (JCG.EqualityComparer<TValue>.Equals(value, item))
                        return true;
                }
                return false;
            }

            /// <summary>
            /// Copies the <see cref="ValueCollection"/> elements to an existing one-dimensional
            /// array, starting at the specified array index.
            /// </summary>
            /// <param name="array">The one-dimensional array that is the destination of the elements copied from
            /// the <see cref="ValueCollection"/>. The array must have zero-based indexing.</param>
            /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
            /// <exception cref="ArgumentNullException"><paramref name="array"/> is <c>null</c>.</exception>
            /// <exception cref="ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception>
            /// <exception cref="ArgumentException">The number of elements in the source <see cref="ValueCollection"/>
            /// is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination
            /// <paramref name="array"/>.</exception>
            /// <remarks>
            /// The elements are copied to the array in the same order in which the enumerator iterates through the
            /// <see cref="ValueCollection"/>.
            /// <para/>
            /// This method is an O(<c>n</c>) operation, where <c>n</c> is <see cref="Count"/>.
            /// </remarks>
            public void CopyTo(TValue[] array, int arrayIndex)
            {
                if (array is null)
                    throw new ArgumentNullException(nameof(array));
                if (arrayIndex < 0)
                    throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, SR.ArgumentOutOfRange_NeedNonNegNum);
                if (arrayIndex > array.Length || dictionary.Count > array.Length - arrayIndex)
                    throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);

                foreach (var entry in this)
                    array[arrayIndex++] = entry!;
            }

            void ICollection.CopyTo(Array array, int index)
            {
                if (array is null)
                    throw new ArgumentNullException(nameof(array));
                if (array.Rank != 1)
                    throw new ArgumentException(SR.Arg_RankMultiDimNotSupported, nameof(array));
                if (array.GetLowerBound(0) != 0)
                    throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
                if (index < 0)
                    throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
                if (array.Length - index < dictionary.Count)
                    throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);

                if (array is TValue[] values)
                {
                    CopyTo(values, index);
                }
                else
                {
                    try
                    {
                        object?[] objects = (object?[])array;
                        foreach (var entry in this)
                            objects[index++] = entry;
                    }
                    catch (ArrayTypeMismatchException)
                    {
                        throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
                    }
                }
            }

            /// <summary>
            /// Returns an enumerator that iterates through the <see cref="ValueCollection"/>.
            /// </summary>
            /// <returns>An enumerator that iterates through the <see cref="ValueCollection"/>.</returns>
            /// <remarks>
            /// An enumerator remains valid as long as the collection remains unchanged. If changes are made to
            /// the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably
            /// invalidated and the next call to <see cref="Enumerator.MoveNext()"/> or <see cref="IEnumerator.Reset()"/>
            /// throws an <see cref="InvalidOperationException"/>.
            /// <para/>
            /// This method is an <c>O(log n)</c> operation.
            /// </remarks>
            public Enumerator GetEnumerator()
            {
                return new Enumerator(dictionary);
            }

            IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => GetEnumerator();

            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

            bool ICollection<TValue>.Remove(TValue item)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ValueCollectionSet);
            }

            /// <summary>
            /// Returns a string that represents the current collection.
            /// <para/>
            /// The presentation has a specific format. It is enclosed by square
            /// brackets ("[]"). Elements are separated by ', ' (comma and space).
            /// <c>null</c> values are represented as the string "null".
            /// </summary>
            /// <returns>A string that represents the current collection.</returns>
            public override string ToString()
            {
                using var i = GetEnumerator();
                if (!i.HasNext)
                    return "[]";

                StringBuilder sb = new StringBuilder();
                sb.Append('[');
                while (i.MoveNext())
                {
                    TValue? value = i.Current;
                    if (sb.Length > 1)
                    {
                        sb.Append(',').Append(' ');
                    }
                    if (value is not null)
                        sb.Append(value.ToString());
                    else
                        sb.Append("null");
                }

                return sb.Append(']').ToString();
            }

            #region Nested Struct: Enumerator

            /// <summary>
            /// Enumerates the elements of a <see cref="ValueCollection"/>.
            /// </summary>
            /// <remarks>
            /// The <c>foreach</c> statement of the C# language (<c>for each</c> in C++, <c>For Each</c> in Visual Basic)
            /// hides the complexity of enumerators. Therefore, using <c>foreach</c> is recommended instead of directly manipulating the enumerator.
            /// <para/>
            /// Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection.
            /// <para/>
            /// Initially, the enumerator is positioned before the first element in the collection. At this position, the
            /// <see cref="Current"/> property is undefined. Therefore, you must call the
            /// <see cref="MoveNext()"/> method to advance the enumerator to the first element
            /// of the collection before reading the value of <see cref="Current"/>.
            /// <para/>
            /// The <see cref="Current"/> property returns the same object until
            /// <see cref="MoveNext()"/> is called. <see cref="MoveNext()"/>
            /// sets <see cref="Current"/> to the next element.
            /// <para/>
            /// If <see cref="MoveNext()"/> passes the end of the collection, the enumerator is
            /// positioned after the last element in the collection and <see cref="MoveNext()"/>
            /// returns <c>false</c>. When the enumerator is at this position, subsequent calls to <see cref="MoveNext()"/>
            /// also return <c>false</c>. If the last call to <see cref="MoveNext()"/> returned <c>false</c>,
            /// <see cref="Current"/> is undefined. You cannot set <see cref="Current"/>
            /// to the first element of the collection again; you must create a new enumerator object instead.
            /// <para/>
            /// An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection,
            /// such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call
            /// to <see cref="MoveNext()"/> or <see cref="IEnumerator.Reset()"/> throws an
            /// <see cref="InvalidOperationException"/>.
            /// <para/>
            /// The enumerator does not have exclusive access to the collection; therefore, enumerating through a collection is
            /// intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the
            /// collection during the entire enumeration. To allow the collection to be accessed by multiple threads for
            /// reading and writing, you must implement your own synchronization.
            /// </remarks>
            // LUCENENET specific
            public readonly struct Enumerator : IEnumerator<TValue>, IEnumerator
            {
                private readonly CharArrayDictionary<TValue>.Enumerator entryIterator;

                internal Enumerator(CharArrayDictionary<TValue> dictionary) // LUCENENET specific - marked internal to match .NET collections
                {
                    this.entryIterator = dictionary.GetEnumerator();
                }

                /// <summary>
                /// Gets the element at the current position of the enumerator.
                /// </summary>
                /// <remarks>
                /// <see cref="Current"/> is undefined under any of the following conditions:
                /// <list type="bullet">
                ///     <item><description>
                ///         The enumerator is positioned before the first element of the collection. That happens after an
                ///         enumerator is created or after the <see cref="IEnumerator.Reset()"/> method is called. The <see cref="MoveNext()"/>
                ///         method must be called to advance the enumerator to the first element of the collection before reading the value of
                ///         the <see cref="Current"/> property.
                ///     </description></item>
                ///     <item><description>
                ///         The last call to <see cref="MoveNext()"/> returned <c>false</c>, which indicates the end of the collection and that the
                ///         enumerator is positioned after the last element of the collection.
                ///     </description></item>
                ///     <item><description>
                ///         The enumerator is invalidated due to changes made in the collection, such as adding, modifying, or deleting elements.
                ///     </description></item>
                /// </list>
                /// <para/>
                /// <see cref="Current"/> does not move the position of the enumerator, and consecutive calls to <see cref="Current"/> return
                /// the same object until either <see cref="MoveNext()"/> or <see cref="IEnumerator.Reset()"/> is called.
                /// </remarks>
                public TValue Current => entryIterator.CurrentValue!;

                object IEnumerator.Current => entryIterator.CurrentValue!;

                /// <summary>
                /// Releases all resources used by the <see cref="Enumerator"/>.
                /// </summary>
                public void Dispose()
                {
                    entryIterator.Dispose();
                }

                /// <summary>
                /// Advances the enumerator to the next element of the <see cref="ValueCollection"/>.
                /// </summary>
                /// <returns><c>true</c> if the enumerator was successfully advanced to the next element;
                /// <c>false</c> if the enumerator has passed the end of the collection.</returns>
                /// <exception cref="InvalidOperationException">The collection was modified after the enumerator was created.</exception>
                /// <remarks>
                /// After an enumerator is created, the enumerator is positioned before the first element in the collection,
                /// and the first call to the <see cref="MoveNext()"/> method advances the enumerator to the first element
                /// of the collection.
                /// <para/>
                /// If MoveNext passes the end of the collection, the enumerator is positioned after the last element in the
                /// collection and <see cref="MoveNext()"/> returns <c>false</c>. When the enumerator is at this position,
                /// subsequent calls to <see cref="MoveNext()"/> also return <c>false</c>.
                /// <para/>
                /// An enumerator remains valid as long as the collection remains unchanged. If changes are made to the
                /// collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated
                /// and the next call to <see cref="MoveNext()"/> or <see cref="IEnumerator.Reset()"/> throws an
                /// <see cref="InvalidOperationException"/>.
                /// </remarks>
                public bool MoveNext()
                {
                    return entryIterator.MoveNext();
                }

                private void Reset()
                {
                    ((IEnumerator)entryIterator).Reset();
                }

                void IEnumerator.Reset() => Reset();

                internal bool HasNext => entryIterator.HasNext;
            }

            #endregion
        }

        #endregion Nested Class: ValueCollection

        /// <summary>
        /// <c>true</c> if the <see cref="CharArrayDictionary{TValue}"/> is read-only; otherwise <c>false</c>.
        /// </summary>
        public virtual bool IsReadOnly => false;

        #endregion For .NET Support LUCENENET

        /// <summary>
        /// Returns an enumerator that iterates through the <see cref="CharArrayDictionary{TValue}"/>.
        /// </summary>
        /// <returns>A <see cref="CharArrayDictionary{TValue}.Enumerator"/> for the
        /// <see cref="CharArrayDictionary{TValue}"/>.</returns>
        /// <remarks>
        /// For purposes of enumeration, each item is a <see cref="KeyValuePair{TKey, TValue}"/> structure
        /// representing a value and its text. There are also properties allowing direct access
        /// to the <see cref="T:char[]"/> array of each element and quick conversions to <see cref="string"/> or <see cref="ICharSequence"/>.
        /// <para/>
        /// The <c>foreach</c> statement of the C# language (<c>for each</c> in C++, <c>For Each</c> in Visual Basic)
        /// hides the complexity of enumerators. Therefore, using <c>foreach</c> is recommended instead of directly manipulating the enumerator.
        /// <para/>
        /// This enumerator can be used to read the data in the collection, or modify the corresponding value at the current position.
        /// <para/>
        /// Initially, the enumerator is positioned before the first element in the collection. At this position, the
        /// <see cref="Enumerator.Current"/> property is undefined. Therefore, you must call the
        /// <see cref="Enumerator.MoveNext()"/> method to advance the enumerator to the first element
        /// of the collection before reading the value of <see cref="Enumerator.Current"/>.
        /// <para/>
        /// The <see cref="Enumerator.Current"/> property returns the same object until
        /// <see cref="Enumerator.MoveNext()"/> is called. <see cref="Enumerator.MoveNext()"/>
        /// sets <see cref="Enumerator.Current"/> to the next element.
        /// <para/>
        /// If <see cref="Enumerator.MoveNext()"/> passes the end of the collection, the enumerator is
        /// positioned after the last element in the collection and <see cref="Enumerator.MoveNext()"/>
        /// returns <c>false</c>. When the enumerator is at this position, subsequent calls to <see cref="Enumerator.MoveNext()"/>
        /// also return <c>false</c>. If the last call to <see cref="Enumerator.MoveNext()"/> returned <c>false</c>,
        /// <see cref="Enumerator.Current"/> is undefined. You cannot set <see cref="Enumerator.Current"/>
        /// to the first element of the collection again; you must create a new enumerator object instead.
        /// <para/>
        /// An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection,
        /// such as adding, modifying, or deleting elements (other than through the <see cref="Enumerator.SetValue(TValue)"/> method),
        /// the enumerator is irrecoverably invalidated and the next call
        /// to <see cref="Enumerator.MoveNext()"/> or <see cref="IEnumerator.Reset()"/> throws an
        /// <see cref="InvalidOperationException"/>.
        /// <para/>
        /// The enumerator does not have exclusive access to the collection; therefore, enumerating through a collection is
        /// intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the
        /// collection during the entire enumeration. To allow the collection to be accessed by multiple threads for
        /// reading and writing, you must implement your own synchronization.
        /// <para/>
        /// Default implementations of collections in the <see cref="J2N.Collections.Generic"/> namespace are not synchronized.
        /// <para/>
        /// This method is an O(1) operation.
        /// </remarks>
        public Enumerator GetEnumerator()
        {
            return new Enumerator(this, Enumerator.KeyValuePair);
        }

        IEnumerator<KeyValuePair<string, TValue>> IEnumerable<KeyValuePair<string, TValue>>.GetEnumerator() => GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        ICharArrayDictionaryEnumerator ICharArrayDictionary.GetEnumerator() => GetEnumerator();

        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            return new Enumerator(this, Enumerator.DictEntry);
        }

        void IDictionary.Remove(object key)
        {
            throw UnsupportedOperationException.Create();
        }

        bool IDictionary<string, TValue>.Remove(string key)
        {
            throw UnsupportedOperationException.Create();
        }

        bool ICollection<KeyValuePair<string, TValue>>.Remove(KeyValuePair<string, TValue> item)
        {
            throw UnsupportedOperationException.Create();
        }

        [SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Following Microsoft's coding style")]
        void ICollection.CopyTo(Array array, int index)
        {
            if (array is null)
                throw new ArgumentNullException(nameof(array));
            if (array.Rank != 1)
                throw new ArgumentException(SR.Arg_RankMultiDimNotSupported);
            if (array.GetLowerBound(0) != 0)
                throw new ArgumentException(SR.Arg_NonZeroLowerBound);
            if (index < 0 || index > array.Length)
                throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
            if (array.Length - index < Count)
                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);

            if (array is KeyValuePair<string, TValue>[] strings)
            {
                CopyTo(strings, index);
            }
            else if (array is KeyValuePair<char[], TValue>[] chars)
            {
                CopyTo(chars, index);
            }
            else if (array is KeyValuePair<ICharSequence, TValue>[] charSequences)
            {
                CopyTo(charSequences, index);
            }
            else if (array is DictionaryEntry[] dictEntryArray)
            {
                foreach (var item in this)
                    dictEntryArray[index++] = new DictionaryEntry(item.Key, item.Value);
            }
            else
            {
                if (!(array is object[] objects))
                {
                    throw new ArgumentException(SR.Argument_InvalidArrayType);
                }
                try
                {
                    foreach (var item in this)
                        objects[index++] = item;
                }
                catch (ArrayTypeMismatchException)
                {
                    throw new ArgumentException(SR.Argument_InvalidArrayType);
                }
            }
        }

        bool IDictionary.IsFixedSize => false;

        bool IDictionary.IsReadOnly => IsReadOnly;

        ICollection IDictionary.Keys => Keys;

        ICollection IDictionary.Values => Values;

        bool ICollection.IsSynchronized => false;

        object ICollection.SyncRoot => this;

        object? IDictionary.this[object key]
        {
            get => Get(key, throwIfNotFound: false);
            set
            {
                if (key is null)
                    throw new ArgumentNullException(nameof(key));

                if (value is null && default(TValue) != null)
                    throw new ArgumentNullException(nameof(value));

                TValue val;
                try
                {
                    val = (TValue)value!;
                }
                catch (InvalidCastException)
                {
                    throw new ArgumentException(string.Format(SR.Arg_WrongType, value, typeof(TValue)), nameof(value));
                }
                var returnType = CharArrayDictionary.ConvertObjectToChars(key, out char[] chars, out string s);
                if (returnType == CharArrayDictionary.CharReturnType.String)
                {
                    Set(s, val);
                }
                else
                {
                    Set(chars, val);
                }
            }
        }

        void IDictionary.Add(object key, object? value)
        {
            if (key is null)
                throw new ArgumentNullException(nameof(key));

            TValue val;
            try
            {
                val = (TValue)value!;
            }
            catch (InvalidCastException)
            {
                throw new ArgumentException(string.Format(SR.Arg_WrongType, value, typeof(TValue)), nameof(value));
            }
            var returnType = CharArrayDictionary.ConvertObjectToChars(key, out char[] chars, out string s);
            if (returnType == CharArrayDictionary.CharReturnType.String)
            {
                Add(s, val);
            }
            else
            {
                Add(chars, val);
            }
        }

        bool IDictionary.Contains(object key) => ContainsKey(key);

        /// <summary>
        /// Gets the number of text/value pairs contained in the <see cref="CharArrayDictionary{TValue}"/>.
        /// </summary>
        public virtual int Count => count;

        /// <summary>
        /// Returns a string that represents the current object. (Inherited from <see cref="object"/>.)
        /// </summary>
        public override string ToString()
        {
            if (count == 0)
                return "{}";

            var sb = new StringBuilder("{");

            using (var iter1 = this.GetEnumerator())
            {
                while (iter1.MoveNext())
                {
                    if (sb.Length > 1)
                    {
                        sb.Append(", ");
                    }
                    sb.Append(iter1.CurrentKey);
                    sb.Append('=');
                    if (iter1.CurrentValue is not null)
                        sb.Append(iter1.CurrentValue);
                    else
                        sb.Append("null");
                }
            }

            return sb.Append('}').ToString();
        }



        // LUCENENET: Removed entrySet because in .NET we use the collection itself as the IEnumerable
        private CharArraySet? keySet = null;

        // LUCENENET: Removed entrySet(), createEntrySet() because in .NET we use the collection itself as the IEnumerable

        // LUCENENET: Removed originalKeySet() because we fixed infinite recursion
        // by adding a custom enumerator for KeyCollection.

        /// <summary>
        /// Returns an <see cref="CharArraySet"/> view on the dictionary's keys.
        /// The set will use the same <see cref="matchVersion"/> as this dictionary.
        /// </summary>
        private CharArraySet KeySet
        {
            get
            {
                if (keySet is null)
                {
                    // prevent adding of entries
                    keySet = new KeyCollection(this);
                }
                return keySet;
            }
        }

        #region Nested Class: KeyCollection

        // LUCENENET: This was an anonymous class in Java
        [DebuggerDisplay("Count = {Count}, Values = {ToString()}")]
        private sealed class KeyCollection : CharArraySet
        {
            internal KeyCollection(CharArrayDictionary<TValue> map)
                : base(map)
            {
            }

            public override bool IsReadOnly => true;

            public override bool Add<T>(T text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
            }
            public override bool Add(ICharSequence text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
            }
            public override bool Add(string text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
            }
            public override bool Add(char[] text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
            }
        }

        #endregion Nested Class: KeyCollection

        #region Nested Class: Enumerator

        /// <summary>
        /// Enumerates the elements of a <see cref="CharArrayDictionary{TValue}"/>.
        /// <para/>
        /// This enumerator exposes <see cref="CurrentKey"/> efficient access to the
        /// underlying <see cref="T:char[]"/>. It also has <see cref="CurrentKeyString"/>,
        /// <see cref="CurrentKeyCharSequence"/>, and <see cref="CurrentValue"/> properties for
        /// convenience.
        /// </summary>
        /// <remarks>
        /// The <c>foreach</c> statement of the C# language (<c>for each</c> in C++, <c>For Each</c> in Visual Basic)
        /// hides the complexity of enumerators. Therefore, using <c>foreach</c> is recommended instead of directly manipulating the enumerator.
        /// <para/>
        /// This enumerator can be used to read the data in the collection, or modify the corresponding value at the current position.
        /// <para/>
        /// Initially, the enumerator is positioned before the first element in the collection. At this position, the
        /// <see cref="Current"/> property is undefined. Therefore, you must call the
        /// <see cref="MoveNext()"/> method to advance the enumerator to the first element
        /// of the collection before reading the value of <see cref="Current"/>.
        /// <para/>
        /// The <see cref="Current"/> property returns the same object until
        /// <see cref="MoveNext()"/> is called. <see cref="MoveNext()"/>
        /// sets <see cref="Current"/> to the next element.
        /// <para/>
        /// If <see cref="MoveNext()"/> passes the end of the collection, the enumerator is
        /// positioned after the last element in the collection and <see cref="MoveNext()"/>
        /// returns <c>false</c>. When the enumerator is at this position, subsequent calls to <see cref="MoveNext()"/>
        /// also return <c>false</c>. If the last call to <see cref="MoveNext()"/> returned <c>false</c>,
        /// <see cref="Current"/> is undefined. You cannot set <see cref="Current"/>
        /// to the first element of the collection again; you must create a new enumerator object instead.
        /// <para/>
        /// An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection,
        /// such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call
        /// to <see cref="MoveNext()"/> or <see cref="IEnumerator.Reset()"/> throws an
        /// <see cref="InvalidOperationException"/>.
        /// <para/>
        /// The enumerator does not have exclusive access to the collection; therefore, enumerating through a collection is
        /// intrinsically not a thread-safe procedure. To guarantee thread safety during enumeration, you can lock the
        /// collection during the entire enumeration. To allow the collection to be accessed by multiple threads for
        /// reading and writing, you must implement your own synchronization.
        /// </remarks>
        // LUCENENET: An attempt was made to make this into a struct, but since it has mutable state that didn't work. So, this should remain a class.
        public sealed class Enumerator : IEnumerator<KeyValuePair<string, TValue>>, IDictionaryEnumerator, ICharArrayDictionaryEnumerator
        {
            private readonly CharArrayDictionary<TValue> dictionary;
            private readonly int getEnumeratorRetType;  // What should Enumerator.Current return?

            internal const int KeyValuePair = 1;
            internal const int DictEntry = 2;

            internal int pos = -1;
            internal int lastPos;
            internal readonly bool allowModify;

            private int version; // LUCENENET specific - track when the enumerator is broken by mutating the state of the original collection
            private bool notStartedOrEnded; // LUCENENET specific

            internal Enumerator(CharArrayDictionary<TValue> dictionary, int getEnumeratorRetType)
            {
                this.dictionary = dictionary;
                this.getEnumeratorRetType = getEnumeratorRetType;
                this.version = dictionary.version;
                this.allowModify = !dictionary.IsReadOnly;
                this.notStartedOrEnded = true;
                GoNext();
            }

            private void GoNext()
            {
                lastPos = pos;
                pos++;
                while (pos < dictionary.keys.Length && dictionary.keys[pos] is null)
                {
                    pos++;
                }
            }

            internal bool HasNext => pos < dictionary.keys.Length;

            /// <summary>
            /// Gets the current text as a <see cref="CharArrayCharSequence"/>.
            /// </summary>
            // LUCENENET specific - quick access to ICharSequence interface
            public ICharSequence CurrentKeyCharSequence
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    char[] key = dictionary.keys[lastPos];
                    if (key is null)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
                    return key.AsCharSequence();
                }
            }

            /// <summary>
            /// Gets the current text... do not modify the returned char[].
            /// </summary>
            [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
            [WritableArray]
            public char[] CurrentKey
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    char[] key = dictionary.keys[lastPos];
                    if (key is null)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
                    return key;
                }
            }

            /// <summary>
            /// Gets the current text as a newly created <see cref="string"/> object.
            /// </summary>
            public string CurrentKeyString
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    char[] key = dictionary.keys[lastPos];
                    if (key is null)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
                    return new string(key);
                }
            }

            /// <summary>
            /// Gets the value associated with the current text.
            /// </summary>
            [MaybeNull]
            public TValue CurrentValue
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
                    char[] key = dictionary.keys[lastPos];
                    if (key is null)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);

                    var val = dictionary.values[lastPos];
                    return val != null ? val.Value : default;
                }
            }

            /// <summary>
            /// Sets the value associated with the current text.
            /// </summary>
            /// <returns>Returns the value prior to the update.</returns>
            [return: MaybeNull]
            public TValue SetValue(TValue value)
            {
                if (notStartedOrEnded)
                    throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
                if (!allowModify)
                    throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);

                MapValue current = dictionary.values[lastPos];
                if (current is null)
                    throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);

                TValue? old = current.Value;
                // LUCENENET specific - increment the versions of both this enumerator
                // and the original collection so only this enumerator instance isn't broken.
                dictionary.version++;
                version++;
                current.Value = value;
                return old;
            }

            // LUCENENET: Next() and Remove() methods eliminated here

            #region Added for better .NET support LUCENENET

            /// <summary>
            /// Releases all resources used by the <see cref="Enumerator"/>.
            /// </summary>
            public void Dispose()
            {
                // nothing to do
            }

            /// <summary>
            /// Advances the enumerator to the next element of the <see cref="CharArrayDictionary{TValue}"/>.
            /// </summary>
            /// <returns><c>true</c> if the enumerator was successfully advanced to the next element;
            /// <c>false</c> if the enumerator has passed the end of the collection.</returns>
            /// <exception cref="InvalidOperationException">The collection was modified after the enumerator was created.</exception>
            /// <remarks>
            /// After an enumerator is created, the enumerator is positioned before the first element in the collection,
            /// and the first call to the <see cref="MoveNext()"/> method advances the enumerator to the first element
            /// of the collection.
            /// <para/>
            /// If <see cref="MoveNext()"/> passes the end of the collection, the enumerator is positioned after the last element in the
            /// collection and <see cref="MoveNext()"/> returns <c>false</c>. When the enumerator is at this position,
            /// subsequent calls to <see cref="MoveNext()"/> also return <c>false</c>.
            /// <para/>
            /// An enumerator remains valid as long as the collection remains unchanged. If changes are made to the
            /// collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated
            /// and the next call to <see cref="MoveNext()"/> or <see cref="IEnumerator.Reset()"/> throws an
            /// <see cref="InvalidOperationException"/>.
            /// </remarks>
            public bool MoveNext()
            {
                if (version != dictionary.version)
                    throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);

                if (!HasNext)
                {
                    notStartedOrEnded = true;
                    return false;
                }
                notStartedOrEnded = false;
                GoNext();
                return true;
            }

            private void Reset()
            {
                if (version != dictionary.version)
                    throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);

                pos = -1;
                notStartedOrEnded = true;
                GoNext();
            }

            void IEnumerator.Reset() => Reset();

            void ICharArrayDictionaryEnumerator.Reset() => Reset();

            /// <summary>
            /// Gets the element at the current position of the enumerator.
            /// </summary>
            /// <remarks>
            /// <see cref="Current"/> is undefined under any of the following conditions:
            /// <list type="bullet">
            ///     <item><description>
            ///         The enumerator is positioned before the first element of the collection. That happens after an
            ///         enumerator is created or after the <see cref="IEnumerator.Reset()"/> method is called. The <see cref="MoveNext()"/>
            ///         method must be called to advance the enumerator to the first element of the collection before reading the value of
            ///         the <see cref="Current"/> property.
            ///     </description></item>
            ///     <item><description>
            ///         The last call to <see cref="MoveNext()"/> returned <c>false</c>, which indicates the end of the collection and that the
            ///         enumerator is positioned after the last element of the collection.
            ///     </description></item>
            ///     <item><description>
            ///         The enumerator is invalidated due to changes made in the collection, such as adding, modifying, or deleting elements.
            ///     </description></item>
            /// </list>
            /// <para/>
            /// <see cref="Current"/> does not move the position of the enumerator, and consecutive calls to <see cref="Current"/> return
            /// the same object until either <see cref="MoveNext()"/> or <see cref="IEnumerator.Reset()"/> is called.
            /// </remarks>
            public KeyValuePair<string, TValue> Current
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    char[] key = dictionary.keys[lastPos];
                    if (key is null)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
                    MapValue value = dictionary.values[lastPos];
                    return new KeyValuePair<string, TValue>(new string(key), value.Value!);
                }
            }

            object IEnumerator.Current
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    char[] key = dictionary.keys[lastPos];
                    if (key is null)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
                    MapValue value = dictionary.values[lastPos];

                    if (getEnumeratorRetType == DictEntry)
                    {
                        return new DictionaryEntry(new string(key), value.Value);
                    }
                    else
                    {
                        return new KeyValuePair<string, TValue>(new string(key), value.Value!);
                    }
                }
            }

            object IDictionaryEnumerator.Key
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    return Current.Key;
                }
            }

            object? IDictionaryEnumerator.Value
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    return Current.Value;
                }
            }

            DictionaryEntry IDictionaryEnumerator.Entry
            {
                get
                {
                    if (notStartedOrEnded)
                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);

                    return new DictionaryEntry(Current.Key, Current.Value);
                }
            }

            bool ICharArrayDictionaryEnumerator.NotStartedOrEnded => notStartedOrEnded;

            #endregion
        }

        #endregion Nested Class: Enumerator

        // LUCENENET NOTE: The Java Lucene type MapEntry was removed here because it is not possible
        // to inherit the value type KeyValuePair{TKey, TValue} in .NET.

        // LUCENENET: EntrySet class removed because in .NET we get the entries by calling GetEnumerator() on the dictionary.

        // LUCENENET: Moved UnmodifiableMap static methods to CharArrayDictionary class

        // LUCENENET: Moved Copy static methods to CharArrayDictionary class

        // LUCENENET: Removed EmptyMap() - use Empty instead

        // LUCENENET: Moved UnmodifiableCharArraymap to CharArrayDictionary class

        // LUCENENET: Moved EmptyCharArrayDictionary to CharArrayDictionary class
    }

    /// <summary>
    /// LUCENENET specific interface used so <see cref="CharArraySet"/>
    /// can hold a reference to <see cref="CharArrayDictionary{TValue}"/> without
    /// knowing its generic closing type for TValue.
    /// </summary>
    internal interface ICharArrayDictionary
    {
        void Clear();
        bool ContainsKey(char[] text, int startIndex, int length);
        bool ContainsKey(char[] text);
        bool ContainsKey<T>(T text);
        bool ContainsKey(string text);
        bool ContainsKey(ICharSequence text);
        int Count { get; }
        bool IgnoreCase { get; }
        bool IsReadOnly { get; }
        LuceneVersion MatchVersion { get; }
        bool Put(char[] text, int startIndex, int length);
        bool Put(char[] text);
        bool Put(ICharSequence text);
        bool Put<T>(T text);
        bool Put(string text);
        void Set(char[] text, int startIndex, int length);
        void Set(char[] text);
        void Set(ICharSequence text);
        void Set<T>(T text);
        void Set(string text);
        ICharArrayDictionaryEnumerator GetEnumerator();
    }

    /// <summary>
    /// LUCENENET specific interface used so <see cref="CharArraySet"/> can
    /// iterate the keys of <see cref="CharArrayDictionary{TValue}"/> without
    /// knowing its generic closing type for TValue.
    /// </summary>
    internal interface ICharArrayDictionaryEnumerator : IDisposable
    {
        bool NotStartedOrEnded { get; }
        bool MoveNext();
        ICharSequence CurrentKeyCharSequence { get; }
        string CurrentKeyString { get; }
        char[] CurrentKey { get; }
        void Reset();
    }

    public static class CharArrayDictionary // LUCENENET specific: CA1052 Static holder types should be Static or NotInheritable
    {
        /// <summary>
        /// Returns a copy of the given dictionary as a <see cref="CharArrayDictionary{TValue}"/>. If the given dictionary
        /// is a <see cref="CharArrayDictionary{TValue}"/> the ignoreCase property will be preserved.
        /// <para>
        /// <b>Note:</b> If you intend to create a copy of another <see cref="CharArrayDictionary{TValue}"/> where
        /// the <see cref="LuceneVersion"/> of the source dictionary differs from its copy
        /// <see cref="CharArrayDictionary{TValue}.CharArrayDictionary(LuceneVersion, IDictionary{string, TValue}, bool)"/> should be used instead.
        /// The <see cref="Copy{TValue}(LuceneVersion, IDictionary{string, TValue})"/> will preserve the <see cref="LuceneVersion"/> of the
        /// source dictionary if it is an instance of <see cref="CharArrayDictionary{TValue}"/>.
        /// </para>
        /// </summary>
        /// <param name="matchVersion">
        ///          compatibility match version see <a href="#version">Version
        ///          note</a> above for details. This argument will be ignored if the
        ///          given dictionary is a <see cref="CharArrayDictionary{TValue}"/>. </param>
        /// <param name="dictionary">
        ///          a dictionary to copy </param>
        /// <returns> a copy of the given dictionary as a <see cref="CharArrayDictionary{TValue}"/>. If the given dictionary
        ///         is a <see cref="CharArrayDictionary{TValue}"/> the ignoreCase property as well as the
        ///         <paramref name="matchVersion"/> will be of the given dictionary will be preserved. </returns>
        public static CharArrayDictionary<TValue> Copy<TValue>(LuceneVersion matchVersion, IDictionary<string, TValue> dictionary)
        {
            if (dictionary == CharArrayDictionary<TValue>.Empty)
            {
                return CharArrayDictionary<TValue>.Empty;
            }

            if (dictionary is CharArrayDictionary<TValue> m)
            {
                // use fast path instead of iterating all values
                // this is even on very small sets ~10 times faster than iterating
                var keys = new char[m.keys.Length][];
                Arrays.Copy(m.keys, 0, keys, 0, keys.Length);
                var values = new CharArrayDictionary<TValue>.MapValue[m.values.Length];
                Arrays.Copy(m.values, 0, values, 0, values.Length);
                m = new CharArrayDictionary<TValue>(m) { keys = keys, values = values };
                return m;
            }
            return new CharArrayDictionary<TValue>(matchVersion, dictionary, false);
        }

        /// <summary>
        /// Used by <see cref="CharArraySet"/> to copy <see cref="CharArrayDictionary{TValue}"/> without knowing
        /// its generic type.
        /// </summary>
        internal static CharArrayDictionary<TValue> Copy<TValue>(LuceneVersion matchVersion, [DisallowNull] ICharArrayDictionary map)
        {
            return Copy(matchVersion, (IDictionary<string, TValue>)map);
        }

        /// <summary>
        /// Returns an unmodifiable <see cref="CharArrayDictionary{TValue}"/>. This allows to provide
        /// unmodifiable views of internal dictionary for "read-only" use.
        /// </summary>
        /// <param name="map">
        ///          a dictionary for which the unmodifiable dictionary is returned. </param>
        /// <returns> an new unmodifiable <see cref="CharArrayDictionary{TValue}"/>. </returns>
        /// <exception cref="ArgumentException">
        ///           if the given dictionary is <c>null</c>. </exception>
        [Obsolete("Use the CharArrayDictionary<TValue>.AsReadOnly() instance method instead. This method will be removed in 4.8.0 release candidate."), EditorBrowsable(EditorBrowsableState.Never)]
        public static CharArrayDictionary<TValue> UnmodifiableMap<TValue>(CharArrayDictionary<TValue> map)
        {
            if (map is null)
            {
                throw new ArgumentNullException(nameof(map)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
            }
            if (map == CharArrayDictionary<TValue>.Empty || map.Count == 0)
            {
                return CharArrayDictionary<TValue>.Empty;
            }
            if (map is CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>)
            {
                return map;
            }
            return new CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>(map);
        }

        /// <summary>
        /// Used by <see cref="CharArraySet"/> to create an <see cref="ReadOnlyCharArrayDictionary{TValue}"/> instance
        /// without knowing the type of <typeparamref name="TValue"/>.
        /// </summary>
        internal static ICharArrayDictionary UnmodifiableMap<TValue>(ICharArrayDictionary map)
        {
            if (map is null)
            {
                throw new ArgumentNullException(nameof(map)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
            }
            if (map == CharArrayDictionary<TValue>.Empty || map.Count == 0)
            {
                return CharArrayDictionary<TValue>.Empty;
            }
            if (map is CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>)
            {
                return map;
            }
            return new CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>(map);
        }

        #region Nested Class: ReadOnlyCharArrayDictionary<TValue>

        // package private CharArraySet instanceof check in CharArraySet
        internal class ReadOnlyCharArrayDictionary<TValue> : CharArrayDictionary<TValue>
        {
            public ReadOnlyCharArrayDictionary([DisallowNull] CharArrayDictionary<TValue> map)
                : base(map)
            { }

            public ReadOnlyCharArrayDictionary([DisallowNull] ICharArrayDictionary map)
                : base((CharArrayDictionary<TValue>)map)
            { }

            private protected override CharArrayDictionary<TValue> AsReadOnlyImpl() => this;

            public override void Clear()
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override bool Put(char[] text, int startIndex, int length, TValue value, [MaybeNullWhen(true)] out TValue previousValue)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override bool Put(char[] text, TValue value, [MaybeNullWhen(true)] out TValue previousValue)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override bool Put(ICharSequence text, TValue value, [MaybeNullWhen(true)] out TValue previousValue)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override bool Put(string text, TValue value, [MaybeNullWhen(true)] out TValue previousValue)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override bool Put<T>(T text, TValue value, [MaybeNullWhen(true)] out TValue previousValue)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override bool Put(char[] text, int startIndex, int length)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override bool Put(char[] text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override bool Put(ICharSequence text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override bool Put(string text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override bool Put<T>(T text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            // LUCENENET: Removed CreateEntrySet() method - we use IsReadOnly to control whether it can be written to

            #region Added for better .NET support LUCENENET

            public override bool IsReadOnly => true;

            public override void Add(string text, TValue value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void Add(char[] text, TValue value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void Add(ICharSequence text, TValue value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void Add<T>(T text, TValue value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override TValue this[char[] text, int startIndex, int length]
            {
                get => base[text, startIndex, length];
                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override TValue this[char[] text]
            {
                get => base[text];
                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override TValue this[ICharSequence text]
            {
                get => base[text];
                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override TValue this[string text]
            {
                get => base[text];
                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override TValue this[object text]
            {
                get => base[text];
                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll(IDictionary<string, TValue> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll(IDictionary<char[], TValue> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll(IDictionary<ICharSequence, TValue> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll<T>(IDictionary<T, TValue> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll(IEnumerable<KeyValuePair<string, TValue>> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll(IEnumerable<KeyValuePair<char[], TValue>> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll(IEnumerable<KeyValuePair<ICharSequence, TValue>> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            public override void PutAll<T>(IEnumerable<KeyValuePair<T, TValue>> collection)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(char[] text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(char[] text, TValue? value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(char[] text, int startIndex, int length)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(char[] text, int startIndex, int length, TValue? value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(ICharSequence text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(ICharSequence text, TValue? value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(string text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set(string text, TValue? value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set<T>(T text)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            internal override void Set<T>(T text, TValue? value)
            {
                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
            }

            #endregion
        }

        #endregion

        #region Nested Class: EmptyCharArrayDictionary<V>

        /// <summary>
        /// Empty <see cref="ReadOnlyCharArrayDictionary{V}"/> optimized for speed.
        /// Contains checks will always return <c>false</c> or throw
        /// NPE if necessary.
        /// </summary>
        internal class EmptyCharArrayDictionary<V> : ReadOnlyCharArrayDictionary<V>
        {
            [SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "Clearly a bug with code analysis - we need the suppression to stop the obsolete warning")]
            public EmptyCharArrayDictionary()
#pragma warning disable CS0618 // Type or member is obsolete
                : base(new CharArrayDictionary<V>(LuceneVersion.LUCENE_CURRENT, 0, false))
#pragma warning restore CS0618 // Type or member is obsolete
            {
            }

            public override bool ContainsKey(char[] text, int startIndex, int length)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                return false;
            }

            public override bool ContainsKey(char[] text)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                return false;
            }

            public override bool ContainsKey(ICharSequence text)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                return false;
            }

            public override bool ContainsKey<T>(T text)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                return false;
            }

            internal override V Get(char[] text, int startIndex, int length, bool throwIfNotFound = true)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                if (throwIfNotFound)
                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, new string(text, startIndex, length)));
                return default!;
            }

            internal override V Get(char[] text, bool throwIfNotFound = true)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                if (throwIfNotFound)
                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, new string(text)));
                return default!;
            }

            internal override V Get(ICharSequence text, bool throwIfNotFound = true)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                if (throwIfNotFound)
                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, text));
                return default!;
            }

            internal override V Get<T>(T text, bool throwIfNotFound = true)
            {
                if (text is null)
                    throw new ArgumentNullException(nameof(text));

                if (throwIfNotFound)
                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, text));
                return default!;
            }
        }

        #endregion

        private readonly static CultureInfo invariant = CultureInfo.InvariantCulture;
        private const string TrueString = "true";
        private const string FalseString = "false";


        internal enum CharReturnType
        {
            String = 0,
            CharArray = 1,
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static CharReturnType ConvertObjectToChars<T>(T key, out char[] chars, out string str)
        {
#if FEATURE_SPANFORMATTABLE
            Span<char> buffer = stackalloc char[256];
#else
            Span<char> buffer = stackalloc char[1];
#endif
            return ConvertObjectToChars(key, out chars, out str, buffer);
        }


        // LUCENENET: We need value types to be represented using the invariant
        // culture, so it is consistent regardless of the current culture.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static CharReturnType ConvertObjectToChars<T>(T key, out char[] chars, out string str, Span<char> reuse)
        {
            chars = Array.Empty<char>();
            str = string.Empty;

            if (key is null)
            {
                return CharReturnType.CharArray;
            }

            // Handle special cases
            if (key is string strResult)
            {
                str = strResult;
                return CharReturnType.String;
            }
            else if (key is char[] charArray)
            {
                chars = charArray;
                return CharReturnType.CharArray;
            }
            else if (key is IList<char> charList)
            {
                char[] result = new char[charList.Count];
                charList.CopyTo(result, arrayIndex: 0);
                chars = result;
                return CharReturnType.CharArray;
            }
            else if (key is StringBuilder stringBuilder)
            {
                char[] result = new char[stringBuilder.Length];
                stringBuilder.CopyTo(sourceIndex: 0, result, destinationIndex: 0, stringBuilder.Length);
                chars = result;
                return CharReturnType.CharArray;
            }

            // ICharSequence types
            else if (key is StringCharSequence strCs)
            {
                str = strCs.Value ?? string.Empty;
                return CharReturnType.String;
            }
            else if (key is CharArrayCharSequence charArrayCs)
            {
                chars = charArrayCs.Value ?? Array.Empty<char>();
                return CharReturnType.CharArray;
            }
            else if (key is StringBuilderCharSequence stringBuilderCs && stringBuilderCs.HasValue)
            {
                var sb = stringBuilderCs.Value!;
                char[] result = new char[sb.Length];
                sb.CopyTo(sourceIndex: 0, result, destinationIndex: 0, sb.Length);
                chars = result;
                return CharReturnType.CharArray;
            }
            else if (key is ICharSequence cs && cs.HasValue)
            {
                int length = cs.Length;
                char[] result = new char[length];
                for (int i = 0; i < length; i++)
                {
                    result[i] = cs[i];
                }
                chars = result;
                return CharReturnType.CharArray;
            }

            // These must be done prior to checking ISpanFormattable and IFormattable
            else if (key is bool b)
            {
                str = b ? TrueString : FalseString;
                return CharReturnType.String;
            }
            else if (key is double d)
            {
                str = J2N.Numerics.Double.ToString(d, invariant);
                return CharReturnType.String;
            }
            else if (key is float f)
            {
                str = J2N.Numerics.Single.ToString(f, invariant);
                return CharReturnType.String;
            }
            else if (key is J2N.Numerics.Number number)
            {
                str = number.ToString(invariant);
                return CharReturnType.String;
            }

#if FEATURE_SPANFORMATTABLE
            else if (key is ISpanFormattable spanFormattable &&
                spanFormattable.TryFormat(reuse, out int charsWritten, string.Empty.AsSpan(), invariant))
            {
                chars = reuse.Slice(0, charsWritten).ToArray();
                return CharReturnType.CharArray;
            }
#endif
            else if (key is IFormattable formattable)
            {
                str = formattable.ToString(string.Empty, invariant);
                return CharReturnType.String;
            }

            using var context = new CultureContext(invariant);
            str = key.ToString() ?? string.Empty;
            return CharReturnType.String;
        }
    }

    /// <summary>
    /// Extensions to <see cref="IDictionary{TKey, TValue}"/> for <see cref="CharArrayDictionary{TValue}"/>.
    /// </summary>
    // LUCENENET specific - allow .NET-like syntax for copying CharArrayDictionary
    public static class DictionaryExtensions
    {
        /// <summary>
        /// Returns a copy of the current <see cref="IDictionary{TKey, TValue}"/> as a <see cref="CharArrayDictionary{TValue}"/>
        /// using the specified <paramref name="matchVersion"/> value.
        /// </summary>
        /// <typeparam name="TValue">The type of dictionary value.</typeparam>
        /// <param name="dictionary">
        ///          A <see cref="IDictionary{TKey, TValue}"/> to copy. </param>
        /// <param name="matchVersion">
        ///          compatibility match version see <a href="#version">Version
        ///          note</a> above for details. </param>
        /// <returns> A copy of the current dictionary as a <see cref="CharArrayDictionary{TValue}"/>. </returns>
        /// <exception cref="ArgumentNullException"><paramref name="dictionary"/> is <c>null</c>.</exception>
        public static CharArrayDictionary<TValue> ToCharArrayDictionary<TValue>(this IDictionary<string, TValue> dictionary, LuceneVersion matchVersion)
        {
            if (dictionary is null)
                throw new ArgumentNullException(nameof(dictionary));

            return CharArrayDictionary.Copy<TValue>(matchVersion, dictionary);
        }

        /// <summary>
        /// Returns a copy of the current <see cref="IDictionary{TKey, TValue}"/> as a <see cref="CharArrayDictionary{TValue}"/>
        /// using the specified <paramref name="matchVersion"/> and <paramref name="ignoreCase"/> values.
        /// </summary>
        /// <typeparam name="TValue">The type of dictionary value.</typeparam>
        /// <param name="dictionary">
        ///          A <see cref="IDictionary{TKey, TValue}"/> to copy. </param>
        /// <param name="matchVersion">
        ///          compatibility match version see <a href="#version">Version
        ///          note</a> above for details. </param>
        /// <param name="ignoreCase"><c>false</c> if and only if the set should be case sensitive otherwise <c>true</c>.</param>
        /// <returns> A copy of the current dictionary as a <see cref="CharArrayDictionary{TValue}"/>. </returns>
        /// <exception cref="ArgumentNullException"><paramref name="dictionary"/> is <c>null</c>.</exception>
        public static CharArrayDictionary<TValue> ToCharArrayDictionary<TValue>(this IDictionary<string, TValue> dictionary, LuceneVersion matchVersion, bool ignoreCase)
        {
            if (dictionary is null)
                throw new ArgumentNullException(nameof(dictionary));

            return new CharArrayDictionary<TValue>(matchVersion, dictionary.Count, ignoreCase);
        }
    }

    /// <summary>
    /// LUCENENET specific. Just a class to make error messages easier to manage in one place.
    /// Ideally, these would be in resources so they can be localized (eventually), but at least
    /// this half-measure will make that somewhat easier to do and is guaranteed not to cause
    /// performance issues.
    /// </summary>
    internal static class SR
    {
        public const string Arg_ArrayPlusOffTooSmall = "Destination array is not long enough to copy all the items in the collection. Check array index and length.";
        public const string Arg_KeyNotFoundWithKey = "The given text '{0}' was not present in the dictionary.";
        public const string Arg_NonZeroLowerBound = "The lower bound of target array must be zero.";
        public const string Arg_RankMultiDimNotSupported = "Only single dimensional arrays are supported for the requested action.";
        public const string Arg_WrongType = "The value '{0}' is not of type '{1}' and cannot be used in this generic collection.";

        public const string Argument_AddingDuplicate = "An item with the same text has already been added. Key: {0}";
        public const string Argument_InvalidArrayType = "Target array type is not compatible with the type of items in the collection.";

        public const string ArgumentOutOfRange_IndexLength = "Index and length must refer to a location within the string.";
        public const string ArgumentOutOfRange_NeedNonNegNum = "Non-negative number required.";

        public const string InvalidOperation_EnumFailedVersion = "Collection was modified after the enumerator was instantiated.";
        public const string InvalidOperation_EnumOpCantHappen = "Enumeration has either not started or has already finished.";

        public const string NotSupported_KeyCollectionSet = "Mutating a text collection derived from a dictionary is not allowed.";
        public const string NotSupported_ReadOnlyCollection = "Collection is read-only.";
        public const string NotSupported_ValueCollectionSet = "Mutating a value collection derived from a dictionary is not allowed.";
    }
}
