You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2022/11/17 19:53:33 UTC

[lucenenet] branch master updated: BREAKING: Refactored CharArraySet and CharArrayMap (now CharArrayDictionary) (#762)

This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git


The following commit(s) were added to refs/heads/master by this push:
     new 987274e99 BREAKING: Refactored CharArraySet and CharArrayMap (now CharArrayDictionary) (#762)
987274e99 is described below

commit 987274e99cecc5a3530d9348848a92ce14dec30a
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Fri Nov 18 02:53:28 2022 +0700

    BREAKING: Refactored CharArraySet and CharArrayMap (now CharArrayDictionary) (#762)
    
    * BUG: Lucene.Net.Analysis.Util (CharArraySet + CharArrayMap): Fixed IsReadOnly flag to return the proper value reflecting read-only state. Added tests.
    
    * Lucene.Net.Analysis.Util (CharArraySet + CharArrayMap): Don't call overridable members in constructor (See #670). Added some missing guard clauses.
    
    * BREAKING: Lucene.Net.Analysis.Util (CharArraySet + CharArrayMap): Removed Remove(), IntersectWith(), ExceptWith(), SymmetricExceptWith(), Contains(KeyValuePair) from public APIs by implementing collection interfaces explicitly.
    
    * Lucene.Net.Analysis.Util (CharArraySet + CharArrayMap): Added ToCharArraySet() and ToCharArrayMap() methods and extension methods to make copying collections easier.
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArrayMap: Renamed EntryIterator > Enumerator, removed HasNext, NextKey(), and NextKeyString(), added CurrentKey and CurrentKeyString properties. Removed EntrySet_ class, EntrySet() method.
    
    * BREAKING: Lucene.Net.Analysis.Util: Renamed CharArrayMap > CharArrayDictionary.
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArrayDictionary: Removed EmptyMap() method and created public Empty static field to replace it.
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArraySet: Renamed EMPTY_SET > Empty.
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary: Added missing guard clauses and updated documentation.
    
    * PERFORMANCE: Lucene.Net.Analysis.Util.CharArrayMap: Use CurrentKeyString and CurrentKeyValue to avoid allocation of KeyValuePair
    
    * BUG: Lucene.Net.Analysis.Util.CharArrayMap::Equals(): Use JCG.EqualityComparer when comparing values for equality.
    
    * Lucene.Net.Analysis.Util.CharArraySet: Added missing guard clauses and added documentation.
    
    * PERFORMANCE/BUG: Lucene.Net.Analysis.Util.CharArrayDictionary: Added cases for string and ICharSequence to object overload of PutImpl, ContainsKey, Get, and TryGetValue. Convert unknown types to string in invariant context.
    
    * PERFORMANCE: Lucene.Net.Analysis.Util.CharArrayDictionary: Added optimized PutImpl(string, MapValue) implementation that only allocates a char[] if it is required.
    
    * PERFORMANCE: Lucene.Net.Analysis.Util.CharArrayDictionary: Added Set/SetImpl methods that don't look up the previous value for use in the CharArrayDictionary.this[] overloads.
    
    * Lucene.Net.Analysis.Util.CharArraySet: Changed signature of UnionWith(IEnumerable<string>) to return a bool instead of void. Added explicit interface implementation to adhere to the ISet<string> contract.
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArraySet: Marked ContainsAll() overloads obsolete, since this is duplicate functionality of ISet<T>.IsSupersetOf().
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArrayDictionary: Moved OriginalKeySet property from the public API to explicitly defined on the ICharArrayDictionary interface. Previously it was hidden from Intellisense, so this will have minimal impact.
    
    * Lucene.Net.Analysis.Util (CharArrayDictionary + CharArraySet): Consolidated KeyCollection and UnmodifiableCharArraySet into a single KeyCollection enumerator. Migrated KeyCollection.KeyEnumerator to CharArraySet and renamed it Enumerator. Added ICharArrayDictionaryEnumerator so the necessary members are visible without using generics.
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary: Added documentation to Put() methods that accept a value indicating that this[key] = value is more efficient if the return value is unused.
    
    * PERFORMANCE: Lucene.Net.Analysis.Common: Updated all classes that use CharArrayDictionary<TValue>.Put() and discard the value to use CharArrayDictionary<TValue>.this[] instead.
    
    * Lucene.Net.Analysis.CharArrayDictionary: Added Set() overloads to use to populate CharArraySet
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArraySet::ctor(): Renamed parameter from c > collection.
    
    * Lucene.Net.Analysis.Util.CharArraySet: Added constructor overloads for ICollection<char[]> and ICollection<ICharSequence>.
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary::Copy(): Use pattern matching when converting to CharArrayDictionary<TValue>
    
    * PERFORMANCE: Lucene.Net.Analysis.Util.CharArrayDictionary: Fixed enumerators to use CurrentKey and CurrentValue where appropriate to reduce allocations
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary: Added KeyValuePair<char[], TValue> overload of CopyTo
    
    * Lucene.Net.Analysis.Util.CharArraySet: Fixed implementation of SetEquals() so it matches any IEnumerable<T> type with the same values (accounting for case sensitivity). Added overloads for IEnumerable<char[]>, IEnumerable<ICharSequence> and IEnumerable<T> (object). Added tests.
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary<TValue>: Added error checking to ensure enumerator instances throw InvalidOperationException when the collection state is mutated, or the enumerator is positioned before or after the bounds of the collection.
    
    * Lucene.Net.Analysis.Util.CharArraySet: Added overloads for IEnumerable<char[]> and IEnumerable<ICharSequence> for IsSubset(), IsSuperset(), IsProperSubset() and IsProperSuperset() + tests.
    
    * Lucene.Net.Analysis.Util.CharArraySet: Added overloads of CopyTo() for IList<char[]> and ICharSequence[].
    
    * Lucene.Net.Analysis.Util.CharArraySet: Added tests for IsSubset(), IsProperSubset(), IsSuperset(), IsProperSuperset(), Overlaps(), SetEquals() with null values in the comparison set. Added overloads of char[] and ICharSequence for Overlaps().
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary: Added CopyTo() overload for ICharSequence + added tests for all 3 overloads
    
    * BREAKING: Lucene.Net.Analysis.Util (CharArraySet + CharArrayDictionary<TValue>): Renamed constructor parameters c > collection, startSize > capacity. Changed type of collection from ICollection<T> to IEnumerable<T> to match .NET collections.
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary<TValue>::ToString(): Print "null" when there is a null value to match Java.
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArraySet: Changed parameter type of Copy() and CopySet() methods from ICollection<T> to IEnumerable<T>. Renamed the parameter from set > collection.
    
    * BUG: Lucene.Net.Analysis.Util.CharArrayDictionary<TValue>::ctor(): Don't call virtual methods in the constructor.
    
    * Lucene.Net.Analysis.Util (CharArraySet + CharArrayDictionary): Implemented standard interfaces for collections and enumerators. Consolidated error messages in new SR class.
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArrayDictionary: Removed Get() overloads from the public API. Refactored them to throw KeyNotFoundException instead of returning default(TValue). Enabled nullable reference type support and fixed warnings.
    
    * BREAKING: Lucene.Net.Analysis.Util.CharArrayDictionary: Refactored Put() overloads allow for value types without requiring them to be made nullable. The signature was changed to return a bool and the previousValue (that was returned before) was made into an out parameter.
    
    * BREAKING: Lucene.Net.Analysis.Util (CharArraySet + CharArrayDictionary): Changed all object overloads of common methods from object to T to allow passing through value types without boxing.
    
    * Lucene.Net.Analysis.Util (CharArraySet + CharArrayDictionary): Reworked ConvertObjectToChars() and optimized ICharSequence paths. Fixed both to check ICharSequence.HasValue before using and throw ArgumentNullException if it is not true when setting keys.
    
    * BREAKING: Removed Lucene.Net.Analysis.Util.CharArraySetExtensions. These are edge cases that are now handled by ConvertObjectToChars().
    
    * BREAKING: Removed Lucene.Net.Analysis.Util.CharArrayDictionaryExtensions. These are edge cases that are now handled by ConvertObjectToChars().
    
    * PERFORMANCE: Lucene.Net.Analysis.Util.CharArrayDictionary::Copy(): Use Span<T> when copying the dictionary/set.
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary.Enumerator::CurrentValue: Set to MaybeNull to match SetValue()
    
    * Lucene.Net.Analysis.Util (CharArraySet + CharArrayDictionary): Added documentation for public members
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary: Renamed UnmodifiableCharArrayDictionary > ReadOnlyCharArrayDictionary to conform with .NET conventions.
    
    * BREAKING: Lucene.Net.Analysis.Util (CharArraySet + CharArrayDictionary): Renamed all method parameters to be consistently using text (instead of key) and startIndex (instead of offset)
    
    * BUG: Lucene.Net.Analysis.Util: Fixed this[char[], int, int] setter so it will correctly set an array representing startIndex and length as the key. Added overloads of Put() to allow setting slices of char[].
    
    * Lucene.Net.Analysis.Util.CharArraySet: Added overloads of Add() to allow setting slices of char[].
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary: Fixed Set() and Add() overloads
    
    * BREAKING: Lucene.Net.Analysis.Util: Removed Put() overloads that don't accept a value from the public API. They were only intended to be used by CharArraySet.
    
    * BREAKING: Removed Add(KeyValuePair<string, TValue>) overload from the public API.
    
    * Lucene.Net.Analysis.Util.CharArrayDictionary: Added constructor overloads for char[] and ICharSequence.
    
    * BUG: Lucene.Net.Analysis.Util.CharArraySet::Empty: Changed empty generic closing type for the related CharArrayDictionary from string to object.
    
    * Lucene.Net.Analysis.Util: Changed extension methods to use IEnumerable<T> for better interop with LINQ.
---
 Directory.Build.targets                            |   18 +-
 .../Analysis/Ar/ArabicAnalyzer.cs                  |    2 +-
 .../Analysis/Bg/BulgarianAnalyzer.cs               |    2 +-
 .../Analysis/Br/BrazilianAnalyzer.cs               |    2 +-
 .../Analysis/Ca/CatalanAnalyzer.cs                 |    2 +-
 .../Analysis/CharFilter/HTMLStripCharFilter.cs     |   14 +-
 .../Analysis/Ckb/SoraniAnalyzer.cs                 |    2 +-
 .../Analysis/Core/StopFilter.cs                    |   16 +-
 .../Analysis/Cz/CzechAnalyzer.cs                   |    2 +-
 .../Analysis/Da/DanishAnalyzer.cs                  |    2 +-
 .../Analysis/De/GermanAnalyzer.cs                  |    2 +-
 .../Analysis/En/EnglishAnalyzer.cs                 |    2 +-
 .../Analysis/En/KStemmer.cs                        |   45 +-
 .../Analysis/Es/SpanishAnalyzer.cs                 |    2 +-
 .../Analysis/Eu/BasqueAnalyzer.cs                  |    2 +-
 .../Analysis/Fi/FinnishAnalyzer.cs                 |    2 +-
 .../Analysis/Fr/FrenchAnalyzer.cs                  |    2 +-
 .../Analysis/Ga/IrishAnalyzer.cs                   |    2 +-
 .../Analysis/Gl/GalicianAnalyzer.cs                |    2 +-
 .../Analysis/Hi/HindiAnalyzer.cs                   |    2 +-
 .../Analysis/Hu/HungarianAnalyzer.cs               |    2 +-
 .../Analysis/Hunspell/Stemmer.cs                   |    5 +-
 .../Analysis/Hy/ArmenianAnalyzer.cs                |    2 +-
 .../Analysis/Id/IndonesianAnalyzer.cs              |    2 +-
 .../Analysis/It/ItalianAnalyzer.cs                 |    2 +-
 .../Analysis/Lv/LatvianAnalyzer.cs                 |    2 +-
 .../Analysis/Nl/DutchAnalyzer.cs                   |   36 +-
 .../Analysis/Nl/DutchStemFilter.cs                 |    4 +-
 .../Analysis/No/NorwegianAnalyzer.cs               |    2 +-
 .../Analysis/Pt/PortugueseAnalyzer.cs              |    2 +-
 .../Analysis/Ro/RomanianAnalyzer.cs                |    2 +-
 .../Analysis/Ru/RussianAnalyzer.cs                 |    2 +-
 .../Analysis/Sv/SwedishAnalyzer.cs                 |    2 +-
 .../Analysis/Synonym/SlowSynonymFilter.cs          |    6 +-
 .../Analysis/Synonym/SlowSynonymMap.cs             |   11 +-
 .../Analysis/Tr/TurkishAnalyzer.cs                 |    2 +-
 .../Analysis/Util/CharArrayMap.cs                  | 4233 ++++++++++++--------
 .../Analysis/Util/CharArraySet.cs                  | 2228 +++++++----
 .../Analysis/Util/StopwordAnalyzerBase.cs          |    2 +-
 .../Analysis/Util/WordlistLoader.cs                |    4 +-
 .../Uk/UkrainianMorfologikAnalyzer.cs              |    2 +-
 .../SmartChineseAnalyzer.cs                        |    4 +-
 .../Pl/PolishAnalyzer.cs                           |    2 +-
 .../Analysis/Ar/TestArabicAnalyzer.cs              |    4 +-
 .../Analysis/Bg/TestBulgarianAnalyzer.cs           |    4 +-
 .../Analysis/Br/TestBrazilianStemmer.cs            |    2 +-
 .../Analysis/Cjk/TestCJKAnalyzer.cs                |    2 +-
 .../Analysis/Ckb/TestSoraniAnalyzer.cs             |    6 +-
 .../Analysis/Core/TestRandomChains.cs              |    6 +-
 .../Analysis/Cz/TestCzechAnalyzer.cs               |    2 +-
 .../Analysis/De/TestGermanAnalyzer.cs              |    2 +-
 .../Analysis/Fr/TestFrenchAnalyzer.cs              |    6 +-
 .../Analysis/Nl/TestDutchStemmer.cs                |   14 +-
 .../Analysis/Synonym/TestSynonymMap.cs             |    4 +-
 .../Analysis/Th/TestThaiAnalyzer.cs                |    8 +-
 .../Analysis/Util/TestCharArrayMap.cs              |  435 +-
 .../Analysis/Util/TestCharArraySet.cs              |  886 +++-
 .../Support/TestApiConsistency.cs                  |    2 +-
 .../Icu/Segmentation/TestWithCJKBigramFilter.cs    |    4 +-
 .../Suggest/Analyzing/BlendedInfixSuggesterTest.cs |   10 +-
 src/Lucene.Net/Support/DictionaryExtensions.cs     |    2 +-
 61 files changed, 5403 insertions(+), 2680 deletions(-)

diff --git a/Directory.Build.targets b/Directory.Build.targets
index 3065ed7af..cfd7fd835 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -22,15 +22,23 @@
 
   <Import Project=".build/dependencies.props" Condition="Exists('.build/dependencies.props')" />
 
+  <!-- Features in .NET 6.x only -->
+  <PropertyGroup Condition=" $(TargetFramework.StartsWith('net6.')) ">
+
+    <DefineConstants>$(DefineConstants);FEATURE_SPANFORMATTABLE</DefineConstants>
+
+  </PropertyGroup>
+
   <!-- Features in .NET 5.x and .NET 6.x only -->
   <PropertyGroup Condition=" $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) ">
 
     <DefineConstants>$(DefineConstants);FEATURE_ASPNETCORE_ENDPOINT_CONFIG</DefineConstants>
+    <DefineConstants>$(DefineConstants);FEATURE_READONLYSET</DefineConstants>
 
   </PropertyGroup>
   
   <!-- Features in .NET Core 3.x, .NET 5.x, and .NET 6.x only -->
-  <PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp3.')) Or $(TargetFramework.StartsWith('net5.')) Or '$(TargetFramework)' == 'net6.0' ">
+  <PropertyGroup Condition=" $(TargetFramework.StartsWith('netcoreapp3.')) Or $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) ">
 
     <DefineConstants>$(DefineConstants);FEATURE_ARGITERATOR</DefineConstants>
     <DefineConstants>$(DefineConstants);FEATURE_DICTIONARY_REMOVE_CONTINUEENUMERATION</DefineConstants>
@@ -39,7 +47,7 @@
   </PropertyGroup>
   
   <!-- Features in .NET Standard, .NET Core, .NET 5.x, and .NET 6.x only (no .NET Framework support) -->
-  <PropertyGroup Condition=" $(TargetFramework.StartsWith('netstandard')) Or $(TargetFramework.StartsWith('netcoreapp')) Or '$(TargetFramework)' == 'net5.0' Or '$(TargetFramework)' == 'net6.0' ">
+  <PropertyGroup Condition=" $(TargetFramework.StartsWith('netstandard')) Or $(TargetFramework.StartsWith('netcoreapp')) Or $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) ">
     
     <DefineConstants>$(DefineConstants);NETSTANDARD</DefineConstants>
     <DefineConstants>$(DefineConstants);FEATURE_ARRAYEMPTY</DefineConstants>
@@ -50,7 +58,7 @@
   </PropertyGroup>
 
   <!-- Features in .NET Standard 2.1, .NET 5.x, and .NET 6.x only -->
-  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' Or $(TargetFramework.StartsWith('netcoreapp3.')) Or '$(TargetFramework)' == 'net5.0' Or '$(TargetFramework)' == 'net6.0' ">
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' Or $(TargetFramework.StartsWith('netcoreapp3.')) Or $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) ">
 
     <DefineConstants>$(DefineConstants);FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR</DefineConstants>
     <DefineConstants>$(DefineConstants);FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE</DefineConstants>
@@ -61,14 +69,14 @@
   </PropertyGroup>
 
   <!-- Features in .NET Standard 2.x, .NET Core 2.x, .NET Core 3.x, .NET 5.x, and .NET 6.x -->
-  <PropertyGroup Condition=" $(TargetFramework.StartsWith('netstandard2.')) Or $(TargetFramework.StartsWith('netcoreapp2.')) Or $(TargetFramework.StartsWith('netcoreapp3.')) Or '$(TargetFramework)' == 'net5.0' Or '$(TargetFramework)' == 'net6.0' ">
+  <PropertyGroup Condition=" $(TargetFramework.StartsWith('netstandard2.')) Or $(TargetFramework.StartsWith('netcoreapp2.')) Or $(TargetFramework.StartsWith('netcoreapp3.')) Or $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) ">
 
     <DefineConstants>$(DefineConstants);FEATURE_ICONFIGURATIONROOT_PROVIDERS</DefineConstants>
 
   </PropertyGroup>
 
   <!-- Features in .NET Framework 4.5+, .NET Standard 2.x, .NET Core 2.x, .NET Core 3.x, .NET 5.x, and .NET 6.x  -->
-  <PropertyGroup Condition=" $(TargetFramework.StartsWith('net4')) Or $(TargetFramework.StartsWith('netstandard2.')) Or $(TargetFramework.StartsWith('netcoreapp2.')) Or $(TargetFramework.StartsWith('netcoreapp3.')) Or '$(TargetFramework)' == 'net5.0' Or '$(TargetFramework)' == 'net6.0' ">
+  <PropertyGroup Condition=" $(TargetFramework.StartsWith('net4')) Or $(TargetFramework.StartsWith('netstandard2.')) Or $(TargetFramework.StartsWith('netcoreapp2.')) Or $(TargetFramework.StartsWith('netcoreapp3.')) Or $(TargetFramework.StartsWith('net5.')) Or $(TargetFramework.StartsWith('net6.')) ">
 
     <DefineConstants>$(DefineConstants);FEATURE_ASSEMBLY_GETCALLINGASSEMBLY</DefineConstants>
     <DefineConstants>$(DefineConstants);FEATURE_FILESTREAM_LOCK</DefineConstants>
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ar/ArabicAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ar/ArabicAnalyzer.cs
index 7e91bdd8a..041bfcb40 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Ar/ArabicAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Ar/ArabicAnalyzer.cs
@@ -99,7 +99,7 @@ namespace Lucene.Net.Analysis.Ar
         /// <param name="stopwords">
         ///          a stopword set </param>
         public ArabicAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Bg/BulgarianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Bg/BulgarianAnalyzer.cs
index 63f9a550d..5c121f82f 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Bg/BulgarianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Bg/BulgarianAnalyzer.cs
@@ -89,7 +89,7 @@ namespace Lucene.Net.Analysis.Bg
         /// Builds an analyzer with the given stop words.
         /// </summary>
         public BulgarianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Br/BrazilianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Br/BrazilianAnalyzer.cs
index 5c8661019..83c341e54 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Br/BrazilianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Br/BrazilianAnalyzer.cs
@@ -77,7 +77,7 @@ namespace Lucene.Net.Analysis.Br
         /// <summary>
         /// Contains words that should be indexed but not stemmed.
         /// </summary>
-        private CharArraySet excltable = CharArraySet.EMPTY_SET;
+        private CharArraySet excltable = CharArraySet.Empty;
 
         /// <summary>
         /// Builds an analyzer with the default stop words (<see cref="DefaultStopSet"/>).
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ca/CatalanAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ca/CatalanAnalyzer.cs
index c4f9700ec..d82b84472 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Ca/CatalanAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Ca/CatalanAnalyzer.cs
@@ -93,7 +93,7 @@ namespace Lucene.Net.Analysis.Ca
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public CatalanAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/CharFilter/HTMLStripCharFilter.cs b/src/Lucene.Net.Analysis.Common/Analysis/CharFilter/HTMLStripCharFilter.cs
index 217a76807..efc80ba27 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/CharFilter/HTMLStripCharFilter.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/CharFilter/HTMLStripCharFilter.cs
@@ -30688,13 +30688,13 @@ namespace Lucene.Net.Analysis.CharFilters
                 {"amp", "AMP" },
             };
 
-        private static readonly CharArrayMap<char> entityValues = LoadEntityValues();
+        private static readonly CharArrayDictionary<char> entityValues = LoadEntityValues();
 
-        private static CharArrayMap<char> LoadEntityValues() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
+        private static CharArrayDictionary<char> LoadEntityValues() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
         {
-            CharArrayMap<char> entityValues
+            CharArrayDictionary<char> entityValues
 #pragma warning disable 612, 618
-            = new CharArrayMap<char>(LuceneVersion.LUCENE_CURRENT, 253, false);
+            = new CharArrayDictionary<char>(LuceneVersion.LUCENE_CURRENT, 253, false);
 #pragma warning restore 612, 618
             string[] entities = {
                 "AElig", "\u00C6", "Aacute", "\u00C1", "Acirc", "\u00C2",
@@ -30774,10 +30774,10 @@ namespace Lucene.Net.Analysis.CharFilters
             for (int i = 0; i < entities.Length; i += 2)
             {
                 var value = entities[i + 1][0];
-                entityValues.Put(entities[i], value);
+                entityValues[entities[i]] = value;
                 if (upperCaseVariantsAccepted.TryGetValue(entities[i], out string upperCaseVariant) && upperCaseVariant != null)
                 {
-                    entityValues.Put(upperCaseVariant, value);
+                    entityValues[upperCaseVariant] = value;
                 }
             }
             return entityValues;
@@ -31611,7 +31611,7 @@ namespace Lucene.Net.Analysis.CharFilters
                             int length = YyLength;
                             inputSegment.Write(zzBuffer, zzStartRead, length);
                             entitySegment.Clear();
-                            char ch = entityValues.Get(zzBuffer, zzStartRead, length);
+                            char ch = entityValues[zzBuffer, zzStartRead, length];
                             entitySegment.Append(ch);
                             outputSegment = entitySegment;
                             YyBegin(CHARACTER_REFERENCE_TAIL);
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ckb/SoraniAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ckb/SoraniAnalyzer.cs
index 524e492b4..072c399a4 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Ckb/SoraniAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Ckb/SoraniAnalyzer.cs
@@ -84,7 +84,7 @@ namespace Lucene.Net.Analysis.Ckb
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public SoraniAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Core/StopFilter.cs b/src/Lucene.Net.Analysis.Common/Analysis/Core/StopFilter.cs
index ea4da357f..641a76bf2 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Core/StopFilter.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Core/StopFilter.cs
@@ -1,4 +1,4 @@
-// Lucene version compatibility level 4.8.1
+// Lucene version compatibility level 4.8.1
 using Lucene.Net.Analysis.TokenAttributes;
 using Lucene.Net.Analysis.Util;
 using Lucene.Net.Util;
@@ -115,6 +115,20 @@ namespace Lucene.Net.Analysis.Core
             return stopSet;
         }
 
+        /// <summary>
+        /// Creates a stopword set from the given stopword list. </summary>
+        /// <param name="matchVersion"> <see cref="LuceneVersion"/> to enable correct Unicode 4.0 behavior in the returned set if Version > 3.0 </param>
+        /// <param name="stopWords"> A List of <see cref="string"/>s or <see cref="T:char[]"/> or any other ToString()-able list representing the stopwords </param>
+        /// <param name="ignoreCase"> if true, all words are lower cased first </param>
+        /// <returns> A Set (<see cref="CharArraySet"/>) containing the words </returns>
+        // LUCENENET specific - Optimization to go through the string version of UnionWith
+        public static CharArraySet MakeStopSet(LuceneVersion matchVersion, IList<string> stopWords, bool ignoreCase)
+        {
+            var stopSet = new CharArraySet(matchVersion, stopWords.Count, ignoreCase);
+            stopSet.UnionWith(stopWords);
+            return stopSet;
+        }
+
         /// <summary>
         /// Returns the next input Token whose Term is not a stop word.
         /// </summary>
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Cz/CzechAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Cz/CzechAnalyzer.cs
index 169f1261d..0f52bb4e7 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Cz/CzechAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Cz/CzechAnalyzer.cs
@@ -99,7 +99,7 @@ namespace Lucene.Net.Analysis.Cz
         /// <param name="matchVersion"> <see cref="LuceneVersion"/> to match </param>
         /// <param name="stopwords"> a stopword set </param>
         public CzechAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Da/DanishAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Da/DanishAnalyzer.cs
index bb5fdf273..8a41fd86a 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Da/DanishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Da/DanishAnalyzer.cs
@@ -86,7 +86,7 @@ namespace Lucene.Net.Analysis.Da
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public DanishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/De/GermanAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/De/GermanAnalyzer.cs
index b88168e62..a3038cd5f 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/De/GermanAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/De/GermanAnalyzer.cs
@@ -137,7 +137,7 @@ namespace Lucene.Net.Analysis.De
         /// <param name="stopwords">
         ///          a stopword set </param>
         public GermanAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/En/EnglishAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/En/EnglishAnalyzer.cs
index a163775ca..5975fa42b 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/En/EnglishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/En/EnglishAnalyzer.cs
@@ -61,7 +61,7 @@ namespace Lucene.Net.Analysis.En
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public EnglishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/En/KStemmer.cs b/src/Lucene.Net.Analysis.Common/Analysis/En/KStemmer.cs
index 77281f902..f4c395b68 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/En/KStemmer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/En/KStemmer.cs
@@ -339,11 +339,11 @@ namespace Lucene.Net.Analysis.En
             }
         }
 
-        private static readonly CharArrayMap<DictEntry> dict_ht = InitializeDictHash();
+        private static readonly CharArrayDictionary<DictEntry> dict_ht = InitializeDictHash();
 
         // caching off 
         // 
-        // private int maxCacheSize; private CharArrayMap{String} cache =
+        // private int maxCacheSize; private CharArrayDictionary{String} cache =
         // null; private static final String SAME = "SAME"; // use if stemmed form is
         // the same
 
@@ -357,7 +357,7 @@ namespace Lucene.Net.Analysis.En
         private int k;
 
         // private void initializeStemHash() { if (maxCacheSize > 0) cache = new
-        // CharArrayMap<String>(maxCacheSize,false); }
+        // CharArrayDictionary<String>(maxCacheSize,false); }
 
         private char FinalChar => word[k];
 
@@ -388,20 +388,20 @@ namespace Lucene.Net.Analysis.En
             }
         }
 
-        private static CharArrayMap<DictEntry> InitializeDictHash()
+        private static CharArrayDictionary<DictEntry> InitializeDictHash()
         {
             DictEntry defaultEntry;
             DictEntry entry;
 
 #pragma warning disable 612, 618
-            CharArrayMap<DictEntry> d = new CharArrayMap<DictEntry>(LuceneVersion.LUCENE_CURRENT, 1000, false);
+            CharArrayDictionary<DictEntry> d = new CharArrayDictionary<DictEntry>(LuceneVersion.LUCENE_CURRENT, 1000, false);
 #pragma warning restore 612, 618
             for (int i = 0; i < exceptionWords.Length; i++)
             {
                 if (!d.ContainsKey(exceptionWords[i]))
                 {
                     entry = new DictEntry(exceptionWords[i], true);
-                    d.Put(exceptionWords[i], entry);
+                    d[exceptionWords[i]] = entry;
                 }
                 else
                 {
@@ -414,7 +414,7 @@ namespace Lucene.Net.Analysis.En
                 if (!d.ContainsKey(directConflations[i][0]))
                 {
                     entry = new DictEntry(directConflations[i][1], false);
-                    d.Put(directConflations[i][0], entry);
+                    d[directConflations[i][0]] = entry;
                 }
                 else
                 {
@@ -427,7 +427,7 @@ namespace Lucene.Net.Analysis.En
                 if (!d.ContainsKey(countryNationality[i][0]))
                 {
                     entry = new DictEntry(countryNationality[i][1], false);
-                    d.Put(countryNationality[i][0], entry);
+                    d[countryNationality[i][0]] = entry;
                 }
                 else
                 {
@@ -444,7 +444,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(array[i]))
                 {
-                    d.Put(array[i], defaultEntry);
+                    d[array[i]] = defaultEntry;
                 }
                 else
                 {
@@ -457,7 +457,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(array[i]))
                 {
-                    d.Put(array[i], defaultEntry);
+                    d[array[i]] = defaultEntry;
                 }
                 else
                 {
@@ -470,7 +470,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(array[i]))
                 {
-                    d.Put(array[i], defaultEntry);
+                    d[array[i]] = defaultEntry;
                 }
                 else
                 {
@@ -483,7 +483,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(array[i]))
                 {
-                    d.Put(array[i], defaultEntry);
+                    d[array[i]] = defaultEntry;
                 }
                 else
                 {
@@ -496,7 +496,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(array[i]))
                 {
-                    d.Put(array[i], defaultEntry);
+                    d[array[i]] = defaultEntry;
                 }
                 else
                 {
@@ -509,7 +509,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(array[i]))
                 {
-                    d.Put(array[i], defaultEntry);
+                    d[array[i]] = defaultEntry;
                 }
                 else
                 {
@@ -522,7 +522,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(array[i]))
                 {
-                    d.Put(array[i], defaultEntry);
+                    d[array[i]] = defaultEntry;
                 }
                 else
                 {
@@ -534,7 +534,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(KStemData8.data[i]))
                 {
-                    d.Put(KStemData8.data[i], defaultEntry);
+                    d[KStemData8.data[i]] = defaultEntry;
                 }
                 else
                 {
@@ -546,7 +546,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(supplementDict[i]))
                 {
-                    d.Put(supplementDict[i], defaultEntry);
+                    d[supplementDict[i]] = defaultEntry;
                 }
                 else
                 {
@@ -558,7 +558,7 @@ namespace Lucene.Net.Analysis.En
             {
                 if (!d.ContainsKey(properNouns[i]))
                 {
-                    d.Put(properNouns[i], defaultEntry);
+                    d[properNouns[i]] = defaultEntry;
                 }
                 else
                 {
@@ -651,8 +651,7 @@ namespace Lucene.Net.Analysis.En
             {
                 return matchedEntry;
             }
-            DictEntry e = dict_ht.Get(word.Array, 0, word.Length);
-            if (e != null && !e.exception)
+            if (dict_ht.TryGetValue(word.Array, 0, word.Length, out DictEntry e) && e != null && !e.exception)
             {
                 matchedEntry = e; // only cache if it's not an exception.
             }
@@ -770,8 +769,7 @@ namespace Lucene.Net.Analysis.En
             // thisLookup); } else { // System.out.println("new lookup:" + thisLookup);
             // }
 
-            matchedEntry = dict_ht.Get(word.Array, 0, word.Length);
-            return matchedEntry != null;
+            return dict_ht.TryGetValue(word.Array, 0, word.Length, out matchedEntry) && matchedEntry != null;
         }
 
         // Set<String> lookups = new HashSet<>();
@@ -1872,8 +1870,7 @@ namespace Lucene.Net.Analysis.En
 
             // first check the stemmer dictionaries, and avoid using the
             // cache if it's in there.
-            DictEntry entry = dict_ht.Get(term, 0, len);
-            if (entry != null)
+            if (dict_ht.TryGetValue(term, 0, len, out DictEntry entry) && entry != null)
             {
                 if (entry.root != null)
                 {
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Es/SpanishAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Es/SpanishAnalyzer.cs
index db38db95e..41948c6f6 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Es/SpanishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Es/SpanishAnalyzer.cs
@@ -94,7 +94,7 @@ namespace Lucene.Net.Analysis.Es
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public SpanishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Eu/BasqueAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Eu/BasqueAnalyzer.cs
index 3cdf7d084..1e2239992 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Eu/BasqueAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Eu/BasqueAnalyzer.cs
@@ -81,7 +81,7 @@ namespace Lucene.Net.Analysis.Eu
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public BasqueAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Fi/FinnishAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Fi/FinnishAnalyzer.cs
index e445d6c98..874f234e5 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Fi/FinnishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Fi/FinnishAnalyzer.cs
@@ -86,7 +86,7 @@ namespace Lucene.Net.Analysis.Fi
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public FinnishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Fr/FrenchAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Fr/FrenchAnalyzer.cs
index 2a95ab789..49ae19ea1 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Fr/FrenchAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Fr/FrenchAnalyzer.cs
@@ -153,7 +153,7 @@ namespace Lucene.Net.Analysis.Fr
         /// <param name="stopwords">
         ///          a stopword set </param>
         public FrenchAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ga/IrishAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ga/IrishAnalyzer.cs
index b4e572c20..87c4c585b 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Ga/IrishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Ga/IrishAnalyzer.cs
@@ -98,7 +98,7 @@ namespace Lucene.Net.Analysis.Ga
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public IrishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Gl/GalicianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Gl/GalicianAnalyzer.cs
index 87e6f1d49..5c38ab5ac 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Gl/GalicianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Gl/GalicianAnalyzer.cs
@@ -84,7 +84,7 @@ namespace Lucene.Net.Analysis.Gl
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public GalicianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Hi/HindiAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Hi/HindiAnalyzer.cs
index 828b4faa6..c4ed75f09 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Hi/HindiAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Hi/HindiAnalyzer.cs
@@ -95,7 +95,7 @@ namespace Lucene.Net.Analysis.Hi
         /// <param name="version"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public HindiAnalyzer(LuceneVersion version, CharArraySet stopwords)
-            : this(version, stopwords, CharArraySet.EMPTY_SET)
+            : this(version, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Hu/HungarianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Hu/HungarianAnalyzer.cs
index 557d6666a..b8cdeb5fa 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Hu/HungarianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Hu/HungarianAnalyzer.cs
@@ -87,7 +87,7 @@ namespace Lucene.Net.Analysis.Hu
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public HungarianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Hunspell/Stemmer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Hunspell/Stemmer.cs
index 520e57481..e15e6365c 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Hunspell/Stemmer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Hunspell/Stemmer.cs
@@ -1,5 +1,6 @@
 // Lucene version compatibility level 4.10.4
 using J2N.Numerics;
+using J2N.Text;
 using Lucene.Net.Analysis.Util;
 using Lucene.Net.Diagnostics;
 using Lucene.Net.Store;
@@ -253,10 +254,10 @@ namespace Lucene.Net.Analysis.Hunspell
             IList<CharsRef> deduped = new JCG.List<CharsRef>();
             foreach (CharsRef s in stems)
             {
-                if (!terms.Contains(s))
+                if (!terms.Contains((ICharSequence)s)) // LUCENENET: Cast to get to ICharSequence overload
                 {
                     deduped.Add(s);
-                    terms.Add(s);
+                    terms.Add((ICharSequence)s); // LUCENENET: Cast to get to ICharSequence overload
                 }
             }
             return deduped;
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Hy/ArmenianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Hy/ArmenianAnalyzer.cs
index aff5eec13..e92f9a32a 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Hy/ArmenianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Hy/ArmenianAnalyzer.cs
@@ -82,7 +82,7 @@ namespace Lucene.Net.Analysis.Hy
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public ArmenianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Id/IndonesianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Id/IndonesianAnalyzer.cs
index 8d761c858..0fa5129a4 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Id/IndonesianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Id/IndonesianAnalyzer.cs
@@ -81,7 +81,7 @@ namespace Lucene.Net.Analysis.Id
         /// <param name="stopwords">
         ///          a stopword set </param>
         public IndonesianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/It/ItalianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/It/ItalianAnalyzer.cs
index 659d4098d..e3f19783e 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/It/ItalianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/It/ItalianAnalyzer.cs
@@ -102,7 +102,7 @@ namespace Lucene.Net.Analysis.It
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public ItalianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Lv/LatvianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Lv/LatvianAnalyzer.cs
index c9c152c32..75904c9aa 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Lv/LatvianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Lv/LatvianAnalyzer.cs
@@ -85,7 +85,7 @@ namespace Lucene.Net.Analysis.Lv
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public LatvianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchAnalyzer.cs
index 08579e941..396bd7cab 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchAnalyzer.cs
@@ -70,7 +70,7 @@ namespace Lucene.Net.Analysis.Nl
         private static class DefaultSetHolder
         {
             internal static readonly CharArraySet DEFAULT_STOP_SET = LoadDefaultStopSet();
-            internal static readonly CharArrayMap<string> DEFAULT_STEM_DICT = LoadDefaultStemDict();
+            internal static readonly CharArrayDictionary<string> DEFAULT_STEM_DICT = LoadDefaultStemDict();
             private static CharArraySet LoadDefaultStopSet() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
             {
                 try
@@ -90,15 +90,15 @@ namespace Lucene.Net.Analysis.Nl
 
             }
 
-            private static CharArrayMap<string> LoadDefaultStemDict() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
+            private static CharArrayDictionary<string> LoadDefaultStemDict() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
             {
 #pragma warning disable 612, 618
-                var DEFAULT_STEM_DICT = new CharArrayMap<string>(LuceneVersion.LUCENE_CURRENT, 4, false);
+                var DEFAULT_STEM_DICT = new CharArrayDictionary<string>(LuceneVersion.LUCENE_CURRENT, 4, false);
 #pragma warning restore 612, 618
-                DEFAULT_STEM_DICT.Put("fiets", "fiets"); //otherwise fiet
-                DEFAULT_STEM_DICT.Put("bromfiets", "bromfiets"); //otherwise bromfiet
-                DEFAULT_STEM_DICT.Put("ei", "eier");
-                DEFAULT_STEM_DICT.Put("kind", "kinder");
+                DEFAULT_STEM_DICT["fiets"] = "fiets"; //otherwise fiet
+                DEFAULT_STEM_DICT["bromfiets"] = "bromfiets"; //otherwise bromfiet
+                DEFAULT_STEM_DICT["ei"] = "eier";
+                DEFAULT_STEM_DICT["kind"] = "kinder";
                 return DEFAULT_STEM_DICT;
             }
         }
@@ -112,12 +112,12 @@ namespace Lucene.Net.Analysis.Nl
         /// <summary>
         /// Contains words that should be indexed but not stemmed.
         /// </summary>
-        private CharArraySet excltable = CharArraySet.EMPTY_SET;
+        private CharArraySet excltable = CharArraySet.Empty;
 
         private readonly StemmerOverrideFilter.StemmerOverrideMap stemdict;
 
         // null if on 3.1 or later - only for bw compat
-        private readonly CharArrayMap<string> origStemdict;
+        private readonly CharArrayDictionary<string> origStemdict;
         private readonly LuceneVersion matchVersion;
 
         /// <summary>
@@ -125,17 +125,17 @@ namespace Lucene.Net.Analysis.Nl
         /// and a few default entries for the stem exclusion table.
         /// </summary>
         public DutchAnalyzer(LuceneVersion matchVersion)
-              : this(matchVersion, DefaultSetHolder.DEFAULT_STOP_SET, CharArraySet.EMPTY_SET, DefaultSetHolder.DEFAULT_STEM_DICT)
+              : this(matchVersion, DefaultSetHolder.DEFAULT_STOP_SET, CharArraySet.Empty, DefaultSetHolder.DEFAULT_STEM_DICT)
         {
             // historically, only this ctor populated the stem dict!!!!!
         }
 
         public DutchAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET,
+              : this(matchVersion, stopwords, CharArraySet.Empty,
 #pragma warning disable 612, 618
                     matchVersion.OnOrAfter(LuceneVersion.LUCENE_36) ?
 #pragma warning restore 612, 618
-                    DefaultSetHolder.DEFAULT_STEM_DICT : CharArrayMap<string>.EmptyMap())
+                    DefaultSetHolder.DEFAULT_STEM_DICT : CharArrayDictionary<string>.Empty)
         {
             // historically, this ctor never the stem dict!!!!!
             // so we populate it only for >= 3.6
@@ -146,13 +146,13 @@ namespace Lucene.Net.Analysis.Nl
 #pragma warning disable 612, 618
                     matchVersion.OnOrAfter(LuceneVersion.LUCENE_36) ?
 #pragma warning restore 612, 618
-                    DefaultSetHolder.DEFAULT_STEM_DICT : CharArrayMap<string>.EmptyMap())
+                    DefaultSetHolder.DEFAULT_STEM_DICT : CharArrayDictionary<string>.Empty)
         {
             // historically, this ctor never the stem dict!!!!!
             // so we populate it only for >= 3.6
         }
 
-        public DutchAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords, CharArraySet stemExclusionTable, CharArrayMap<string> stemOverrideDict)
+        public DutchAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords, CharArraySet stemExclusionTable, CharArrayDictionary<string> stemOverrideDict)
         {
             this.matchVersion = matchVersion;
             this.stoptable = CharArraySet.Copy(matchVersion, stopwords).AsReadOnly();
@@ -162,19 +162,19 @@ namespace Lucene.Net.Analysis.Nl
 #pragma warning restore 612, 618
             {
                 this.stemdict = null;
-                this.origStemdict = CharArrayMap.Copy(matchVersion, stemOverrideDict).AsReadOnly();
+                this.origStemdict = CharArrayDictionary.Copy(matchVersion, stemOverrideDict).AsReadOnly();
             }
             else
             {
                 this.origStemdict = null;
                 // we don't need to ignore case here since we lowercase in this analyzer anyway
                 StemmerOverrideFilter.Builder builder = new StemmerOverrideFilter.Builder(false);
-                using (CharArrayMap<string>.EntryIterator iter = (CharArrayMap<string>.EntryIterator)stemOverrideDict.EntrySet().GetEnumerator())
+                using (var iter = stemOverrideDict.GetEnumerator())
                 {
                     CharsRef spare = new CharsRef();
-                    while (iter.HasNext)
+                    while (iter.MoveNext())
                     {
-                        char[] nextKey = iter.NextKey();
+                        char[] nextKey = iter.CurrentKey;
                         spare.CopyChars(nextKey, 0, nextKey.Length);
                         builder.Add(spare.Chars, iter.CurrentValue);
                     }
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchStemFilter.cs b/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchStemFilter.cs
index 500e64003..27404c072 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchStemFilter.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Nl/DutchStemFilter.cs
@@ -113,13 +113,13 @@ namespace Lucene.Net.Analysis.Nl
         /// Set dictionary for stemming, this dictionary overrules the algorithm,
         /// so you can correct for a particular unwanted word-stem pair.
         /// </summary>
-        public CharArrayMap<string> StemDictionary
+        public CharArrayDictionary<string> StemDictionary
         {
             get // LUCENENET NOTE: Added getter per MSDN guidelines
             {
                 if (stemmer != null)
                 {
-                    return stemmer.StemDictionary as CharArrayMap<string>;
+                    return stemmer.StemDictionary as CharArrayDictionary<string>;
                 }
                 return null;
             }
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/No/NorwegianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/No/NorwegianAnalyzer.cs
index 499457e03..7e7c264b5 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/No/NorwegianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/No/NorwegianAnalyzer.cs
@@ -86,7 +86,7 @@ namespace Lucene.Net.Analysis.No
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public NorwegianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-              : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+              : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Pt/PortugueseAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Pt/PortugueseAnalyzer.cs
index 307e3be48..2eb343920 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Pt/PortugueseAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Pt/PortugueseAnalyzer.cs
@@ -91,7 +91,7 @@ namespace Lucene.Net.Analysis.Pt
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public PortugueseAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ro/RomanianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ro/RomanianAnalyzer.cs
index 929ae73bf..6d78f9fb5 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Ro/RomanianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Ro/RomanianAnalyzer.cs
@@ -87,7 +87,7 @@ namespace Lucene.Net.Analysis.Ro
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public RomanianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Ru/RussianAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Ru/RussianAnalyzer.cs
index d7fd7575c..5af5f5a13 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Ru/RussianAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Ru/RussianAnalyzer.cs
@@ -116,7 +116,7 @@ namespace Lucene.Net.Analysis.Ru
         /// <param name="stopwords">
         ///          a stopword set </param>
         public RussianAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Sv/SwedishAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Sv/SwedishAnalyzer.cs
index 6c12d5e94..a7254d96b 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Sv/SwedishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Sv/SwedishAnalyzer.cs
@@ -87,7 +87,7 @@ namespace Lucene.Net.Analysis.Sv
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public SwedishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymFilter.cs b/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymFilter.cs
index 94ebfeee9..79ab12d43 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymFilter.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymFilter.cs
@@ -95,8 +95,7 @@ namespace Lucene.Net.Analysis.Synonym
                     return false;
                 }
                 var termAtt = firstTok.AddAttribute<ICharTermAttribute>();
-                SlowSynonymMap result = map.Submap != null ? map.Submap.Get(termAtt.Buffer, 0, termAtt.Length) : null;
-                if (result is null)
+                if (map.Submap is null || !map.Submap.TryGetValue(termAtt.Buffer, 0, termAtt.Length, out SlowSynonymMap result) || result is null)
                 {
                     Copy(this, firstTok);
                     return true;
@@ -274,9 +273,8 @@ namespace Lucene.Net.Analysis.Synonym
                     }
                     // check for positionIncrement!=1?  if>1, should not match, if==0, check multiple at this level?
                     var termAtt = tok.GetAttribute<ICharTermAttribute>();
-                    SlowSynonymMap subMap = map.Submap.Get(termAtt.Buffer, 0, termAtt.Length);
 
-                    if (subMap != null)
+                    if (map.Submap.TryGetValue(termAtt.Buffer, 0, termAtt.Length, out SlowSynonymMap subMap) && subMap != null)
                     {
                         // recurse
                         result = Match(subMap);
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymMap.cs b/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymMap.cs
index 875385bf0..8e774ada4 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymMap.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymMap.cs
@@ -37,12 +37,12 @@ namespace Lucene.Net.Analysis.Synonym
     {
         /// <summary>
         /// @lucene.internal </summary>
-        public CharArrayMap<SlowSynonymMap> Submap // recursive: Map<String, SynonymMap>
+        public CharArrayDictionary<SlowSynonymMap> Submap // recursive: Map<String, SynonymMap>
         {
             get => submap;
             set => submap = value;
         }
-        private CharArrayMap<SlowSynonymMap> submap;
+        private CharArrayDictionary<SlowSynonymMap> submap;
 
         /// <summary>
         /// @lucene.internal </summary>
@@ -88,15 +88,14 @@ namespace Lucene.Net.Analysis.Synonym
                 {
                     // for now hardcode at 4.0, as its what the old code did.
                     // would be nice to fix, but shouldn't store a version in each submap!!!
-                    currMap.submap = new CharArrayMap<SlowSynonymMap>(LuceneVersion.LUCENE_CURRENT, 1, IgnoreCase);
+                    currMap.submap = new CharArrayDictionary<SlowSynonymMap>(LuceneVersion.LUCENE_CURRENT, 1, IgnoreCase);
                 }
 
-                var map = currMap.submap.Get(str);
-                if (map is null)
+                if (!currMap.submap.TryGetValue(str, out SlowSynonymMap map) || map is null)
                 {
                     map = new SlowSynonymMap();
                     map.flags |= flags & IGNORE_CASE;
-                    currMap.submap.Put(str, map);
+                    currMap.submap[str] = map;
                 }
 
                 currMap = map;
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Tr/TurkishAnalyzer.cs b/src/Lucene.Net.Analysis.Common/Analysis/Tr/TurkishAnalyzer.cs
index 6e6c48c30..9a56850bf 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Tr/TurkishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Tr/TurkishAnalyzer.cs
@@ -86,7 +86,7 @@ namespace Lucene.Net.Analysis.Tr
         /// <param name="matchVersion"> lucene compatibility version </param>
         /// <param name="stopwords"> a stopword set </param>
         public TurkishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArrayMap.cs b/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArrayMap.cs
index db0e30c11..050da6771 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArrayMap.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArrayMap.cs
@@ -3,13 +3,19 @@ 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
 
 namespace Lucene.Net.Analysis.Util
 {
@@ -31,17 +37,17 @@ namespace Lucene.Net.Analysis.Util
      */
 
     /// <summary>
-    /// A simple class that stores key <see cref="string"/>s as <see cref="T:char[]"/>'s in a
+    /// 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
-    /// map, nor does it resize its hash table to be smaller,
+    /// 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="CharArrayMap"/>:
+    /// compatibility when creating <see cref="CharArrayDictionary{TValue}"/>:
     /// <list type="bullet">
     ///   <item><description> As of 3.1, supplementary characters are
     ///       properly lowercased.</description></item>
@@ -49,14 +55,17 @@ namespace Lucene.Net.Analysis.Util
     /// 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="CharArrayMap"/> with the behavior before Lucene
+    /// <see cref="CharArrayDictionary{TValue}"/> with the behavior before Lucene
     /// 3.1 pass a <see cref="LuceneVersion"/> &lt; 3.1 to the constructors.
     /// </para>
     /// </summary>
-    public class CharArrayMap<TValue> : ICharArrayMap, IDictionary<string, TValue>
+    [DebuggerDisplay("Count = {Count}, Values = {ToString()}")]
+    public class CharArrayDictionary<TValue> : ICharArrayDictionary, IDictionary<string, TValue>, IDictionary, IReadOnlyDictionary<string, TValue>
     {
-        // private only because missing generics
-        private static readonly CharArrayMap<TValue> EMPTY_MAP = new CharArrayMap.EmptyCharArrayMap<TValue>();
+        // LUCENENET: Made public, renamed Empty
+        /// <summary>
+        /// Returns an empty, read-only dictionary. </summary>
+        public static readonly CharArrayDictionary<TValue> Empty = new CharArrayDictionary.EmptyCharArrayDictionary<TValue>();
 
         private const int INIT_SIZE = 8;
         private readonly CharacterUtils charUtils;
@@ -66,11 +75,15 @@ namespace Lucene.Net.Analysis.Util
         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 CharArrayMap
+        /// 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
@@ -87,37 +100,45 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         internal class MapValue
         {
+            [AllowNull]
             private TValue value = default;
+
+            [AllowNull]
             public TValue Value
             {
-                get => 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(TValue value)
+            public MapValue([AllowNull] TValue value)
             {
                 this.value = value;
             }
         }
 
         /// <summary>
-        /// Create map with enough capacity to hold <paramref name="startSize"/> terms
+        /// Create dictionary with enough capacity to hold <paramref name="capacity"/> terms.
         /// </summary>
         /// <param name="matchVersion">
-        ///          lucene compatibility version - see <see cref="CharArrayMap{TValue}"/> for details. </param>
-        /// <param name="startSize">
+        ///          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>
-        public CharArrayMap(LuceneVersion matchVersion, int startSize, bool ignoreCase)
+        /// <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 (startSize + (startSize >> 2) > size)
+            while (capacity + (capacity >> 2) > size)
             {
                 size <<= 1;
             }
@@ -128,29 +149,96 @@ namespace Lucene.Net.Analysis.Util
         }
 
         /// <summary>
-        /// Creates a map from the mappings in another map. 
+        /// Creates a dictionary from the mappings in another dictionary.
         /// </summary>
         /// <param name="matchVersion">
-        ///          compatibility match version see <a href="#version">Version
-        ///          note</a> above for details. </param>
-        /// <param name="c">
-        ///          a map (<see cref="T:IDictionary{string, V}"/>) whose mappings to be copied </param>
+        ///          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>
-        public CharArrayMap(LuceneVersion matchVersion, IDictionary<string, TValue> c, bool ignoreCase)
-            : this(matchVersion, c.Count, ignoreCase)
+        /// <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)
         {
-            foreach (var v in c)
+            // LUCENENET: Added guard clause
+            if (collection is null)
+                throw new ArgumentNullException(nameof(collection));
+
+            foreach (var v in collection)
             {
-                Add(v);
+                // 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 map (used internally for readonly maps...)
+        /// Create set from the supplied dictionary (used internally for readonly maps...)
         /// </summary>
-        internal CharArrayMap(CharArrayMap<TValue> toCopy)
+        internal CharArrayDictionary(CharArrayDictionary<TValue> toCopy)
         {
             this.keys = toCopy.keys;
             this.values = toCopy.values;
@@ -166,97 +254,205 @@ namespace Lucene.Net.Analysis.Util
         /// </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>
-        public virtual void Add(KeyValuePair<string, TValue> item)
+        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="key"/>.
+        /// 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="key">The string-able type to be added/updated in the dictionary.</param>
-        /// <param name="value">The corresponding value for the given <paramref name="key"/>.</param>
-        public virtual void Add(string key, TValue value)
+        /// <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(key))
+            if (ContainsKey(text))
             {
-                throw new ArgumentException("The key " + key + " already exists in the dictionary");
+                throw new ArgumentException(string.Format(SR.Argument_AddingDuplicate, text), nameof(text));
             }
-            Put(key, value);
+            Set(text, value);
         }
 
         /// <summary>
-        /// Returns an unmodifiable <see cref="CharArrayMap{TValue}"/>. This allows to provide
-        /// unmodifiable views of internal map for "read-only" use.
+        /// 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="CharArrayMap{TValue}"/>. </returns>
+        /// <returns> an new unmodifiable <see cref="CharArrayDictionary{TValue}"/>. </returns>
         // LUCENENET specific - allow .NET-like syntax for creating immutable collections
-        public CharArrayMap<TValue> AsReadOnly()
+        public CharArrayDictionary<TValue> AsReadOnly()
         {
-            return this is CharArrayMap.UnmodifiableCharArrayMap<TValue> readOnlyDictionary ?
+            return this is CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue> readOnlyDictionary ?
                 readOnlyDictionary :
-                new CharArrayMap.UnmodifiableCharArrayMap<TValue>(this);
+                new CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>(this);
         }
 
         /// <summary>
-        /// Clears all entries in this map. This method is supported for reusing, but not 
+        /// 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;
             keys.Fill(null);
             values.Fill(null);
         }
 
         /// <summary>
-        /// Not supported. 
+        /// Not supported.
         /// </summary>
-        [Obsolete("Not applicable in this class.")]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public virtual bool Contains(KeyValuePair<string, TValue> item) // LUCENENET TODO: API - rather than marking this DesignerSerializationVisibility.Hidden, it would be better to make an explicit implementation that isn't public
+        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"/>.
+        /// 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>
+        public virtual void CopyTo(KeyValuePair<string, TValue>[] array, int index)
+        {
+            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<string, TValue>(iter.CurrentKeyString, 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="arrayIndex">A 32-bit integer that represents the index in <paramref name="array"/> at which copying begins.</param>
-        public virtual void CopyTo(KeyValuePair<string, TValue>[] array, int arrayIndex)
+        /// <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>
+        public virtual void CopyTo(KeyValuePair<char[], TValue>[] array, int index)
         {
-            using var iter = (EntryIterator)EntrySet().GetEnumerator();
-            for (int i = arrayIndex; iter.MoveNext(); i++)
+            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<string, TValue>(iter.Current.Key, iter.CurrentValue);
+                array[i] = new KeyValuePair<char[], TValue>((char[])iter.CurrentKey.Clone(), iter.CurrentValue!);
             }
         }
 
         /// <summary>
-        /// Copies all items in the current <see cref="CharArrayMap{TValue}"/> to the passed in
-        /// <see cref="CharArrayMap{TValue}"/>.
+        /// 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="map"></param>
-        public virtual void CopyTo(CharArrayMap<TValue> map)
+        /// <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>
+        public virtual void CopyTo(KeyValuePair<ICharSequence, TValue>[] array, int index)
         {
-            using var iter = (EntryIterator)EntrySet().GetEnumerator();
-            while (iter.MoveNext())
+            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++)
             {
-                map.Put(iter.Current.Key, iter.CurrentValue);
+                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="offset"/>
+        /// <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>
-        public virtual bool ContainsKey(char[] text, int offset, int length)
+        /// <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, offset, length)] != null;
+            return keys[GetSlot(text, startIndex, length)] != null;
         }
 
         /// <summary>
@@ -264,8 +460,12 @@ namespace Lucene.Net.Analysis.Util
         /// <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;
         }
 
@@ -273,6 +473,7 @@ namespace Lucene.Net.Analysis.Util
         /// <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;
@@ -282,91 +483,180 @@ namespace Lucene.Net.Analysis.Util
         /// <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="o"/> <see cref="object.ToString()"/> is in the <see cref="Keys"/>; 
-        /// otherwise <c>false</c>
+        /// <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>
-        public virtual bool ContainsKey(object o)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        public virtual bool ContainsKey<T>(T text)
         {
-            if (o is null)
-            {
-                throw new ArgumentNullException(nameof(o), "o can't be null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
-            }
-
-            var c = o as char[];
-            if (c != null)
-            {
-                var text = c;
-                return ContainsKey(text, 0, text.Length);
-            }
-            return ContainsKey(o.ToString());
+            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="offset"/>
+        /// Returns the value of the mapping of <paramref name="length"/> chars of <paramref name="text"/>
+        /// starting at <paramref name="startIndex"/>.
         /// </summary>
-        public virtual TValue Get(char[] text, int offset, int length)
+        /// <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)
         {
-            var value = values[GetSlot(text, offset, length)];
-            return (value != null) ? value.Value : default;
+            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"/>
+        /// Returns the value of the mapping of the chars inside this <paramref name="text"/>.
         /// </summary>
-        public virtual TValue Get(char[] text)
+        /// <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)
         {
-            var value = values[GetSlot(text, 0, text.Length)];
-            return (value != null) ? value.Value : default;
+            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"/>
+        /// Returns the value of the mapping of the chars inside this <see cref="ICharSequence"/>.
         /// </summary>
-        public virtual TValue Get(ICharSequence text)
+        /// <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)];
-            return (value != null) ? value.Value : default;
+            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"/>
+        /// Returns the value of the mapping of the chars inside this <see cref="string"/>.
         /// </summary>
-        public virtual TValue Get(string text)
+        /// <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)];
-            return (value != null) ? value.Value : default;
+            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()"/>
+        /// Returns the value of the mapping of the chars inside this <see cref="object.ToString()"/>.
         /// </summary>
-        public virtual TValue Get(object o)
+        /// <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 (o is char[])
-            {
-                var text = o as char[];
-                return Get(text, 0, text.Length);
-            }
-            return Get(o.ToString());
+            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);
         }
 
-        private int GetSlot(char[] text, int offset, int length)
+        #endregion Get
+
+        #region GetSlot
+
+        private int GetSlot(char[] text, int startIndex, int length)
         {
-            int code = GetHashCode(text, offset, length);
+            int code = GetHashCode(text, startIndex, length);
             int pos = code & (keys.Length - 1);
             char[] text2 = keys[pos];
-            if (text2 != null && !Equals(text, offset, length, text2))
+            if (text2 != null && !Equals(text, startIndex, length, text2))
             {
                 int inc = ((code >> 8) + code) | 1;
                 do
@@ -374,14 +664,21 @@ namespace Lucene.Net.Analysis.Util
                     code += inc;
                     pos = code & (keys.Length - 1);
                     text2 = keys[pos];
-                } while (text2 != null && !Equals(text, offset, length, text2));
+                } while (text2 != null && !Equals(text, startIndex, length, text2));
             }
             return pos;
         }
 
         /// <summary>
-        /// Returns true if the <see cref="ICharSequence"/> is in the set
+        /// 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);
@@ -401,8 +698,9 @@ namespace Lucene.Net.Analysis.Util
         }
 
         /// <summary>
-        /// Returns true if the <see cref="string"/> is in the set
+        /// 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);
@@ -421,84 +719,289 @@ namespace Lucene.Net.Analysis.Util
             return pos;
         }
 
+        #endregion GetSlot
+
+        #region Put (value)
+
         /// <summary>
         /// Add the given mapping.
-        /// </summary>
-        public virtual TValue Put(ICharSequence text, TValue value)
-        {
-            MapValue oldValue = PutImpl(text, new MapValue(value)); // could be more efficient
-            return (oldValue != null) ? oldValue.Value : default;
+        /// 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 using the <see cref="object.ToString()"/> representation
-        /// of <paramref name="o"/> in the <see cref="CultureInfo.InvariantCulture"/>.
-        /// </summary>
-        public virtual TValue Put(object o, TValue value)
-        {
-            MapValue oldValue = PutImpl(o, new MapValue(value));
-            return (oldValue != null) ? oldValue.Value : default;
+        /// 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.
-        /// </summary>
-        public virtual TValue Put(string text, TValue value)
-        {
-            MapValue oldValue = PutImpl(text, new MapValue(value));
-            return (oldValue != null) ? oldValue.Value : default;
+        /// <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.
-        /// If ignoreCase is true for this Set, the text array will be directly modified.
-        /// The user should never modify this text array after calling this method.
-        /// </summary>
-        public virtual TValue Put(char[] text, TValue value)
-        {
-            MapValue oldValue = PutImpl(text, new MapValue(value));
-            return (oldValue != null) ? oldValue.Value : default;
+        /// <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>
-        private MapValue PutImpl(ICharSequence text, MapValue value)
+        /// <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)
         {
-            return PutImpl(text.ToString(), value); // could be more efficient
+            // LUCENENET: Added guard clause
+            if (text is null || !text.HasValue)
+                throw new ArgumentNullException(nameof(text));
+
+            if (text is CharArrayCharSequence charArrayCs)
+                return PutImpl(charArrayCs.Value ?? Arrays.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);
         }
 
-        private MapValue PutImpl(object o, MapValue 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 (o is char[])
+            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)
             {
-                var c = o as char[];
-                return PutImpl(c, value);
+                MapValue oldValue = values[slot];
+                values[slot] = value;
+                return oldValue;
             }
-            // LUCENENET: We need value types to be represented using the invariant
-            // culture, so it is consistent regardless of the current culture. 
-            // It's easy to work out if this is a value type, but difficult
-            // to get to the ToString(IFormatProvider) overload of the type without
-            // a lot of special cases. It's easier just to change the culture of the 
-            // thread before calling ToString(), but we don't want that behavior to
-            // bleed into PutImpl.
-            string s;
-            using (var context = new CultureContext(CultureInfo.InvariantCulture))
+            keys[slot] = text.ToCharArray();
+            values[slot] = value;
+            count++;
+
+            if (count + (count >> 2) > keys.Length)
             {
-                s = o.ToString();
+                Rehash();
             }
-            return PutImpl(s, value);
+
+            return null;
         }
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        private MapValue PutImpl(string text, MapValue value)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private MapValue? PutImpl(char[] text, int startIndex, int length, MapValue value)
         {
-            return PutImpl(text.ToCharArray(), 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>
@@ -507,8 +1010,16 @@ namespace Lucene.Net.Analysis.Util
         /// 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>
-        private MapValue PutImpl(char[] text, MapValue value)
+        /// <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);
@@ -532,114 +1043,579 @@ namespace Lucene.Net.Analysis.Util
             return null;
         }
 
-        #region PutAll
+        #endregion PutImpl
+
+        #region Set
 
         /// <summary>
-        /// This implementation enumerates over the specified <see cref="T:IDictionary{char[],TValue}"/>'s
-        /// entries, and calls this map's <see cref="Put(char[], TValue)"/> operation once for each entry.
+        /// Sets the value of the mapping of the chars inside this <paramref name="text"/>.
         /// </summary>
-        /// <param name="collection">A dictionary of values to add/update in the current map.</param>
-        public virtual void PutAll(IDictionary<char[], TValue> collection)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal virtual void Set(char[] text, int startIndex, int length)
         {
-            foreach (var kvp in collection)
-            {
-                Put(kvp.Key, kvp.Value);
-            }
+            if (text is null)
+                throw new ArgumentNullException(nameof(text));
+
+            SetImpl(text, startIndex, length, PLACEHOLDER);
         }
 
         /// <summary>
-        /// This implementation enumerates over the specified <see cref="T:IDictionary{string,TValue}"/>'s
-        /// entries, and calls this map's <see cref="Put(string, TValue)"/> operation once for each entry.
+        /// Sets the value of the mapping of the chars inside this <paramref name="text"/>.
         /// </summary>
-        /// <param name="collection">A dictionary of values to add/update in the current map.</param>
-        public virtual void PutAll(IDictionary<string, TValue> collection)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal virtual void Set(char[] text)
         {
-            foreach (var kvp in collection)
-            {
-                Put(kvp.Key, kvp.Value);
-            }
+            if (text is null)
+                throw new ArgumentNullException(nameof(text));
+
+            SetImpl(text, PLACEHOLDER);
         }
 
         /// <summary>
-        /// This implementation enumerates over the specified <see cref="T:IDictionary{ICharSequence,TValue}"/>'s
-        /// entries, and calls this map's <see cref="Put(ICharSequence, TValue)"/> operation once for each entry.
+        /// Sets the value of the mapping of the chars inside this <see cref="ICharSequence"/>.
         /// </summary>
-        /// <param name="collection">A dictionary of values to add/update in the current map.</param>
-        public virtual void PutAll(IDictionary<ICharSequence, TValue> collection)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal virtual void Set(ICharSequence text)
         {
-            foreach (var kvp in collection)
-            {
-                Put(kvp.Key, kvp.Value);
-            }
+            if (text is null || !text.HasValue)
+                throw new ArgumentNullException(nameof(text));
+
+            SetImpl(text, PLACEHOLDER);
         }
 
         /// <summary>
-        /// This implementation enumerates over the specified <see cref="T:IDictionary{object,TValue}"/>'s
-        /// entries, and calls this map's <see cref="Put(object, TValue)"/> operation once for each entry.
+        /// Sets the value of the mapping of the chars inside this <see cref="string"/>.
         /// </summary>
-        /// <param name="collection">A dictionary of values to add/update in the current map.</param>
-        public virtual void PutAll(IDictionary<object, TValue> collection)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal virtual void Set(string text)
         {
-            foreach (var kvp in collection)
-            {
-                Put(kvp.Key, kvp.Value);
-            }
+            if (text is null)
+                throw new ArgumentNullException(nameof(text));
+
+            SetImpl(text, PLACEHOLDER);
         }
 
         /// <summary>
-        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{char[],TValue}}"/>'s
-        /// entries, and calls this map's <see cref="Put(char[], TValue)"/> operation once for each entry.
+        /// Sets the value of the mapping of the chars inside this <see cref="object.ToString()"/>.
         /// </summary>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public virtual void PutAll(IEnumerable<KeyValuePair<char[], TValue>> collection)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        internal virtual void Set<T>(T text)
         {
-            foreach (var kvp in collection)
+            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)
             {
-                Put(kvp.Key, kvp.Value);
+                Set(str);
+                return;
+            }
+            if (text is char[] charArray)
+            {
+                Set(charArray);
+                return;
+            }
+            if (text is ICharSequence cs)
+            {
+                Set(cs);
+                return;
             }
-        }
 
-        /// <summary>
-        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{string,TValue}}"/>'s
-        /// entries, and calls this map's <see cref="Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public virtual void PutAll(IEnumerable<KeyValuePair<string, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
+            if (returnType == CharArrayDictionary.CharReturnType.String)
             {
-                Put(kvp.Key, kvp.Value);
+                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>
-        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{ICharSequence,TValue}}"/>'s
-        /// entries, and calls this map's <see cref="Put(ICharSequence, TValue)"/> operation once for each entry.
+        /// 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="collection">The values to add/update in the current map.</param>
+        /// <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 ?? Arrays.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)
             {
-                Put(kvp.Key, kvp.Value);
+                Set(kvp.Key, kvp.Value);
             }
         }
 
         /// <summary>
-        /// This implementation enumerates over the specified <see cref="T:IEnumerable{KeyValuePair{object,TValue}}"/>'s
-        /// entries, and calls this map's <see cref="Put(object, TValue)"/> operation once for each entry.
+        /// 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 map.</param>
-        public virtual void PutAll(IEnumerable<KeyValuePair<object, TValue>> collection)
+        /// <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)
             {
-                Put(kvp.Key, kvp.Value);
+                // 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);
@@ -662,18 +1638,19 @@ namespace Lucene.Net.Analysis.Util
             }
         }
 
-        private bool Equals(char[] text1, int offset, int length, char[] text2)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private bool Equals(char[] text1, int startIndex, int length, char[] text2)
         {
             if (length != text2.Length)
             {
                 return false;
             }
-            int limit = offset + length;
+            int limit = startIndex + length;
             if (ignoreCase)
             {
                 for (int i = 0; i < length;)
                 {
-                    var codePointAt = charUtils.CodePointAt(text1, offset + i, limit);
+                    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;
@@ -685,7 +1662,7 @@ namespace Lucene.Net.Analysis.Util
             {
                 for (int i = 0; i < length; i++)
                 {
-                    if (text1[offset + i] != text2[i])
+                    if (text1[startIndex + i] != text2[i])
                     {
                         return false;
                     }
@@ -694,6 +1671,7 @@ namespace Lucene.Net.Analysis.Util
             return true;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private bool Equals(ICharSequence text1, char[] text2)
         {
             int length = text1.Length;
@@ -726,6 +1704,7 @@ namespace Lucene.Net.Analysis.Util
             return true;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private bool Equals(string text1, char[] text2)
         {
             int length = text1.Length;
@@ -763,24 +1742,37 @@ namespace Lucene.Net.Analysis.Util
         /// </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 key value pairs as the current map</returns>
-        public override bool Equals(object obj)
+        /// the same text value pairs as the current dictionary</returns>
+        public override bool Equals(object? obj)
         {
-            var other = obj as IDictionary<string, TValue>;
-            if (other is null)
+            if (obj is null)
+                return false;
+            if (obj is not IDictionary<string, TValue> other)
                 return false;
-
             if (this.Count != other.Count)
                 return false;
 
-            using (var iter = other.GetEnumerator())
+            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))
+                    if (!this.TryGetValue(iter.Current.Key, out TValue? value))
                         return false;
 
-                    if (!EqualityComparer<TValue>.Default.Equals(value, iter.Current.Value))
+                    if (!JCG.EqualityComparer<TValue>.Default.Equals(value!, iter.Current.Value))
                         return false;
                 }
             }
@@ -797,28 +1789,35 @@ namespace Lucene.Net.Analysis.Util
         {
             const int PRIME = 31; // arbitrary prime
             int hash = PRIME;
-            using (var iter = (EntryIterator)EntrySet().GetEnumerator())
+            using (var iter = GetEnumerator())
             {
                 while (iter.MoveNext())
                 {
-                    hash = (hash * PRIME) ^ iter.Current.Key.GetHashCode();
-                    hash = (hash * PRIME) ^ iter.Current.Value.GetHashCode();
+                    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;
         }
 
-        private int GetHashCode(char[] text, int offset, int length)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private int GetHashCode(char[] text, int startIndex, int length)
         {
             if (text is null)
-            {
-                throw new ArgumentNullException(nameof(text), "text can't be null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
-            }
+                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 = offset + length;
+            int stop = startIndex + length;
             if (ignoreCase)
             {
-                for (int i = offset; i < stop;)
+                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
@@ -827,7 +1826,7 @@ namespace Lucene.Net.Analysis.Util
             }
             else
             {
-                for (int i = offset; i < stop; i++)
+                for (int i = startIndex; i < stop; i++)
                 {
                     code = code * 31 + text[i];
                 }
@@ -835,12 +1834,11 @@ namespace Lucene.Net.Analysis.Util
             return code;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private int GetHashCode(ICharSequence text)
         {
-            if (text is null)
-            {
-                throw new ArgumentNullException(nameof(text), "text can't be null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
-            }
+            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;
@@ -863,12 +1861,11 @@ namespace Lucene.Net.Analysis.Util
             return code;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private int GetHashCode(string text)
         {
             if (text is null)
-            {
-                throw new ArgumentNullException(nameof(text), "text can't be null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
-            }
+                throw new ArgumentNullException(nameof(text)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
 
             int code = 0;
             int length = text.Length;
@@ -900,54 +1897,137 @@ namespace Lucene.Net.Analysis.Util
         public virtual LuceneVersion MatchVersion => matchVersion;
 
         /// <summary>
-        /// Adds a placeholder with the given <paramref name="text"/> as the key.
+        /// 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>
-        public virtual bool Put(char[] text)
+        /// <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 key.
+        /// Adds a placeholder with the given <paramref name="text"/> as the text.
         /// Primarily for internal use by <see cref="CharArraySet"/>.
         /// </summary>
-        public virtual bool Put(ICharSequence text)
+        /// <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 key.
+        /// Adds a placeholder with the given <paramref name="text"/> as the text.
         /// Primarily for internal use by <see cref="CharArraySet"/>.
         /// </summary>
-        public virtual bool Put(string text)
+        /// <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="o"/> as the key.
+        /// Adds a placeholder with the given <paramref name="text"/> as the text.
         /// Primarily for internal use by <see cref="CharArraySet"/>.
         /// </summary>
-        public virtual bool Put(object o)
+        /// <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 PutImpl(o, PLACEHOLDER) is null;
+            return new CharArrayDictionary<TValue>(matchVersion, this, ignoreCase);
         }
 
         /// <summary>
-        /// Gets the value associated with the specified key.
+        /// Gets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="offset">The position of the <paramref name="key"/> where the target key begins.</param>
-        /// <param name="length">The total length of the <paramref name="key"/>.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key is found; otherwise, the default value for the type of the value parameter. 
+        /// <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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public virtual bool TryGetValue(char[] key, int offset, int length, out TValue value)
+        /// <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(key, offset, length)];
+            var val = values[GetSlot(text, startIndex, length)];
             if (val != null)
             {
                 value = val.Value;
@@ -958,16 +2038,20 @@ namespace Lucene.Net.Analysis.Util
         }
 
         /// <summary>
-        /// Gets the value associated with the specified key.
+        /// Gets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key is found; otherwise, the default value for the type of the value parameter. 
+        /// <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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public virtual bool TryGetValue(char[] key, out TValue value)
+        /// <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)
         {
-            var val = values[GetSlot(key, 0, key.Length)];
+            if (text is null)
+                throw new ArgumentNullException(nameof(text));
+
+            var val = values[GetSlot(text, 0, text.Length)];
             if (val != null)
             {
                 value = val.Value;
@@ -978,16 +2062,33 @@ namespace Lucene.Net.Analysis.Util
         }
 
         /// <summary>
-        /// Gets the value associated with the specified key.
+        /// Gets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key is found; otherwise, the default value for the type of the value parameter. 
+        /// <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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public virtual bool TryGetValue(ICharSequence key, out TValue value)
-        {
-            var val = values[GetSlot(key)];
+        /// <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;
@@ -998,113 +2099,146 @@ namespace Lucene.Net.Analysis.Util
         }
 
         /// <summary>
-        /// Gets the value associated with the specified key.
+        /// Gets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key is found; otherwise, the default value for the type of the value parameter. 
+        /// <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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public virtual bool TryGetValue(string key, out TValue value)
+        /// <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(key)];
+            var val = values[GetSlot(text)];
             if (val != null)
             {
-                value = val.Value;
+                value = val.Value!;
                 return true;
             }
-            value = default;
+            value = default!;
             return false;
         }
 
         /// <summary>
-        /// Gets the value associated with the specified key.
+        /// Gets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key is found; otherwise, the default value for the type of the value parameter. 
+        /// <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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public virtual bool TryGetValue(object key, out TValue value)
+        /// <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 (key is char[])
-            {
-                var text = key as char[];
-                return TryGetValue(text, 0, text.Length, out value);
-            }
-            return TryGetValue(key.ToString(), out value);
+            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 key.
+        /// 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="key">The key of the value to get or set.</param>
-        /// <param name="offset">The position of the <paramref name="key"/> where the target key begins.</param>
-        /// <param name="length">The total length of the <paramref name="key"/>.</param>
-        public virtual TValue this[char[] key, int offset, int length]
+        /// <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(key, offset, length);
-            set => Put(key, value);
+            get => Get(text, startIndex, length, throwIfNotFound: true);
+            set => Set(text, startIndex, length, value);
         }
 
         /// <summary>
-        /// Gets or sets the value associated with the specified key.
+        /// 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="key">The key of the value to get or set.</param>
-        public virtual TValue this[char[] key]
+        /// <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(key);
-            set => Put(key, value);
+            get => Get(text, throwIfNotFound: true);
+            set => Set(text, value);
         }
 
         /// <summary>
-        /// Gets or sets the value associated with the specified key.
+        /// Gets or sets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get or set.</param>
-        public virtual TValue this[ICharSequence key]
+        /// <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(key);
-            set => Put(key, value);
+            get => Get(text, throwIfNotFound: true);
+            set => Set(text, value);
         }
 
         /// <summary>
-        /// Gets or sets the value associated with the specified key.
+        /// Gets or sets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get or set.</param>
-        public virtual TValue this[string key]
+        /// <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(key);
-            set => Put(key, value);
+            get => Get(text, throwIfNotFound: true);
+            set => Set(text, value);
         }
 
         /// <summary>
-        /// Gets or sets the value associated with the specified key.
+        /// Gets or sets the value associated with the specified text.
         /// </summary>
-        /// <param name="key">The key of the value to get or set.</param>
-        public virtual TValue this[object key]
+        /// <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(key);
-            set => Put(key, value);
+            get => Get(text, throwIfNotFound: true);
+            set => Set(text, value);
         }
 
         /// <summary>
-        /// Gets a collection containing the keys in the <see cref="CharArrayMap{TValue}"/>.
+        /// Gets a collection containing the keys in the <see cref="CharArrayDictionary{TValue}"/>.
         /// </summary>
-        public virtual ICollection<string> Keys => KeySet;
+        public virtual CharArraySet Keys => KeySet;
+
+        ICollection<string> IDictionary<string, TValue>.Keys => KeySet;
+
+        IEnumerable<string> IReadOnlyDictionary<string, TValue>.Keys => KeySet;
 
 
-        private volatile ICollection<TValue> valueSet;
+        private volatile ValueCollection? valueSet;
 
         /// <summary>
-        /// Gets a collection containing the values in the <see cref="CharArrayMap{TValue}"/>.
+        /// 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 ICollection<TValue> Values
+        public ValueCollection Values
         {
             get
             {
@@ -1116,159 +2250,172 @@ namespace Lucene.Net.Analysis.Util
             }
         }
 
-        /// <summary>
-        /// LUCENENET specific class used to break the infinite recursion when the
-        /// CharArraySet iterates the keys of this dictionary via <see cref="OriginalKeySet"/>. 
-        /// In Java, the keyset of the abstract base class was used to break the infinite recursion, 
-        /// however this class doesn't have an abstract base class so that is not an option. 
-        /// This class is just a facade around the keys (not another collection of keys), so it 
-        /// doesn't consume any additional RAM while providing another "virtual" collection to iterate over.
-        /// </summary>
-        internal class KeyCollection : ICollection<string>
-        {
-            private readonly CharArrayMap<TValue> outerInstance;
+        ICollection<TValue> IDictionary<string, TValue>.Values => Values;
 
-            public KeyCollection(CharArrayMap<TValue> outerInstance)
-            {
-                this.outerInstance = outerInstance;
-            }
+        IEnumerable<TValue> IReadOnlyDictionary<string, TValue>.Values => Values;
 
-            public int Count => outerInstance.Count;
+        #region Nested Class: ValueCollection
 
-            public bool IsReadOnly => outerInstance.IsReadOnly;
+        /// <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;
 
-            public void Add(string item) // LUCENENET TODO: API - make an explicit implementation that isn't public
+            /// <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)
             {
-                throw UnsupportedOperationException.Create();
+                this.dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
             }
 
-            public void Clear()
-            {
-                outerInstance.Clear();
-            }
+            /// <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;
 
-            public bool Contains(string item)
-            {
-                return outerInstance.ContainsKey(item);
-            }
+            bool ICollection<TValue>.IsReadOnly => true;
 
-            public void CopyTo(string[] array, int arrayIndex)
-            {
-                using var iter = GetEnumerator();
-                for (int i = arrayIndex; iter.MoveNext(); i++)
-                {
-                    array[i] = iter.Current;
-                }
-            }
+            bool ICollection.IsSynchronized => false;
 
-            public IEnumerator<string> GetEnumerator()
-            {
-                return new KeyEnumerator(outerInstance);
-            }
+            object ICollection.SyncRoot => ((ICollection)dictionary).SyncRoot;
 
-            public bool Remove(string item) // LUCENENET TODO: API - make an explicit implementation that isn't public
+            void ICollection<TValue>.Add(TValue item)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ValueCollectionSet);
             }
 
-            IEnumerator IEnumerable.GetEnumerator()
+            void ICollection<TValue>.Clear()
             {
-                return GetEnumerator();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ValueCollectionSet);
             }
 
             /// <summary>
-            /// LUCENENET specific class to iterate the values in the <see cref="KeyCollection"/>.
+            /// Determines whether the set contains a specific element.
             /// </summary>
-            private class KeyEnumerator : IEnumerator<string>
+            /// <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)
             {
-                private readonly EntryIterator entryIterator;
-
-                public KeyEnumerator(CharArrayMap<TValue> outerInstance)
-                {
-                    this.entryIterator = new EntryIterator(outerInstance, !outerInstance.IsReadOnly);
-                }
-
-                public string Current => entryIterator.Current.Key;
-
-                object IEnumerator.Current => Current;
-
-                public void Dispose()
-                {
-                    // nothing to do
-                }
-
-                public bool MoveNext()
+                for (int i = 0; i < dictionary.values.Length; i++)
                 {
-                    return entryIterator.MoveNext();
+                    var value = dictionary.values[i];
+                    if (JCG.EqualityComparer<TValue>.Equals(value, item))
+                        return true;
                 }
+                return false;
+            }
 
-                public void Reset()
+            /// <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="index">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="index"/> 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="index"/> 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 index)
+            {
+                if (array is null)
+                    throw new ArgumentNullException(nameof(array));
+                if (index < 0)
+                    throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
+                if (index > array.Length || dictionary.Count > array.Length - index)
+                    throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+
+                foreach (var entry in this)
+                    array[index++] = 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)
                 {
-                    entryIterator.Reset();
+                    CopyTo(values, index);
                 }
-            }
-        }
-
-        /// <summary>
-        /// LUCENENET specific class that represents the values in the <see cref="CharArrayMap{TValue}"/>.
-        /// </summary>
-        internal class ValueCollection : ICollection<TValue>
-        {
-            private readonly CharArrayMap<TValue> outerInstance;
-
-            public ValueCollection(CharArrayMap<TValue> outerInstance)
-            {
-                this.outerInstance = outerInstance;
-            }
-
-            public int Count => outerInstance.Count;
-
-            public bool IsReadOnly => outerInstance.IsReadOnly;
-
-            public void Add(TValue item) // LUCENENET TODO: API - make an explicit implementation that isn't public
-            {
-                throw UnsupportedOperationException.Create();
-            }
-
-            public void Clear()
-            {
-                outerInstance.Clear();
-            }
-
-            public bool Contains(TValue item)
-            {
-                for (int i = 0; i < outerInstance.values.Length; i++)
+                else
                 {
-                    var value = outerInstance.values[i];
-                    if (J2N.Collections.Generic.EqualityComparer<TValue>.Equals(value, item))
-                        return true;
+                    try
+                    {
+                        object?[] objects = (object?[])array;
+                        foreach (var entry in this)
+                            objects[index++] = entry;
+                    }
+                    catch (ArrayTypeMismatchException)
+                    {
+                        throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
+                    }
                 }
-                return false;
             }
 
-            public void CopyTo(TValue[] array, int arrayIndex) // LUCENENET TODO: API - make an explicit implementation that isn't public
+            /// <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()
             {
-                throw UnsupportedOperationException.Create();
+                return new Enumerator(dictionary);
             }
 
-            public IEnumerator<TValue> GetEnumerator()
-            {
-                return new ValueEnumerator(outerInstance);
-            }
+            IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => GetEnumerator();
 
-            public bool Remove(TValue item) // LUCENENET TODO: API - make an explicit implementation that isn't public
-            {
-                throw UnsupportedOperationException.Create();
-            }
+            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
-            IEnumerator IEnumerable.GetEnumerator()
+            bool ICollection<TValue>.Remove(TValue item)
             {
-                return GetEnumerator();
+                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 = (ValueEnumerator)GetEnumerator();
+                using var i = GetEnumerator();
                 if (!i.HasNext)
                     return "[]";
 
@@ -1276,93 +2423,348 @@ namespace Lucene.Net.Analysis.Util
                 sb.Append('[');
                 while (i.MoveNext())
                 {
-                    TValue value = i.Current;
+                    TValue? value = i.Current;
                     if (sb.Length > 1)
                     {
                         sb.Append(',').Append(' ');
                     }
-                    sb.Append(value.ToString());
+                    if (value is not null)
+                        sb.Append(value.ToString());
+                    else
+                        sb.Append("null");
                 }
 
                 return sb.Append(']').ToString();
             }
 
+            #region Nested Struct: Enumerator
+
             /// <summary>
-            /// LUCENENET specific class to enumerate the values in the <see cref="ValueCollection"/>.
+            /// Enumerates the elements of a <see cref="ValueCollection"/>.
             /// </summary>
-            private class ValueEnumerator : IEnumerator<TValue>
-            {
-                private readonly EntryIterator entryIterator;
-
-                public ValueEnumerator(CharArrayMap<TValue> outerInstance)
+            /// <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 = new EntryIterator(outerInstance, !outerInstance.IsReadOnly);
+                    this.entryIterator = dictionary.GetEnumerator();
                 }
 
-                public TValue Current => entryIterator.CurrentValue;
-
-                object IEnumerator.Current => Current;
-
+                /// <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();
                 }
 
-                public void Reset()
+                private void Reset()
                 {
-                    entryIterator.Reset();
+                    ((IEnumerator)entryIterator).Reset();
                 }
 
-                public bool HasNext => entryIterator.HasNext;
+                void IEnumerator.Reset() => Reset();
+
+                internal bool HasNext => entryIterator.HasNext;
             }
+
+            #endregion
         }
 
-        #endregion
+        #endregion Nested Class: ValueCollection
 
         /// <summary>
-        /// <c>true</c> if the <see cref="CharArrayMap{TValue}"/> is read-only; otherwise <c>false</c>.
+        /// <c>true</c> if the <see cref="CharArrayDictionary{TValue}"/> is read-only; otherwise <c>false</c>.
         /// </summary>
-        public virtual bool IsReadOnly { get; private set; }
+        public virtual bool IsReadOnly => false;
+
+        #endregion For .NET Support LUCENENET
 
         /// <summary>
-        /// Returns an enumerator that iterates through the <see cref="CharArrayMap{TValue}"/>.
+        /// Returns an enumerator that iterates through the <see cref="CharArrayDictionary{TValue}"/>.
         /// </summary>
-        public virtual IEnumerator<KeyValuePair<string, TValue>> GetEnumerator()
+        /// <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 EntryIterator(this, false);
+            return new Enumerator(this, Enumerator.KeyValuePair);
         }
 
-        /// <summary>
-        /// Returns an enumerator that iterates through the <see cref="CharArrayMap{TValue}"/>.
-        /// </summary>
-        IEnumerator IEnumerable.GetEnumerator()
+        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)
         {
-            return GetEnumerator();
+            throw UnsupportedOperationException.Create();
         }
 
-        [Obsolete("Not applicable in this class.")]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public virtual bool Remove(string key) // LUCENENET TODO: API - make an explicit implementation that isn't public
+        bool IDictionary<string, TValue>.Remove(string key)
         {
             throw UnsupportedOperationException.Create();
         }
 
-        [Obsolete("Not applicable in this class.")]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public virtual bool Remove(KeyValuePair<string, TValue> item) // LUCENENET TODO: API - make an explicit implementation that isn't public
+        bool ICollection<KeyValuePair<string, TValue>>.Remove(KeyValuePair<string, TValue> item)
         {
             throw UnsupportedOperationException.Create();
         }
 
+        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 key/value pairs contained in the <see cref="CharArrayMap{TValue}"/>.
+        /// Gets the number of text/value pairs contained in the <see cref="CharArrayDictionary{TValue}"/>.
         /// </summary>
         public virtual int Count => count;
 
@@ -1371,67 +2773,44 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         public override string ToString()
         {
+            if (count == 0)
+                return "{}";
+
             var sb = new StringBuilder("{");
 
             using (var iter1 = this.GetEnumerator())
             {
                 while (iter1.MoveNext())
                 {
-                    KeyValuePair<string, TValue> entry = iter1.Current;
                     if (sb.Length > 1)
                     {
                         sb.Append(", ");
                     }
-                    sb.Append(entry.Key);
+                    sb.Append(iter1.CurrentKey);
                     sb.Append('=');
-                    sb.Append(entry.Value);
+                    if (iter1.CurrentValue is not null)
+                        sb.Append(iter1.CurrentValue);
+                    else
+                        sb.Append("null");
                 }
             }
 
             return sb.Append('}').ToString();
         }
 
-        private EntrySet_ entrySet = null;
-        private CharArraySet keySet = null;
-        private KeyCollection originalKeySet = null;
+   
 
-        internal virtual EntrySet_ CreateEntrySet()
-        {
-            return new EntrySet_(this, true);
-        }
+        // LUCENENET: Removed entrySet because in .NET we use the collection itself as the IEnumerable
+        private CharArraySet? keySet = null;
 
-        // LUCENENET NOTE: This MUST be a method, since there is an
-        // extension method that this class needs to override the behavior of.
-        public EntrySet_ EntrySet()
-        {
-            if (entrySet is null)
-            {
-                entrySet = CreateEntrySet();
-            }
-            return entrySet;
-        }
+        // LUCENENET: Removed entrySet(), createEntrySet() because in .NET we use the collection itself as the IEnumerable
 
-        /// <summary>
-        /// helper for <see cref="CharArraySet"/> to not produce endless recursion
-        /// </summary>
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public ICollection<string> OriginalKeySet
-        {
-            get
-            {
-                if (originalKeySet is null)
-                {
-                    // prevent adding of entries
-                    originalKeySet = new KeyCollection(this);
-                }
-                return originalKeySet;
-            }
-        }
+        // LUCENENET: Removed originalKeySet() because we fixed infinite recursion
+        // by adding a custom enumerator for KeyCollection.
 
         /// <summary>
-        /// Returns an <see cref="CharArraySet"/> view on the map's keys.
-        /// The set will use the same <see cref="matchVersion"/> as this map. 
+        /// 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
         {
@@ -1440,1529 +2819,1065 @@ namespace Lucene.Net.Analysis.Util
                 if (keySet is null)
                 {
                     // prevent adding of entries
-                    keySet = new UnmodifiableCharArraySet(this);
+                    keySet = new KeyCollection(this);
                 }
                 return keySet;
             }
         }
 
-        private sealed class UnmodifiableCharArraySet : CharArraySet
+        #region Nested Class: KeyCollection
+
+        // LUCENENET: This was an anonymous class in Java
+        [DebuggerDisplay("Count = {Count}, Values = {ToString()}")]
+        private sealed class KeyCollection : CharArraySet
         {
-            internal UnmodifiableCharArraySet(ICharArrayMap map) 
+            internal KeyCollection(CharArrayDictionary<TValue> map)
                 : base(map)
             {
             }
 
-            public override bool Add(object o) // LUCENENET TODO: API - make an explicit implementation that isn't public
+            public override bool IsReadOnly => true;
+
+            public override bool Add<T>(T text)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
             }
-            public override bool Add(ICharSequence text) // LUCENENET TODO: API - make an explicit implementation that isn't public
+            public override bool Add(ICharSequence text)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
             }
-            public override bool Add(string text) // LUCENENET TODO: API - make an explicit implementation that isn't public
+            public override bool Add(string text)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
             }
-            public override bool Add(char[] text) // LUCENENET TODO: API - make an explicit implementation that isn't public
+            public override bool Add(char[] text)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_KeyCollectionSet);
             }
         }
 
+        #endregion Nested Class: KeyCollection
+
+        #region Nested Class: Enumerator
+
         /// <summary>
-        /// public iterator class so efficient methods are exposed to users
+        /// 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>
-        public class EntryIterator : IEnumerator<KeyValuePair<string, TValue>>
+        /// <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 CharArrayMap<TValue> outerInstance;
+            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;
 
-            internal EntryIterator(CharArrayMap<TValue> outerInstance, 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.outerInstance = outerInstance;
-                this.allowModify = allowModify;
+                this.dictionary = dictionary;
+                this.getEnumeratorRetType = getEnumeratorRetType;
+                this.version = dictionary.version;
+                this.allowModify = !dictionary.IsReadOnly;
+                this.notStartedOrEnded = true;
                 GoNext();
             }
 
-            internal void GoNext()
+            private void GoNext()
             {
                 lastPos = pos;
                 pos++;
-                while (pos < outerInstance.keys.Length && outerInstance.keys[pos] is null)
+                while (pos < dictionary.keys.Length && dictionary.keys[pos] is null)
                 {
                     pos++;
                 }
             }
 
-            public virtual bool HasNext => pos < outerInstance.keys.Length;
+            internal bool HasNext => pos < dictionary.keys.Length;
 
             /// <summary>
-            /// gets the next key... do not modify the returned char[]
+            /// Gets the current text as a <see cref="CharArrayCharSequence"/>.
             /// </summary>
-            public virtual char[] NextKey()
+            // LUCENENET specific - quick access to ICharSequence interface
+            public ICharSequence CurrentKeyCharSequence
             {
-                GoNext();
-                return outerInstance.keys[lastPos];
+                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 next key as a newly created <see cref="string"/> object
+            /// Gets the current text... do not modify the returned char[].
             /// </summary>
-            public virtual string NextKeyString()
+            [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
+            [WritableArray]
+            public char[] CurrentKey
             {
-                return new string(NextKey());
+                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>
-            /// returns the value associated with the current key
+            /// Gets the current text as a newly created <see cref="string"/> object.
             /// </summary>
-            public virtual TValue CurrentValue
+            public string CurrentKeyString
             {
                 get
                 {
-                    var val = outerInstance.values[lastPos];
-                    return val != null ? val.Value : default;
+                    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>
-            /// sets the value associated with the last key returned
+            /// Gets the value associated with the current text.
             /// </summary>
-            public virtual TValue SetValue(TValue value)
+            [MaybeNull]
+            public TValue CurrentValue
             {
-                if (!allowModify)
+                get
                 {
-                    throw UnsupportedOperationException.Create();
+                    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;
                 }
-                TValue old = outerInstance.values[lastPos].Value;
-                outerInstance.values[lastPos].Value = value;
+            }
+
+            /// <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
-            public void Dispose()
-            {
-                Dispose(true);
-                GC.SuppressFinalize(this);
-            }
 
-            protected virtual void Dispose(bool disposing)
+            /// <summary>
+            /// Releases all resources used by the <see cref="Enumerator"/>.
+            /// </summary>
+            public void Dispose()
             {
                 // nothing to do
             }
 
-            public virtual bool MoveNext()
-            {
-                if (!HasNext) return false;
+            /// <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;
             }
 
-            public virtual void Reset()
+            private void Reset()
             {
+                if (version != dictionary.version)
+                    throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+
                 pos = -1;
+                notStartedOrEnded = true;
                 GoNext();
             }
 
-            public virtual KeyValuePair<string, TValue> Current
+            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
                 {
-                    var val = outerInstance.values[lastPos];
-                    return new KeyValuePair<string, TValue>(
-                        new string(outerInstance.keys[lastPos]), 
-                        val != null ? val.Value : default);
+                    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 => Current;
-
-            #endregion
-        }
-
-        // 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.
-
-        /// <summary>
-        /// public EntrySet_ class so efficient methods are exposed to users
-        /// 
-        /// NOTE: In .NET this was renamed to EntrySet_ because it conflicted with the
-        /// method EntrySet(). Since there is also an extension method named <see cref="T:IDictionary{K,V}.EntrySet()"/> 
-        /// that this class needs to override, changing the name of the method was not
-        /// possible because the extension method would produce incorrect results if it were
-        /// inadvertently called, leading to hard-to-diagnose bugs.
-        /// 
-        /// Another difference between this set and the Java counterpart is that it implements
-        /// <see cref="ICollection{T}"/> rather than <see cref="ISet{T}"/> so we don't have to implement
-        /// a bunch of methods that we aren't really interested in. The <see cref="Keys"/> and <see cref="Values"/>
-        /// properties both return <see cref="ICollection{T}"/>, and while there is no <see cref="EntrySet()"/> method
-        /// or property in .NET, if there were it would certainly return <see cref="ICollection{T}"/>.
-        /// </summary>
-        public sealed class EntrySet_ : ICollection<KeyValuePair<string, TValue>>
-        {
-            private readonly CharArrayMap<TValue> outerInstance;
-
-            internal readonly bool allowModify;
-
-            internal EntrySet_(CharArrayMap<TValue> outerInstance, bool allowModify)
+            object IEnumerator.Current
             {
-                this.outerInstance = outerInstance;
-                this.allowModify = allowModify;
-            }
+                get
+                {
+                    if (notStartedOrEnded)
+                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
 
-            public IEnumerator GetEnumerator()
-            {
-                return new EntryIterator(outerInstance, allowModify);
-            }
+                    char[] key = dictionary.keys[lastPos];
+                    if (key is null)
+                        throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
+                    MapValue value = dictionary.values[lastPos];
 
-            IEnumerator<KeyValuePair<string, TValue>> IEnumerable<KeyValuePair<string, TValue>>.GetEnumerator()
-            {
-                return (IEnumerator<KeyValuePair<string, TValue>>)GetEnumerator();
+                    if (getEnumeratorRetType == DictEntry)
+                    {
+                        return new DictionaryEntry(new string(key), value.Value);
+                    }
+                    else
+                    {
+                        return new KeyValuePair<string, TValue>(new string(key), value.Value!);
+                    }
+                }
             }
 
-            public bool Contains(object o)
+            object IDictionaryEnumerator.Key
             {
-                if (!(o is KeyValuePair<string, TValue>))
+                get
                 {
-                    return false;
-                }
-                var e = (KeyValuePair<string, TValue>)o;
-                string key = e.Key;
-                TValue val = e.Value;
-                TValue v = outerInstance.Get(key);
-                return v is null ? val is null : v.Equals(val);
-            }
+                    if (notStartedOrEnded)
+                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
 
-            [Obsolete("Not applicable in this class.")]
-            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-            [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-            public bool Remove(KeyValuePair<string, TValue> item) // LUCENENET TODO: API - make an explicit implementation that isn't public
-            {
-                throw UnsupportedOperationException.Create();
+                    return Current.Key;
+                }
             }
 
-            public int Count => outerInstance.count;
-
-            public void Clear()
+            object? IDictionaryEnumerator.Value
             {
-                if (!allowModify)
+                get
                 {
-                    throw UnsupportedOperationException.Create();
+                    if (notStartedOrEnded)
+                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+
+                    return Current.Value;
                 }
-                outerInstance.Clear();
             }
 
-            #region LUCENENET Added for better .NET support
-
-            public void CopyTo(KeyValuePair<string, TValue>[] array, int arrayIndex)
+            DictionaryEntry IDictionaryEnumerator.Entry
             {
-                outerInstance.CopyTo(array, arrayIndex);
-            }
+                get
+                {
+                    if (notStartedOrEnded)
+                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
 
-            [Obsolete("Not applicable in this class.")]
-            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-            [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-            public bool Contains(KeyValuePair<string, TValue> item)
-            {
-                return outerInstance.Contains(item);
+                    return new DictionaryEntry(Current.Key, Current.Value);
+                }
             }
 
-            public void Add(KeyValuePair<string, TValue> item)
-            {
-                outerInstance.Add(item);
-            }
+            bool ICharArrayDictionaryEnumerator.NotStartedOrEnded => notStartedOrEnded;
 
-            public bool IsReadOnly => !allowModify;
+            #endregion
+        }
 
-            public override string ToString()
-            {
-                var sb = new StringBuilder("[");
+        #endregion Nested Class: Enumerator
 
-                IEnumerator<KeyValuePair<string, TValue>> iter1 = new EntryIterator(this.outerInstance, false);
-                while (iter1.MoveNext())
-                {
-                    KeyValuePair<string, TValue> entry = iter1.Current;
-                    if (sb.Length > 1)
-                    {
-                        sb.Append(", ");
-                    }
-                    sb.Append(entry.Key);
-                    sb.Append('=');
-                    sb.Append(entry.Value);
-                }
+        // 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.
 
-                return sb.Append(']').ToString();
-            }
-            #endregion
-        }
+        // LUCENENET: EntrySet class removed because in .NET we get the entries by calling GetEnumerator() on the dictionary.
 
-        // LUCENENET: Moved UnmodifiableMap static methods to CharArrayMap class
+        // LUCENENET: Moved UnmodifiableMap static methods to CharArrayDictionary class
 
-        // LUCENENET: Moved Copy static methods to CharArrayMap class
+        // LUCENENET: Moved Copy static methods to CharArrayDictionary class
 
-        /// <summary>
-        /// Returns an empty, unmodifiable map. </summary>
-        public static CharArrayMap<TValue> EmptyMap()
-        {
-            return EMPTY_MAP;
-        }
+        // LUCENENET: Removed EmptyMap() - use Empty instead
 
-        // LUCENENET: Moved UnmodifiableCharArraymap to CharArrayMap class
+        // LUCENENET: Moved UnmodifiableCharArraymap to CharArrayDictionary class
 
-        // LUCENENET: Moved EmptyCharArrayMap to CharArrayMap class
+        // LUCENENET: Moved EmptyCharArrayDictionary to CharArrayDictionary class
     }
 
     /// <summary>
     /// LUCENENET specific interface used so <see cref="CharArraySet"/>
-    /// can hold a reference to <see cref="CharArrayMap{TValue}"/> without
-    /// knowing its generic type.
+    /// can hold a reference to <see cref="CharArrayDictionary{TValue}"/> without
+    /// knowing its generic closing type for TValue.
     /// </summary>
-    internal interface ICharArrayMap
+    internal interface ICharArrayDictionary
     {
         void Clear();
-        bool ContainsKey(char[] text, int offset, int length);
+        bool ContainsKey(char[] text, int startIndex, int length);
         bool ContainsKey(char[] text);
-        bool ContainsKey(object o);
+        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; }
-        ICollection<string> OriginalKeySet { get; }
+        bool Put(char[] text, int startIndex, int Length);
         bool Put(char[] text);
         bool Put(ICharSequence text);
-        bool Put(object o);
+        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();
     }
 
-    public static class CharArrayMap // LUCENENET specific: CA1052 Static holder types should be Static or NotInheritable
+    /// <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 map as a <see cref="CharArrayMap{TValue}"/>. If the given map
-        /// is a <see cref="CharArrayMap{TValue}"/> the ignoreCase property will be preserved.
+        /// 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="CharArrayMap{TValue}"/> where
-        /// the <see cref="LuceneVersion"/> of the source map differs from its copy
-        /// <see cref="CharArrayMap{TValue}.CharArrayMap(LuceneVersion, IDictionary{string, TValue}, bool)"/> should be used instead.
+        /// <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 map if it is an instance of <see cref="CharArrayMap{TValue}"/>.
+        /// 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 map is a <see cref="CharArrayMap{TValue}"/>. </param>
-        /// <param name="map">
-        ///          a map to copy </param>
-        /// <returns> a copy of the given map as a <see cref="CharArrayMap{TValue}"/>. If the given map
-        ///         is a <see cref="CharArrayMap{TValue}"/> the ignoreCase property as well as the
-        ///         <paramref name="matchVersion"/> will be of the given map will be preserved. </returns>
-        public static CharArrayMap<TValue> Copy<TValue>(LuceneVersion matchVersion, IDictionary<string, TValue> map)
+        ///          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 (map == CharArrayMap<TValue>.EmptyMap())
+            if (dictionary == CharArrayDictionary<TValue>.Empty)
             {
-                return CharArrayMap<TValue>.EmptyMap();
+                return CharArrayDictionary<TValue>.Empty;
             }
 
-            if (map is CharArrayMap<TValue>)
+            if (dictionary is CharArrayDictionary<TValue> m)
             {
-                var m = map as CharArrayMap<TValue>;
                 // 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][];
-                Array.Copy(m.keys, 0, keys, 0, keys.Length);
-                var values = new CharArrayMap<TValue>.MapValue[m.values.Length];
-                Array.Copy(m.values, 0, values, 0, values.Length);
-                m = new CharArrayMap<TValue>(m) { keys = keys, values = values };
+                m.keys.AsSpan().CopyTo(keys.AsSpan());
+                var values = new CharArrayDictionary<TValue>.MapValue[m.values.Length];
+                m.values.AsSpan().CopyTo(values.AsSpan());
+                m = new CharArrayDictionary<TValue>(m) { keys = keys, values = values };
                 return m;
             }
-            return new CharArrayMap<TValue>(matchVersion, map, false);
+            return new CharArrayDictionary<TValue>(matchVersion, dictionary, false);
         }
 
         /// <summary>
-        /// Used by <see cref="CharArraySet"/> to copy <see cref="CharArrayMap{TValue}"/> without knowing 
+        /// Used by <see cref="CharArraySet"/> to copy <see cref="CharArrayDictionary{TValue}"/> without knowing 
         /// its generic type.
         /// </summary>
-        internal static CharArrayMap<TValue> Copy<TValue>(LuceneVersion matchVersion, ICharArrayMap map)
+        internal static CharArrayDictionary<TValue> Copy<TValue>(LuceneVersion matchVersion, [DisallowNull] ICharArrayDictionary map)
         {
-            return Copy(matchVersion, map as IDictionary<string, TValue>);
+            return Copy(matchVersion, (IDictionary<string, TValue>)map);
         }
 
         /// <summary>
-        /// Returns an unmodifiable <see cref="CharArrayMap{TValue}"/>. This allows to provide
-        /// unmodifiable views of internal map for "read-only" use.
+        /// 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 map for which the unmodifiable map is returned. </param>
-        /// <returns> an new unmodifiable <see cref="CharArrayMap{TValue}"/>. </returns>
+        ///          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 map is <c>null</c>. </exception>
-        [Obsolete("Use the CharArrayMap<TValue>.AsReadOnly() instance method instead. This method will be removed in 4.8.0 release candidate."), EditorBrowsable(EditorBrowsableState.Never)]
-        public static CharArrayMap<TValue> UnmodifiableMap<TValue>(CharArrayMap<TValue> map)
+        ///           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), "Given map is null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
+                throw new ArgumentNullException(nameof(map)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
             }
-            if (map == CharArrayMap<TValue>.EmptyMap() || map.Count == 0)
+            if (map == CharArrayDictionary<TValue>.Empty || map.Count == 0)
             {
-                return CharArrayMap<TValue>.EmptyMap();
+                return CharArrayDictionary<TValue>.Empty;
             }
-            if (map is CharArrayMap.UnmodifiableCharArrayMap<TValue>)
+            if (map is CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>)
             {
                 return map;
             }
-            return new CharArrayMap.UnmodifiableCharArrayMap<TValue>(map);
+            return new CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>(map);
         }
 
         /// <summary>
-        /// Used by <see cref="CharArraySet"/> to create an <see cref="UnmodifiableCharArrayMap{TValue}"/> instance
+        /// Used by <see cref="CharArraySet"/> to create an <see cref="ReadOnlyCharArrayDictionary{TValue}"/> instance
         /// without knowing the type of <typeparamref name="TValue"/>.
         /// </summary>
-        internal static ICharArrayMap UnmodifiableMap<TValue>(ICharArrayMap map)
+        internal static ICharArrayDictionary UnmodifiableMap<TValue>(ICharArrayDictionary map)
         {
             if (map is null)
             {
-                throw new ArgumentNullException(nameof(map), "Given map is null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
+                throw new ArgumentNullException(nameof(map)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
             }
-            if (map == CharArrayMap<TValue>.EmptyMap() || map.Count == 0)
+            if (map == CharArrayDictionary<TValue>.Empty || map.Count == 0)
             {
-                return CharArrayMap<TValue>.EmptyMap();
+                return CharArrayDictionary<TValue>.Empty;
             }
-            if (map is CharArrayMap.UnmodifiableCharArrayMap<TValue>)
+            if (map is CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>)
             {
                 return map;
             }
-            return new CharArrayMap.UnmodifiableCharArrayMap<TValue>(map);
+            return new CharArrayDictionary.ReadOnlyCharArrayDictionary<TValue>(map);
         }
 
+        #region Nested Class: ReadOnlyCharArrayDictionary<TValue>
+
         // package private CharArraySet instanceof check in CharArraySet
-        internal class UnmodifiableCharArrayMap<TValue> : CharArrayMap<TValue>
+        internal class ReadOnlyCharArrayDictionary<TValue> : CharArrayDictionary<TValue>
         {
-            public UnmodifiableCharArrayMap(CharArrayMap<TValue> map)
+            public ReadOnlyCharArrayDictionary([DisallowNull] CharArrayDictionary<TValue> map)
                 : base(map)
             { }
 
-            public UnmodifiableCharArrayMap(ICharArrayMap map)
-                : base(map as CharArrayMap<TValue>)
+            public ReadOnlyCharArrayDictionary([DisallowNull] ICharArrayDictionary map)
+                : base((CharArrayDictionary<TValue>)map)
             { }
 
             public override void Clear()
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override TValue Put(char[] text, TValue val)
+            public override bool Put(char[] text, int startIndex, int length, TValue value, [MaybeNullWhen(true)] out TValue previousValue)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override TValue Put(ICharSequence text, TValue val)
+            public override bool Put(char[] text, TValue value, [MaybeNullWhen(true)] out TValue previousValue)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override TValue Put(string text, TValue val)
+            public override bool Put(ICharSequence text, TValue val, [MaybeNullWhen(true)] out TValue previousValue)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override TValue Put(object o, TValue val)
+            public override bool Put(string text, TValue val, [MaybeNullWhen(true)] out TValue previousValue)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool Put(char[] text)
+            public override bool Put<T>(T text, TValue val, [MaybeNullWhen(true)] out TValue previousValue)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool Put(ICharSequence text)
+            internal override bool Put(char[] text, int startIndex, int length)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool Put(string text)
+            internal override bool Put(char[] text)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool Put(object o)
+            internal override bool Put(ICharSequence text)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            [Obsolete("Not applicable in this class.")]
-            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-            [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-            public override bool Remove(string key)
+            internal override bool Put(string text)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            internal override EntrySet_ CreateEntrySet()
+            internal override bool Put<T>(T text)
             {
-                return new EntrySet_(this, false);
+                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 void Add(string key, TValue value)
+
+            public override bool IsReadOnly => true;
+
+            public override void Add(string text, TValue value)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-            public override void Add(KeyValuePair<string, TValue> item)
+
+            public override void Add(char[] text, TValue value)
             {
-                throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-            public override TValue this[char[] key, int offset, int length]
+
+            public override void Add(ICharSequence text, TValue value)
             {
-                get => base[key, offset, length];
-                set => throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-            public override TValue this[char[] key]
+
+            public override void Add<T>(T text, TValue value)
             {
-                get => base[key];
-                set => throw UnsupportedOperationException.Create();
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-            public override TValue this[ICharSequence key]
+
+            public override TValue this[char[] text, int startIndex, int length]
             {
-                get => base[key];
-                set => throw UnsupportedOperationException.Create();
+                get => base[text, startIndex, length];
+                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-            public override TValue this[string key]
+
+            public override TValue this[char[] text]
             {
-                get => base[key];
-                set => throw UnsupportedOperationException.Create();
+                get => base[text];
+                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-            public override TValue this[object key]
+
+            public override TValue this[ICharSequence text]
             {
-                get => base[key];
-                set => throw UnsupportedOperationException.Create();
+                get => base[text];
+                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            [Obsolete("Not applicable in this class.")]
-            [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-            [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-            public override bool Remove(KeyValuePair<string, TValue> item)
+            public override TValue this[string text]
             {
-                throw UnsupportedOperationException.Create();
+                get => base[text];
+                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-            #endregion
-        }
 
-        /// <summary>
-        /// Empty <see cref="UnmodifiableCharArrayMap{V}"/> optimized for speed.
-        /// Contains checks will always return <c>false</c> or throw
-        /// NPE if necessary.
-        /// </summary>
-        internal class EmptyCharArrayMap<V> : UnmodifiableCharArrayMap<V>
-        {
-            public EmptyCharArrayMap()
-#pragma warning disable 612, 618
-                : base(new CharArrayMap<V>(LuceneVersion.LUCENE_CURRENT, 0, false))
-#pragma warning restore 612, 618
+            public override TValue this[object text]
             {
+                get => base[text];
+                set => throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool ContainsKey(char[] text, int offset, int length)
+            public override void PutAll(IDictionary<string, TValue> collection)
             {
-                if (text is null)
-                {
-                    throw new ArgumentNullException(nameof(text));
-                }
-                return false;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool ContainsKey(char[] text)
+            public override void PutAll(IDictionary<char[], TValue> collection)
             {
-                if (text is null)
-                {
-                    throw new ArgumentNullException(nameof(text));
-                }
-                return false;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool ContainsKey(ICharSequence text)
+            public override void PutAll(IDictionary<ICharSequence, TValue> collection)
             {
-                if (text is null)
-                {
-                    throw new ArgumentNullException(nameof(text));
-                }
-                return false;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override bool ContainsKey(object o)
+            public override void PutAll<T>(IDictionary<T, TValue> collection)
             {
-                if (o is null)
-                {
-                    throw new ArgumentNullException(nameof(o));
-                }
-                return false;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override V Get(char[] text, int offset, int length)
+            public override void PutAll(IEnumerable<KeyValuePair<string, TValue>> collection)
             {
-                if (text is null)
-                {
-                    throw new ArgumentNullException(nameof(text));
-                }
-                return default;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override V Get(char[] text)
+            public override void PutAll(IEnumerable<KeyValuePair<char[], TValue>> collection)
             {
-                if (text is null)
-                {
-                    throw new ArgumentNullException(nameof(text));
-                }
-                return default;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override V Get(ICharSequence text)
+            public override void PutAll(IEnumerable<KeyValuePair<ICharSequence, TValue>> collection)
             {
-                if (text is null)
-                {
-                    throw new ArgumentNullException(nameof(text));
-                }
-                return default;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
 
-            public override V Get(object o)
+            public override void PutAll<T>(IEnumerable<KeyValuePair<T, TValue>> collection)
             {
-                if (o is null)
-                {
-                    throw new ArgumentNullException(nameof(o));
-                }
-                return default;
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
             }
-        }
-    }
-
-    /// <summary>
-    /// LUCENENET specific extension methods for CharArrayMap
-    /// </summary>
-    public static class CharArrayMapExtensions
-    {
-        #region ContainsKey
-
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="bool"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, bool key)
-        {
-            return map.ContainsKey(key.ToString());
-        }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="byte"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, byte key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="char"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, char key)
-        {
-            return map.ContainsKey("" + key);
-        }
+            internal override void Set(char[] text)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        ///// <summary>
-        ///// <c>true</c> if the <paramref name="key"/> <see cref="decimal"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        ///// otherwise <c>false</c>
-        ///// </summary>
-        //public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, decimal key)
-        //{
-        //    return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        //}
+            internal override void Set(char[] text, TValue? value)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        ///// <summary>
-        ///// <c>true</c> if the <paramref name="key"/> <see cref="double"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        ///// otherwise <c>false</c>
-        ///// </summary>
-        //public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, double key)
-        //{
-        //    return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        //}
+            internal override void Set(char[] text, int startIndex, int length)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        ///// <summary>
-        ///// <c>true</c> if the <paramref name="key"/> <see cref="float"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        ///// otherwise <c>false</c>
-        ///// </summary>
-        //public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, float key)
-        //{
-        //    return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        //}
+            internal override void Set(char[] text, int startIndex, int length, TValue? value)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="int"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, int key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        }
+            internal override void Set(ICharSequence text)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="long"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, long key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        }
+            internal override void Set(ICharSequence text, TValue? value)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="sbyte"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        [CLSCompliant(false)]
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, sbyte key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        }
+            internal override void Set(string text)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="short"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, short key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        }
+            internal override void Set(string text, TValue? value)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="uint"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        [CLSCompliant(false)]
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, uint key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        }
+            internal override void Set<T>(T text)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="ulong"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        [CLSCompliant(false)]
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, ulong key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
-        }
+            internal override void Set<T>(T text, TValue? value)
+            {
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+            }
 
-        /// <summary>
-        /// <c>true</c> if the <paramref name="key"/> <see cref="ushort"/> is in the <see cref="CharArrayMap{TValue}.KeySet"/>; 
-        /// otherwise <c>false</c>
-        /// </summary>
-        [CLSCompliant(false)]
-        public static bool ContainsKey<TValue>(this CharArrayMap<TValue> map, ushort key)
-        {
-            return map.ContainsKey(key.ToString(CultureInfo.InvariantCulture));
+            #endregion
         }
 
         #endregion
 
-        #region Get
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, bool text)
-        {
-            return map.Get(text.ToString());
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, byte text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, char text)
-        {
-            return map.Get("" + text);
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, decimal text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, double text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, float text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, int text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, long text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
+        #region Nested Class: EmptyCharArrayDictionary<V>
 
         /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, sbyte text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, short text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, uint text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
+        /// Empty <see cref="ReadOnlyCharArrayDictionary{V}"/> optimized for speed.
+        /// Contains checks will always return <c>false</c> or throw
+        /// NPE if necessary.
         /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, ulong text)
+        internal class EmptyCharArrayDictionary<V> : ReadOnlyCharArrayDictionary<V>
         {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
+            public EmptyCharArrayDictionary()
+#pragma warning disable 612, 618
+                : base(new CharArrayDictionary<V>(LuceneVersion.LUCENE_CURRENT, 0, false))
+#pragma warning restore 612, 618
+            {
+            }
 
-        /// <summary>
-        /// returns the value of the mapping of the chars inside this <paramref name="text"/>
-        /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Get<TValue>(this CharArrayMap<TValue> map, ushort text)
-        {
-            return map.Get(text.ToString(CultureInfo.InvariantCulture));
-        }
+            public override bool ContainsKey(char[] text, int startIndex, int length)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        #endregion
+                return false;
+            }
 
-        #region Put
+            public override bool ContainsKey(char[] text)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, bool text, TValue value)
-        {
-            return map.Put(text.ToString(), value);
-        }
+                return false;
+            }
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, byte text, TValue value)
-        {
-            return map.Put(text.ToString(), value);
-        }
+            public override bool ContainsKey(ICharSequence text)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, char text, TValue value)
-        {
-            return map.Put(text.ToString(), value);
-        }
+                return false;
+            }
 
-        ///// <summary>
-        ///// Add the given mapping.
-        ///// </summary>
-        //public static TValue Put<TValue>(this CharArrayMap<TValue> map, decimal text, TValue value)
-        //{
-        //    return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        //}
+            public override bool ContainsKey<T>(T text)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        ///// <summary>
-        ///// Add the given mapping.
-        ///// </summary>
-        //public static TValue Put<TValue>(this CharArrayMap<TValue> map, double text, TValue value)
-        //{
-        //    return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        //}
+                return false;
+            }
 
-        ///// <summary>
-        ///// Add the given mapping.
-        ///// </summary>
-        //public static TValue Put<TValue>(this CharArrayMap<TValue> map, float text, TValue value)
-        //{
-        //    return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        //}
+            internal override V Get(char[] text, int startIndex, int length, bool throwIfNotfound = true)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, int text, TValue value)
-        {
-            return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        }
+                if (throwIfNotfound)
+                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, new string(text, startIndex, length)));
+                return default!;
+            }
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, long text, TValue value)
-        {
-            return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        }
+            internal override V Get(char[] text, bool throwIfNotfound = true)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, sbyte text, TValue value)
-        {
-            return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        }
+                if (throwIfNotfound)
+                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, new string(text)));
+                return default!;
+            }
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, short text, TValue value)
-        {
-            return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        }
+            internal override V Get(ICharSequence text, bool throwIfNotfound = true)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, uint text, TValue value)
-        {
-            return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        }
+                if (throwIfNotfound)
+                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, text));
+                return default!;
+            }
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, ulong text, TValue value)
-        {
-            return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
-        }
+            internal override V Get<T>(T text, bool throwIfNotfound = true)
+            {
+                if (text is null)
+                    throw new ArgumentNullException(nameof(text));
 
-        /// <summary>
-        /// Add the given mapping.
-        /// </summary>
-        [CLSCompliant(false)]
-        public static TValue Put<TValue>(this CharArrayMap<TValue> map, ushort text, TValue value)
-        {
-            return map.Put(text.ToString(CultureInfo.InvariantCulture), value);
+                if (throwIfNotfound)
+                    throw new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, text));
+                return default!;
+            }
         }
 
         #endregion
 
-        #region PutAll
+        private readonly static CultureInfo invariant = CultureInfo.InvariantCulture;
+        private const string TrueString = "true";
+        private const string FalseString = "false";
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<bool, TValue> dictionary)
-        {
-            foreach (var kvp in dictionary)
-            {
-                map.Put(kvp.Key.ToString(), kvp.Value);
-            }
-        }
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<byte, TValue> dictionary)
+        internal enum CharReturnType
         {
-            foreach (var kvp in dictionary)
-            {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-            }
+            String = 0,
+            CharArray = 1,
         }
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<char, TValue> dictionary)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static CharReturnType ConvertObjectToChars<T>(T key, out char[] chars, out string str)
         {
-            foreach (var kvp in dictionary)
-            {
-                map.Put("" + kvp.Key, kvp.Value);
-            }
+#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);
         }
 
-        ///// <summary>
-        ///// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        ///// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        //public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<decimal, TValue> dictionary)
-        //{
-        //    foreach (var kvp in dictionary)
-        //    {
-        //        map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-        //    }
-        //}
 
-        ///// <summary>
-        ///// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        ///// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        //public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<double, TValue> dictionary)
-        //{
-        //    foreach (var kvp in dictionary)
-        //    {
-        //        map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-        //    }
-        //}
-
-        ///// <summary>
-        ///// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        ///// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        //public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<float, TValue> dictionary)
-        //{
-        //    foreach (var kvp in dictionary)
-        //    {
-        //        map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-        //    }
-        //}
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<int, TValue> dictionary)
+        // 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)
         {
-            foreach (var kvp in dictionary)
-            {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-            }
-        }
+            chars = Arrays.Empty<char>();
+            str = string.Empty;
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<long, TValue> dictionary)
-        {
-            foreach (var kvp in dictionary)
+            if (key is null)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                return CharReturnType.CharArray;
             }
-        }
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<sbyte, TValue> dictionary)
-        {
-            foreach (var kvp in dictionary)
+            // Handle special cases
+            if (key is string strResult)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                str = strResult;
+                return CharReturnType.String;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<short, TValue> dictionary)
-        {
-            foreach (var kvp in dictionary)
+            else if (key is char[] charArray)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                chars = charArray;
+                return CharReturnType.CharArray;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<uint, TValue> dictionary)
-        {
-            foreach (var kvp in dictionary)
+            else if (key is IList<char> charList)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                char[] result = new char[charList.Count];
+                charList.CopyTo(result, arrayIndex: 0);
+                chars = result;
+                return CharReturnType.CharArray;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<ulong, TValue> dictionary)
-        {
-            foreach (var kvp in dictionary)
+            else if (key is StringBuilder stringBuilder)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                char[] result = new char[stringBuilder.Length];
+                stringBuilder.CopyTo(sourceIndex: 0, result, destinationIndex: 0, stringBuilder.Length);
+                chars = result;
+                return CharReturnType.CharArray;
             }
-        }
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="dictionary"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="dictionary">A dictionary of values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IDictionary<ushort, TValue> dictionary)
-        {
-            foreach (var kvp in dictionary)
+            // ICharSequence types
+            else if (key is StringCharSequence strCs)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                str = strCs.Value ?? string.Empty;
+                return CharReturnType.String;
             }
-        }
-
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<bool, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            else if (key is CharArrayCharSequence charArrayCs)
             {
-                map.Put(kvp.Key.ToString(), kvp.Value);
+                chars = charArrayCs.Value ?? Arrays.Empty<char>();
+                return CharReturnType.CharArray;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<byte, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            else if (key is StringBuilderCharSequence stringBuilderCs && stringBuilderCs.HasValue)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                var sb = stringBuilderCs.Value!;
+                char[] result = new char[sb.Length];
+                sb.CopyTo(sourceIndex: 0, result, destinationIndex: 0, sb.Length);
+                chars = result;
+                return CharReturnType.CharArray;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<char, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            else if (key is ICharSequence cs && cs.HasValue)
             {
-                map.Put("" + kvp.Key, kvp.Value);
-            }
-        }
-
-        ///// <summary>
-        ///// This implementation enumerates over the specified <paramref name="collection"/>'s
-        ///// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="collection">The values to add/update in the current map.</param>
-        //public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<decimal, TValue>> collection)
-        //{
-        //    foreach (var kvp in collection)
-        //    {
-        //        map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-        //    }
-        //}
-
-        ///// <summary>
-        ///// This implementation enumerates over the specified <paramref name="collection"/>'s
-        ///// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="collection">The values to add/update in the current map.</param>
-        //public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<double, TValue>> collection)
-        //{
-        //    foreach (var kvp in collection)
-        //    {
-        //        map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-        //    }
-        //}
-
-        ///// <summary>
-        ///// This implementation enumerates over the specified <paramref name="collection"/>'s
-        ///// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="collection">The values to add/update in the current map.</param>
-        //public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<float, TValue>> collection)
-        //{
-        //    foreach (var kvp in collection)
-        //    {
-        //        map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
-        //    }
-        //}
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<int, TValue>> collection)
-        {
-            foreach (var kvp in collection)
-            {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                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;
             }
-        }
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<long, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            // These must be done prior to checking ISpanFormattable and IFormattable
+            else if (key is bool b)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                str = b ? TrueString : FalseString;
+                return CharReturnType.String;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<sbyte, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            else if (key is double d)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                str = J2N.Numerics.Double.ToString(d, invariant);
+                return CharReturnType.String;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<short, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            else if (key is float f)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                str = J2N.Numerics.Single.ToString(f, invariant);
+                return CharReturnType.String;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<uint, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+            else if (key is J2N.Numerics.Number number)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                str = number.ToString(invariant);
+                return CharReturnType.String;
             }
-        }
 
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<ulong, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+#if FEATURE_SPANFORMATTABLE
+            else if (key is ISpanFormattable spanFormattable &&
+                spanFormattable.TryFormat(reuse, out int charsWritten, string.Empty.AsSpan(), invariant))
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                chars = reuse.Slice(0, charsWritten).ToArray();
+                return CharReturnType.CharArray;
             }
-        }
-
-        /// <summary>
-        /// This implementation enumerates over the specified <paramref name="collection"/>'s
-        /// entries, and calls this map's <see cref="CharArrayMap{TValue}.Put(string, TValue)"/> operation once for each entry.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="collection">The values to add/update in the current map.</param>
-        [CLSCompliant(false)]
-        public static void PutAll<TValue>(this CharArrayMap<TValue> map, IEnumerable<KeyValuePair<ushort, TValue>> collection)
-        {
-            foreach (var kvp in collection)
+#endif
+            else if (key is IFormattable formattable)
             {
-                map.Put(kvp.Key.ToString(CultureInfo.InvariantCulture), kvp.Value);
+                str = formattable.ToString(string.Empty, invariant);
+                return CharReturnType.String;
             }
-        }
-
-        #endregion
-
-        #region TryGetValue
 
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, bool key, out TValue value)
-        {
-            return map.TryGetValue(key.ToString(), out value);
+            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>
-        /// Gets the value associated with the specified key.
+        /// 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>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, byte key, out TValue value)
+        /// <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)
         {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        }
+            if (dictionary is null)
+                throw new ArgumentNullException(nameof(dictionary));
 
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, char key, out TValue value)
-        {
-            return map.TryGetValue("" + key, out value);
-        }
-
-        ///// <summary>
-        ///// Gets the value associated with the specified key.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="key">The key of the value to get.</param>
-        ///// <param name="value">When this method returns, contains the value associated with the specified key, 
-        ///// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        //public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, decimal key, out TValue value)
-        //{
-        //    return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        //}
-
-        ///// <summary>
-        ///// Gets the value associated with the specified key.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="key">The key of the value to get.</param>
-        ///// <param name="value">When this method returns, contains the value associated with the specified key, 
-        ///// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        //public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, double key, out TValue value)
-        //{
-        //    return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        //}
-
-        ///// <summary>
-        ///// Gets the value associated with the specified key.
-        ///// </summary>
-        ///// <param name="map">this map</param>
-        ///// <param name="key">The key of the value to get.</param>
-        ///// <param name="value">When this method returns, contains the value associated with the specified key, 
-        ///// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        //public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, float key, out TValue value)
-        //{
-        //    return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        //}
-
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, int key, out TValue value)
-        {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
+            return CharArrayDictionary.Copy<TValue>(matchVersion, dictionary);
         }
 
         /// <summary>
-        /// Gets the value associated with the specified key.
+        /// 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>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, long key, out TValue value)
+        /// <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)
         {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        }
+            if (dictionary is null)
+                throw new ArgumentNullException(nameof(dictionary));
 
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        [CLSCompliant(false)]
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, sbyte key, out TValue value)
-        {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
+            return new CharArrayDictionary<TValue>(matchVersion, dictionary.Count, ignoreCase);
         }
+    }
 
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, short key, out TValue value)
-        {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        }
+    /// <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.";
 
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        [CLSCompliant(false)]
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, uint key, out TValue value)
-        {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        }
+        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.";
 
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        [CLSCompliant(false)]
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, ulong key, out TValue value)
-        {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        }
+        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.";
 
-        /// <summary>
-        /// Gets the value associated with the specified key.
-        /// </summary>
-        /// <param name="map">this map</param>
-        /// <param name="key">The key of the value to get.</param>
-        /// <param name="value">When this method returns, contains the value associated with the specified key, 
-        /// if the key 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="CharArrayMap{TValue}"/> contains an element with the specified key; otherwise, <c>false</c>.</returns>
-        [CLSCompliant(false)]
-        public static bool TryGetValue<TValue>(this CharArrayMap<TValue> map, ushort key, out TValue value)
-        {
-            return map.TryGetValue(key.ToString(CultureInfo.InvariantCulture), out value);
-        }
+        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.";
 
-#endregion
+        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.";
     }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArraySet.cs b/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArraySet.cs
index 538fca235..d2b1009de 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArraySet.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Util/CharArraySet.cs
@@ -1,16 +1,18 @@
 // Lucene version compatibility level 4.8.1
-using J2N.Globalization;
 using J2N.Text;
+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.Linq;
 using System.Text;
 using JCG = J2N.Collections.Generic;
+#nullable enable
 
 namespace Lucene.Net.Analysis.Util
 {
@@ -63,51 +65,146 @@ namespace Lucene.Net.Analysis.Util
     /// The <see cref="GetEnumerator()"/> returns an <see cref="T:IEnumerator{char[]}"/>
     /// </para>
     /// </summary>
-    public class CharArraySet : ISet<string>
+    [DebuggerDisplay("Count = {Count}, Values = {ToString()}")]
+    public class CharArraySet : ISet<string>, ICollection<string>, ICollection, IReadOnlyCollection<string>
+#if FEATURE_READONLYSET
+        , IReadOnlySet<string>
+#endif
     {
         [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 CharArraySet EMPTY_SET = new CharArraySet(CharArrayMap<string>.EmptyMap());
-        // LUCENENET: PLACEHOLDER moved to CharArrayMap
+        public static readonly CharArraySet Empty = new CharArraySet(CharArrayDictionary<object>.Empty);
 
-        internal readonly ICharArrayMap map;
+        [Obsolete("Use Empty instead. This field will be removed in 4.8.0 release candidate."), EditorBrowsable(EditorBrowsableState.Never)]
+        public static CharArraySet EMPTY_SET => Empty;
+
+        // LUCENENET: PLACEHOLDER moved to CharArrayDictionary
+
+        internal readonly ICharArrayDictionary map;
+
+        private const int DefaultSetSize = 8; // LUCENENET specific
 
         /// <summary>
-        /// Create set with enough capacity to hold <paramref name="startSize"/> terms
+        /// Create set with enough capacity to hold <paramref name="capacity"/> terms
         /// </summary>
         /// <param name="matchVersion">
         ///          compatibility match version see <see cref="CharArraySet"/> for details. </param>
-        /// <param name="startSize">
+        /// <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>
-        public CharArraySet(LuceneVersion matchVersion, int startSize, bool ignoreCase)
-            : this(new CharArrayMap<object>(matchVersion, startSize, ignoreCase))
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than zero.</exception>
+        public CharArraySet(LuceneVersion matchVersion, int capacity, bool ignoreCase)
+            : this(new CharArrayDictionary<object>(matchVersion, capacity, ignoreCase))
         {
         }
 
         /// <summary>
-        /// Creates a set from a collection of objects. 
+        /// Creates a set from a collection of <see cref="string"/>s. 
         /// </summary>
         /// <param name="matchVersion">
-        ///          compatibility match version see <see cref="CharArraySet"/> for details. </param>
-        /// <param name="c">
-        ///          a collection whose elements to be placed into the set </param>
+        ///          Compatibility match version see <see cref="CharArraySet"/> for details. </param>
+        /// <param name="collection">
+        ///          A collection whose elements to be placed into the set. </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>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// A given element within the <paramref name="collection"/> is <c>null</c>.
+        /// </exception>
+        public CharArraySet(LuceneVersion matchVersion, IEnumerable<string> collection, bool ignoreCase)
+            : this(matchVersion, collection is ICollection<string> c ? c.Count : DefaultSetSize, ignoreCase)
+        {
+            // LUCENENET: Added guard clause
+            if (collection is null)
+                throw new ArgumentNullException(nameof(collection));
+
+            foreach (string text in collection)
+            {
+                // LUCENENET: S1699: Don't call call protected members in the constructor
+                map.Set(text);
+            }
+        }
+
+        /// <summary>
+        /// Creates a set from a collection of <see cref="T:char[]"/>s.
+        /// <para/>
+        /// <b>NOTE:</b> If <paramref name="ignoreCase"/> is <c>true</c>, the text arrays will be directly modified.
+        /// The user should never modify these text arrays after calling this method.
+        /// </summary>
+        /// <param name="matchVersion">
+        ///          Compatibility match version see <see cref="CharArraySet"/> for details. </param>
+        /// <param name="collection">
+        ///          A collection whose elements to be placed into the set. </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>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// A given element within the <paramref name="collection"/> is <c>null</c>.
+        /// </exception>
+        public CharArraySet(LuceneVersion matchVersion, IEnumerable<char[]> collection, bool ignoreCase)
+            : this(matchVersion, collection is ICollection<char[]> c ? c.Count : DefaultSetSize, ignoreCase)
+        {
+            // LUCENENET: Added guard clause
+            if (collection is null)
+                throw new ArgumentNullException(nameof(collection));
+
+            foreach (char[] text in collection)
+            {
+                // LUCENENET: S1699: Don't call call protected members in the constructor
+                map.Set(text);
+            }
+        }
+
+        /// <summary>
+        /// Creates a set from a collection of <see cref="ICharSequence"/>s. 
+        /// </summary>
+        /// <param name="matchVersion">
+        ///          Compatibility match version see <see cref="CharArraySet"/> for details. </param>
+        /// <param name="collection">
+        ///          A collection whose elements to be placed into the set. </param>
         /// <param name="ignoreCase">
         ///          <c>false</c> if and only if the set should be case sensitive
         ///          otherwise <c>true</c>. </param>
-        public CharArraySet(LuceneVersion matchVersion, ICollection<string> c, bool ignoreCase)
-            : this(matchVersion, c.Count, ignoreCase)
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="collection"/> is <c>null</c>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// A given element within the <paramref name="collection"/> is <c>null</c>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// The <see cref="ICharSequence.HasValue"/> property for a given element in the <paramref name="collection"/> returns <c>false</c>.
+        /// </exception>
+        public CharArraySet(LuceneVersion matchVersion, IEnumerable<ICharSequence> collection, bool ignoreCase)
+            : this(matchVersion, collection is ICollection<ICharSequence> c ? c.Count : DefaultSetSize, ignoreCase)
         {
-            this.UnionWith(c);
+            // LUCENENET: Added guard clause
+            if (collection is null)
+                throw new ArgumentNullException(nameof(collection));
+
+            foreach (ICharSequence text in collection)
+            {
+                // LUCENENET: S1699: Don't call call protected members in the constructor
+                map.Set(text);
+            }
         }
 
         /// <summary>
-        /// Create set from the specified map (internal only), used also by <see cref="CharArrayMap{TValue}.Keys"/>
+        /// Create set from the specified map (internal only), used also by <see cref="CharArrayDictionary{TValue}.Keys"/>
         /// </summary>
-        internal CharArraySet(ICharArrayMap map)
+        internal CharArraySet(ICharArrayDictionary map)
         {
             this.map = map;
         }
@@ -121,97 +218,135 @@ namespace Lucene.Net.Analysis.Util
         }
 
         /// <summary>
-        /// <c>true</c> if the <paramref name="length"/> chars of <paramref name="text"/> starting at <paramref name="offset"/>
-        /// are in the set 
+        /// <c>true</c> if the <paramref name="length"/> chars of <paramref name="text"/> starting at <paramref name="startIndex"/>
+        /// are in the set.
         /// </summary>
-        public virtual bool Contains(char[] text, int offset, int length)
+        /// <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 Contains(char[] text, int startIndex, int length)
         {
-            return map.ContainsKey(text, offset, length);
+            return map.ContainsKey(text, startIndex, length);
         }
 
         /// <summary>
         /// <c>true</c> if the <see cref="T:char[]"/>s 
         /// are in the set 
         /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
         public virtual bool Contains(char[] text)
         {
+            if (text is null)
+                throw new ArgumentNullException(nameof(text));
+
             return map.ContainsKey(text, 0, text.Length);
         }
 
         /// <summary>
-        /// <c>true</c> if the <see cref="ICharSequence"/> is in the set
+        /// <c>true</c> if the <see cref="ICharSequence"/> is in the set.
         /// </summary>
-        public virtual bool Contains(ICharSequence cs)
+        /// <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 Contains(ICharSequence text)
         {
-            return map.ContainsKey(cs);
+            return map.ContainsKey(text);
         }
 
         /// <summary>
-        /// <c>true</c> if the <see cref="string"/> is in the set
+        /// <c>true</c> if the <see cref="string"/> is in the set.
         /// </summary>
-        public virtual bool Contains(string cs)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        public virtual bool Contains(string text)
         {
-            return map.ContainsKey(cs);
+            return map.ContainsKey(text);
         }
 
         /// <summary>
-        /// <c>true</c> if the <see cref="object.ToString()"/> representation of <paramref name="o"/> is in the set
+        /// <c>true</c> if the <see cref="object.ToString()"/> representation of <paramref name="text"/> is in the set.
         /// </summary>
-        public virtual bool Contains(object o)
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        public virtual bool Contains<T>(T text)
         {
-            return map.ContainsKey(o);
+            return map.ContainsKey(text);
         }
 
         /// <summary>
-        /// Add the <see cref="object.ToString()"/> representation of <paramref name="o"/> into the set.
+        /// Adds the <see cref="object.ToString()"/> representation of <paramref name="text"/> into the set.
         /// The <see cref="object.ToString()"/> method is called after setting the thread to <see cref="CultureInfo.InvariantCulture"/>.
-        /// If the type of <paramref name="o"/> is a value type, it will be converted using the 
+        /// If the type of <paramref name="text"/> is a value type, it will be converted using the 
         /// <see cref="CultureInfo.InvariantCulture"/>.
         /// </summary>
-        /// <param name="o">A string-able object</param>
-        /// <returns><c>true</c> if <paramref name="o"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        public virtual bool Add(object o)
+        /// <param name="text">A string-able object.</param>
+        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
+        public virtual bool Add<T>(T text)
         {
-            return map.Put(o);
+            return map.Put(text);
         }
 
         /// <summary>
-        /// Add this <see cref="ICharSequence"/> into the set
+        /// Adds a <see cref="ICharSequence"/> into the set
         /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
+        /// <param name="text">The text to be added to the set.</param>
+        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
         public virtual bool Add(ICharSequence text)
         {
             return map.Put(text);
         }
 
         /// <summary>
-        /// Add this <see cref="string"/> into the set
+        /// Adds a <see cref="string"/> into the set
         /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
+        /// <param name="text">The text to be added to the set.</param>
+        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
         public virtual bool Add(string text)
         {
             return map.Put(text);
         }
 
         /// <summary>
-        /// Add this <see cref="T:char[]"/> directly to the set.
-        /// If <c>ignoreCase</c> is true for this <see cref="CharArraySet"/>, the text array will be directly modified.
+        /// Adds a <see cref="T:char[]"/> directly to the set.
+        /// <para/>
+        /// <b>NOTE:</b> If <c>ignoreCase</c> is <c>true</c> for this <see cref="CharArraySet"/>, 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 <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
+        /// <param name="text">The text to be added to the set.</param>
+        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is <c>null</c>.</exception>
         public virtual bool Add(char[] text)
         {
             return map.Put(text);
         }
 
         /// <summary>
-        /// LUCENENET specific for supporting <see cref="ICollection{T}"/>.
+        /// Adds a <see cref="T:char[]"/> to the set using the specified <paramref name="startIndex"/> and <paramref name="length"/>.
+        /// <para/>
+        /// <b>NOTE:</b> If <c>ignoreCase</c> is <c>true</c> for this <see cref="CharArraySet"/>, the text array will be directly modified.
         /// </summary>
-        void ICollection<string>.Add(string item)
+        /// <param name="text">The text to be added to the 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>
+        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call.</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 Add(char[] text, int startIndex, int length)
         {
-            Add(item);
+            return map.Put(text, startIndex, length);
         }
 
+        /// <summary>
+        /// LUCENENET specific for supporting <see cref="ICollection{T}"/>.
+        /// </summary>
+        void ICollection<string>.Add(string item) => Add(item);
+
         /// <summary>
         /// Gets the number of elements contained in the <see cref="CharArraySet"/>.
         /// </summary>
@@ -220,7 +355,13 @@ namespace Lucene.Net.Analysis.Util
         /// <summary>
         /// <c>true</c> if the <see cref="CharArraySet"/> is read-only; otherwise <c>false</c>.
         /// </summary>
-        public virtual bool IsReadOnly { get; private set; }
+        public virtual bool IsReadOnly => map.IsReadOnly;
+
+        bool ICollection<string>.IsReadOnly => map.IsReadOnly;
+
+        bool ICollection.IsSynchronized => false;
+
+        object ICollection.SyncRoot => this;
 
         /// <summary>
         /// Returns an unmodifiable <see cref="CharArraySet"/>. This allows to provide
@@ -236,17 +377,17 @@ namespace Lucene.Net.Analysis.Util
         {
             if (set is null)
             {
-                throw new ArgumentNullException(nameof(set), "Given set is null"); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
+                throw new ArgumentNullException(nameof(set)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
             }
-            if (set == EMPTY_SET)
+            if (set == Empty)
             {
-                return EMPTY_SET;
+                return Empty;
             }
-            if (set.map is CharArrayMap.UnmodifiableCharArrayMap<object>)
+            if (set.map is CharArrayDictionary.ReadOnlyCharArrayDictionary<object>)
             {
                 return set;
             }
-            return new CharArraySet(CharArrayMap.UnmodifiableMap<object>(set.map));
+            return new CharArraySet(CharArrayDictionary.UnmodifiableMap<object>(set.map));
         }
 
         /// <summary>
@@ -257,15 +398,67 @@ namespace Lucene.Net.Analysis.Util
         // LUCENENET specific - allow .NET-like syntax for creating immutable collections
         public virtual CharArraySet AsReadOnly()
         {
-            if (this == EMPTY_SET)
+            if (this == Empty)
             {
-                return EMPTY_SET;
+                return Empty;
             }
-            if (this.map is CharArrayMap.UnmodifiableCharArrayMap<object>)
+            if (this.map is CharArrayDictionary.ReadOnlyCharArrayDictionary<object>)
             {
                 return this;
             }
-            return new CharArraySet(CharArrayMap.UnmodifiableMap<object>(this.map));
+            return new CharArraySet(CharArrayDictionary.UnmodifiableMap<object>(this.map));
+        }
+
+        /// <summary>
+        /// Returns a copy of this set as a new instance <see cref="CharArraySet"/>.
+        /// The <see cref="LuceneVersion"/> and <c>ignoreCase</c> property will be preserved.
+        /// </summary>
+        /// <returns>A copy of this set as a new instance of <see cref="CharArraySet"/>.
+        ///         The <see cref="CharArrayDictionary{TValue}.ignoreCase"/> field as well as the
+        ///         <see cref="CharArrayDictionary{TValue}.MatchVersion"/> will be preserved.</returns>
+        // LUCENENET specific - allow .NET-like syntax for copying CharArraySet
+        public virtual CharArraySet ToCharArraySet()
+        {
+            if (this == Empty)
+            {
+                return Empty;
+            }
+
+            return new CharArraySet(CharArrayDictionary.Copy<object>(this.map.MatchVersion, this.map));
+        }
+
+        /// <summary>
+        /// Returns a copy of this set as a new instance <see cref="CharArraySet"/>
+        /// with the provided <paramref name="matchVersion"/>.
+        /// The <c>ignoreCase</c> property will be preserved from this <see cref="CharArraySet"/>.
+        /// </summary>
+        /// <returns>A copy of this set as a new instance of <see cref="CharArraySet"/>.
+        ///         The <see cref="CharArrayDictionary{TValue}.ignoreCase"/> field will be preserved.</returns>
+        // LUCENENET specific - allow .NET-like syntax for copying CharArraySet
+        public virtual CharArraySet ToCharArraySet(LuceneVersion matchVersion)
+        {
+            if (this == Empty)
+            {
+                return Empty;
+            }
+
+            return new CharArraySet(new CharArrayDictionary<object>(matchVersion, (IDictionary<string, object>)this.map, this.map.IgnoreCase));
+        }
+
+        /// <summary>
+        /// Returns a copy of this set as a new instance <see cref="CharArraySet"/>
+        /// with the provided <paramref name="matchVersion"/> and <paramref name="ignoreCase"/> values.
+        /// </summary>
+        /// <returns>A copy of this set as a new instance of <see cref="CharArraySet"/>.</returns>
+        // LUCENENET specific - allow .NET-like syntax for copying CharArraySet
+        public virtual CharArraySet ToCharArraySet(LuceneVersion matchVersion, bool ignoreCase)
+        {
+            if (this == Empty)
+            {
+                return Empty;
+            }
+
+            return new CharArraySet(new CharArrayDictionary<object>(matchVersion, (IDictionary<string, object>)this.map, ignoreCase));
         }
 
         /// <summary>
@@ -274,73 +467,250 @@ namespace Lucene.Net.Analysis.Util
         /// <para>
         /// <b>Note:</b> If you intend to create a copy of another <see cref="CharArraySet"/> where
         /// the <see cref="LuceneVersion"/> of the source set differs from its copy
-        /// <see cref="CharArraySet.CharArraySet(LuceneVersion, ICollection{string}, bool)"/> should be used instead.
-        /// The <see cref="Copy{T}(LuceneVersion, ICollection{T})"/> will preserve the <see cref="LuceneVersion"/> of the
+        /// <see cref="CharArraySet.CharArraySet(LuceneVersion, IEnumerable{string}, bool)"/> should be used instead.
+        /// The <see cref="Copy{T}(LuceneVersion, IEnumerable{T})"/> method will preserve the <see cref="LuceneVersion"/> of the
         /// source set it is an instance of <see cref="CharArraySet"/>.
         /// </para>
         /// </summary>
         /// <param name="matchVersion">
         ///          compatibility match version. This argument will be ignored if the
         ///          given set is a <see cref="CharArraySet"/>. </param>
-        /// <param name="set">
+        /// <param name="collection">
         ///          a set to copy </param>
-        /// <returns> a copy of the given set as a <see cref="CharArraySet"/>. If the given set
-        ///         is a <see cref="CharArraySet"/> the <see cref="CharArrayMap{TValue}.ignoreCase"/> field as well as the
-        ///         <see cref="CharArrayMap{TValue}.MatchVersion"/> will be preserved. </returns>
-        public static CharArraySet Copy<T>(LuceneVersion matchVersion, ICollection<T> set)
+        /// <returns> A copy of the given set as a <see cref="CharArraySet"/>. If the given set
+        ///         is a <see cref="CharArraySet"/> the <see cref="CharArrayDictionary{TValue}.ignoreCase"/> field as well as the
+        ///         <see cref="CharArrayDictionary{TValue}.MatchVersion"/> will be preserved. </returns>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="collection"/> is <c>null</c>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// A given element within the <paramref name="collection"/> is <c>null</c>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// The <see cref="ICharSequence.HasValue"/> property for a given element in the <paramref name="collection"/> returns <c>false</c>.
+        /// </exception>
+        public static CharArraySet Copy<T>(LuceneVersion matchVersion, IEnumerable<T> collection)
         {
-            if (set == EMPTY_SET)
+            if (collection is null)
+                throw new ArgumentNullException(nameof(collection));
+
+            if (collection == Empty)
             {
-                return EMPTY_SET;
+                return Empty;
             }
 
             // 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 (set is CharArraySet)
+            if (collection is CharArraySet source)
             {
-                var source = set as CharArraySet;
-                return new CharArraySet(CharArrayMap.Copy<object>(source.map.MatchVersion, source.map));
+                return new CharArraySet(CharArrayDictionary.Copy<object>(source.map.MatchVersion, source.map));
             }
 
-            // Convert the elements in the collection to string in the invariant context.
-            string[] stringSet;
-            using (var context = new CultureContext(CultureInfo.InvariantCulture))
+            return CopySet(matchVersion, collection, ignoreCase: false);
+        }
+
+        internal static CharArraySet CopySet<T>(LuceneVersion matchVersion, IEnumerable<T> collection, bool ignoreCase)
+        {
+            if (collection is null)
+                throw new ArgumentNullException(nameof(collection));
+
+            if (collection is IEnumerable<string> stringCollection)
+            {
+                return new CharArraySet(matchVersion, stringCollection, ignoreCase);
+            }
+            else if (collection is IEnumerable<char[]> charArrayCollection)
             {
-                stringSet = set.Select(x => x.ToString()).ToArray(); // LUCENENET TODO: Performance - this approach can probably be improved
+                return new CharArraySet(matchVersion, charArrayCollection, ignoreCase);
+            }
+            else if (collection is IEnumerable<ICharSequence> charSequenceCollection)
+            {
+                return new CharArraySet(matchVersion, charSequenceCollection, ignoreCase);
             }
 
-            return new CharArraySet(matchVersion, stringSet, false);
+            return new CharArraySet(matchVersion, collection.Select(text =>
+            {
+                // We cannot capture Span<T> from outside of the lambda, so we just re-alocate the
+                // stack on every loop.
+                var returnType = CharArrayDictionary.ConvertObjectToChars(text, out char[] chars, out string s);
+                if (returnType == CharArrayDictionary.CharReturnType.String)
+                    return s.ToCharArray();
+                else
+                    return chars;
+            }), ignoreCase);
         }
 
         /// <summary>
-        /// Returns an <see cref="IEnumerator"/> for <see cref="T:char[]"/> instances in this set.
+        /// Returns an enumerator that iterates through the <see cref="CharArraySet"/>.
         /// </summary>
-        public virtual IEnumerator GetEnumerator()
+        /// <returns>An enumerator that iterates through the <see cref="CharArraySet"/>.</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()
         {
-            // use the OriginalKeySet's enumerator (to not produce endless recursion)
-            return map.OriginalKeySet.GetEnumerator();
+            // LUCENENET specific - Use custom Enumerator to prevent endless recursion
+            return new Enumerator(map);
         }
 
-        IEnumerator<string> IEnumerable<string>.GetEnumerator()
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+        IEnumerator<string> IEnumerable<string>.GetEnumerator() => GetEnumerator();
+
+        #region Nested Struct: Enumerator
+
+        /// <summary>
+        /// Enumerates the elements of a <see cref="CharArraySet"/> object.
+        /// <para/>
+        /// This implementation provides direct access to the <see cref="T:char[]"/> array of the underlying collection
+        /// as well as convenience properties for converting to <see cref="string"/> and <see cref="ICharSequence"/>.
+        /// </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 false,
+        /// <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.
+        /// <para/>
+        /// This method is an O(1) operation.
+        /// </remarks>
+        //  LUCENENET specific.
+        public readonly struct Enumerator : IEnumerator<string>, IEnumerator
         {
-            // use the OriginalKeySet's enumerator (to not produce endless recursion)
-            return (IEnumerator<string>)map.OriginalKeySet.GetEnumerator();
+            private readonly ICharArrayDictionaryEnumerator enumerator;
+
+            internal Enumerator(ICharArrayDictionary map)
+            {
+                this.enumerator = map.GetEnumerator();
+            }
+
+            /// <summary>
+            /// Gets the current value as a <see cref="CharArrayCharSequence"/>.
+            /// </summary>
+            // LUCENENET specific - quick access to ICharSequence interface
+            public ICharSequence CurrentValueCharSequence
+                => enumerator.CurrentKeyCharSequence;
+
+            /// <summary>
+            /// Gets the current value... do not modify the returned char[].
+            /// </summary>
+            [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
+            [WritableArray]
+            public char[] CurrentValue => enumerator.CurrentKey;
+
+            /// <summary>
+            /// Gets the current value as a newly created <see cref="string"/> object.
+            /// </summary>
+            public string Current => enumerator.CurrentKeyString;
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    if (enumerator.NotStartedOrEnded)
+                        throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
+
+                    return Current;
+                }
+            }
+
+            /// <summary>
+            /// Releases all resources used by the <see cref="Enumerator"/>.
+            /// </summary>
+            public void Dispose()
+            {
+                enumerator.Dispose();
+            }
+
+            /// <summary>
+            /// Advances the enumerator to the next element of the <see cref="CharArraySet"/>.
+            /// </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()
+            {
+                return enumerator.MoveNext();
+            }
+
+            void IEnumerator.Reset() => enumerator.Reset();
         }
 
+        #endregion Nested Struct: Enumerator
+
         /// <summary>
-        /// Returns a string that represents the current object. (Inherited from <see cref="object"/>.)
+        /// Returns a string that represents the current collection.
+        /// <para/>
+        /// The presentation has a specific format. It is enclosed by curly
+        /// brackets ("{}"). Keys and values are separated by '=',
+        /// KeyValuePairs 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()
         {
+            if (Count == 0)
+                return "[]";
+
             var sb = new StringBuilder("[");
-            foreach (var item in this)
+            using var iter = GetEnumerator();
+            while (iter.MoveNext())
             {
                 if (sb.Length > 1)
                 {
                     sb.Append(", ");
                 }
-                sb.Append(item);
+                var currentValue = iter.CurrentValue; // LUCENENET specific - avoid string allocations by using iter.CurrentValue instead of iter.Current
+                if (currentValue is not null)
+                    sb.Append(currentValue);
+                else
+                    sb.Append("null");
             }
             return sb.Append(']').ToString();
         }
@@ -361,8 +731,10 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="obj">object to be compared for equality with this set</param>
         /// <returns><c>true</c> if the specified object is equal to this set</returns>
-        public override bool Equals(object obj)
+        public override bool Equals(object? obj)
         {
+            if (obj is null)
+                return false;
             if (obj is ISet<string> other)
                 return JCG.SetEqualityComparer<string>.Default.Equals(this, other);
             return false;
@@ -389,190 +761,551 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="array">The one-dimensional <see cref="T:string[]"/> Array that is the destination of the 
         /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
-        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
-        public void CopyTo(string[] array, int arrayIndex)
+        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
+        /// <exception cref="ArgumentException">The number of elements in the source is greater
+        /// than the available space in the destination array.</exception>
+        public void CopyTo(string[] array)
+        {
+            CopyTo(array, 0, map.Count);
+        }
+
+        /// <summary>
+        /// Copies the entire <see cref="CharArraySet"/> to a one-dimensional <see cref="T:string[]"/> array, 
+        /// starting at the specified index of the target array.
+        /// </summary>
+        /// <param name="array">The one-dimensional <see cref="T:string[]"/> Array that is the destination of the 
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <param name="index">The zero-based index in 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>
+        public void CopyTo(string[] array, int index)
+        {
+            CopyTo(array, index, map.Count);
+        }
+
+        /// <summary>
+        /// Copies the entire <see cref="CharArraySet"/> to a one-dimensional <see cref="T:string[]"/> array, 
+        /// starting at the specified index of the target array.
+        /// </summary>
+        /// <param name="array">The one-dimensional <see cref="T:string[]"/> Array that is the destination of the 
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <param name="index">The zero-based index in array at which copying begins.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> or <paramref name="count"/> is less than zero.</exception>
+        /// <exception cref="ArgumentException">
+        /// <paramref name="index"/> is greater than the length of the destination <paramref name="array"/>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// <paramref name="count"/> is greater than the available space from the <paramref name="index"/>
+        /// to the end of the destination <paramref name="array"/>.
+        /// </exception>
+        internal void CopyTo(string[] array, int index, int count)
         {
-            using (var iter = map.OriginalKeySet.GetEnumerator())
+            if (array is null)
+                throw new ArgumentNullException(nameof(array));
+            if (index < 0)
+                throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count), count, SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (index > array.Length || count > array.Length - index)
+                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+
+            using var iter = GetEnumerator();
+            for (int i = index, numCopied = 0; numCopied < count && iter.MoveNext(); i++, numCopied++)
             {
-                for (int i = arrayIndex; iter.MoveNext(); i++)
-                {
-                    array[i] = iter.Current;
-                }
+                array[i] = iter.Current;
             }
         }
 
-        [Obsolete("Not applicable in this class.")]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public virtual bool Remove(string item) // LUCENENET TODO: API - make an explicit implementation that isn't public
+        /// <summary>
+        /// Copies the entire <see cref="CharArraySet"/> to a jagged <see cref="T:char[][]"/> array or <see cref="IList{T}"/> of type char[],
+        /// starting at the specified index of the target array.
+        /// </summary>
+        /// <param name="array">The jagged <see cref="T:char[][]"/> array or <see cref="IList{T}"/> of type char[] that is the destination of the
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
+        /// <exception cref="ArgumentException">The number of elements in the source is greater
+        /// than the available space in the destination array.</exception>
+        public void CopyTo(IList<char[]> array)
         {
-            // LUCENENET NOTE: According to the documentation header, Remove should not be supported
-            throw UnsupportedOperationException.Create();
+            CopyTo(array, 0, map.Count);
         }
 
-        // LUCENENET - Added to ensure equality checking works in tests
         /// <summary>
-        /// Determines whether the current set and the specified collection contain the same elements.
+        /// Copies the entire <see cref="CharArraySet"/> to a jagged <see cref="T:char[][]"/> array or <see cref="IList{T}"/> of type char[]
+        /// starting at the specified index of the target array.
         /// </summary>
-        /// <param name="other">The collection to compare to the current set.</param>
-        /// <returns><c>true</c> if the current set is equal to other; otherwise, <c>false</c>.</returns>
-        public virtual bool SetEquals(IEnumerable<string> other)
+        /// <param name="array">The jagged <see cref="T:char[][]"/> array or <see cref="IList{T}"/> of type char[] that is the destination of the
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <param name="index">The zero-based index in 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>
+        public void CopyTo(IList<char[]> array, int index)
         {
-            if (!(other is CharArraySet otherSet))
-                return false;
-
-            // Invoke the implementation on CharArrayMap that
-            // tests the dictionaries to ensure they contain
-            // the same keys and values.
-            return this.map.Equals(otherSet.map);
+            CopyTo(array, index, map.Count);
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Copies the entire <see cref="CharArraySet"/> to a jagged <see cref="T:char[][]"/> array or <see cref="IList{T}"/> of type char[]
+        /// starting at the specified index of the target array.
         /// </summary>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public virtual bool UnionWith(IEnumerable<char[]> other)
+        /// <param name="array">The jagged <see cref="T:char[][]"/> array or <see cref="IList{T}"/> of type char[] that is the destination of the
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <param name="index">The zero-based index in array at which copying begins.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> or <paramref name="count"/> is less than zero.</exception>
+        /// <exception cref="ArgumentException">
+        /// <paramref name="index"/> is greater than the length of the destination <paramref name="array"/>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// <paramref name="count"/> is greater than the available space from the <paramref name="index"/>
+        /// to the end of the destination <paramref name="array"/>.
+        /// </exception>
+        internal void CopyTo(IList<char[]> array, int index, int count)
         {
-            if (other is null)
-            {
-                throw new ArgumentNullException(nameof(other));
-            }
-            if (IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+            if (array is null)
+                throw new ArgumentNullException(nameof(array));
+            if (index < 0)
+                throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count), count, SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (index > array.Count || count > array.Count - index)
+                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+
+            using var iter = GetEnumerator();
+            for (int i = index, numCopied = 0; numCopied < count && iter.MoveNext(); i++, numCopied++)
             {
-                if (Add(item))
-                {
-                    modified = true;
-                }
+                array[i] = (char[])iter.CurrentValue.Clone();
             }
-            return modified;
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Copies the entire <see cref="CharArraySet"/> to a one-dimensional <see cref="T:ICharSequence[]"/> array, 
+        /// starting at the specified index of the target array.
         /// </summary>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public virtual bool UnionWith(IEnumerable<ICharSequence> other)
+        /// <param name="array">The one-dimensional <see cref="T:ICharSequence[]"/> Array that is the destination of the 
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
+        /// <exception cref="ArgumentException">The number of elements in the source is greater
+        /// than the available space in the destination array.</exception>
+        public void CopyTo(ICharSequence[] array)
         {
-            if (other is null)
-            {
-                throw new ArgumentNullException(nameof(other));
-            }
-            if (IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
-            {
-                if (Add(item))
-                {
-                    modified = true;
-                }
-            }
-            return modified;
+            CopyTo(array, 0, map.Count);
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Copies the entire <see cref="CharArraySet"/> to a one-dimensional <see cref="T:ICharSequence[]"/> array, 
+        /// starting at the specified index of the target array.
         /// </summary>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        public virtual void UnionWith(IEnumerable<string> other)
+        /// <param name="array">The one-dimensional <see cref="T:ICharSequence[]"/> Array that is the destination of the 
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <param name="index">The zero-based index in 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>
+        public void CopyTo(ICharSequence[] array, int index)
         {
-            if (other is null)
+            CopyTo(array, index, map.Count);
+        }
+
+        /// <summary>
+        /// Copies the entire <see cref="CharArraySet"/> to a one-dimensional <see cref="T:ICharSequence[]"/> array, 
+        /// starting at the specified index of the target array.
+        /// </summary>
+        /// <param name="array">The one-dimensional <see cref="T:ICharSequence[]"/> Array that is the destination of the 
+        /// elements copied from <see cref="CharArraySet"/>. The Array must have zero-based indexing.</param>
+        /// <param name="index">The zero-based index in array at which copying begins.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="array"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> or <paramref name="count"/> is less than zero.</exception>
+        /// <exception cref="ArgumentException">
+        /// <paramref name="index"/> is greater than the length of the destination <paramref name="array"/>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// <paramref name="count"/> is greater than the available space from the <paramref name="index"/>
+        /// to the end of the destination <paramref name="array"/>.
+        /// </exception>
+        internal void CopyTo(ICharSequence[] array, int index, int count)
+        {
+            if (array is null)
+                throw new ArgumentNullException(nameof(array));
+            if (index < 0)
+                throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count), count, SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (index > array.Length || count > array.Length - index)
+                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
+
+            using var iter = GetEnumerator();
+            for (int i = index, numCopied = 0; numCopied < count && iter.MoveNext(); i++, numCopied++)
             {
-                throw new ArgumentNullException(nameof(other));
+                array[i] = ((char[])iter.CurrentValue.Clone()).AsCharSequence();
             }
-            if (IsReadOnly)
+        }
+
+        [SuppressMessage("Style", "IDE0019: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, nameof(array));
+
+            if (array.GetLowerBound(0) != 0)
             {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
+                throw new ArgumentException(SR.Arg_NonZeroLowerBound, nameof(array));
             }
-            foreach (var item in other)
+
+            if (index < 0)
             {
-                Add(item);
+                throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
             }
-        }
 
-        /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
-        /// </summary>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public virtual bool UnionWith<T>(IEnumerable<T> other)
-        {
-            if (other is null)
+            if (array.Length - index < Count)
             {
-                throw new ArgumentNullException(nameof(other));
+                throw new ArgumentException(SR.Arg_ArrayPlusOffTooSmall);
             }
-            if (IsReadOnly)
+
+            if (array is string[] strings)
             {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
+                CopyTo(strings, index);
             }
-            bool modified = false;
-            foreach (var item in other)
+            else if (array is IList<char[]> chars)
             {
-                if (item is char[])
-                {
-                    if (Add(item as char[]))
-                    {
-                        modified = true;
-                    }
-                    continue;
+                CopyTo(chars, index);
+            }
+            else if (array is ICharSequence[] charSequences)
+            {
+                CopyTo(charSequences, index);
+            }
+            else
+            {
+                object?[]? objects = array as object[];
+                if (objects == null)
+                {
+                    throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
+                }
+
+                try
+                {
+
+                    foreach (var entry in this)
+                        objects[index++] = entry;
+                }
+                catch (ArrayTypeMismatchException)
+                {
+                    throw new ArgumentException(SR.Argument_InvalidArrayType, nameof(array));
+                }
+            }
+        }
+
+        bool ICollection<string>.Remove(string item)
+        {
+            // LUCENENET NOTE: According to the documentation header, Remove should not be supported
+            throw UnsupportedOperationException.Create();
+        }
+
+        // LUCENENET - Added to ensure equality checking works in tests
+        /// <summary>
+        /// Determines whether the current set and the specified collection contain the same elements.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current set.</param>
+        /// <returns><c>true</c> if the current set is equal to other; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool SetEquals(IEnumerable<string> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            if (other is CharArraySet charArraySet)
+                return this.map.Equals(charArraySet.map);
+
+            if (other is ICollection<string> otherAsCollection)
+            {
+                if (this.Count != otherAsCollection.Count)
+                    return false;
+
+                // already confirmed that the sets have the same number of distinct elements, so if
+                // one is a superset of the other then they must be equal
+                return ContainsAllElements(otherAsCollection);
+            }
+
+            int otherCount = 0;
+            foreach (var local in other)
+            {
+                if (local is not null && !this.Contains(local))
+                {
+                    return false;
+                }
+                otherCount++;
+            }
+            return this.Count == otherCount;
+        }
+
+        // LUCENENET - Added to ensure equality checking works in tests
+        /// <summary>
+        /// Determines whether the current set and the specified collection contain the same elements.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current set.</param>
+        /// <returns><c>true</c> if the current set is equal to other; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool SetEquals(IEnumerable<char[]> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            if (other is CharArraySet charArraySet)
+                return this.map.Equals(charArraySet.map);
+
+            if (other is ICollection<char[]> otherAsCollection)
+            {
+                if (this.Count != otherAsCollection.Count)
+                    return false;
+
+                // already confirmed that the sets have the same number of distinct elements, so if
+                // one is a superset of the other then they must be equal
+                return ContainsAllElements(otherAsCollection);
+            }
+
+            int otherCount = 0;
+            foreach (var local in other)
+            {
+                if (local is not null && !this.Contains(local))
+                {
+                    return false;
+                }
+                otherCount++;
+            }
+            return this.Count == otherCount;
+        }
+
+        // LUCENENET - Added to ensure equality checking works in tests
+        /// <summary>
+        /// Determines whether the current set and the specified collection contain the same elements.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current set.</param>
+        /// <returns><c>true</c> if the current set is equal to other; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool SetEquals(IEnumerable<ICharSequence> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            if (other is CharArraySet charArraySet)
+                return this.map.Equals(charArraySet.map);
+
+            if (other is ICollection<ICharSequence> otherAsCollection)
+            {
+                if (this.Count != otherAsCollection.Count)
+                    return false;
+
+                // already confirmed that the sets have the same number of distinct elements, so if
+                // one is a superset of the other then they must be equal
+                return ContainsAllElements(otherAsCollection);
+            }
+
+            int otherCount = 0;
+            foreach (var local in other)
+            {
+                if (local is null || !local.HasValue || !this.Contains(local))
+                {
+                    return false;
                 }
+                otherCount++;
+            }
+            return this.Count == otherCount;
+        }
+
+        // LUCENENET - Added to ensure equality checking works in tests
+        /// <summary>
+        /// Determines whether the current set and the specified collection contain the same elements.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current set.</param>
+        /// <returns><c>true</c> if the current set is equal to other; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool SetEquals<T>(IEnumerable<T> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            if (other is CharArraySet charArraySet)
+                return this.map.Equals(charArraySet.map);
+
+            if (other is ICollection<T> otherAsCollection)
+            {
+                if (this.Count != otherAsCollection.Count)
+                    return false;
+
+                // already confirmed that the sets have the same number of distinct elements, so if
+                // one is a superset of the other then they must be equal
+                return ContainsAllElements(otherAsCollection);
+            }
 
-                // Convert the item to a string in the invariant culture
-                string stringItem;
-                using (var context = new CultureContext(CultureInfo.InvariantCulture))
+            int otherCount = 0;
+            foreach (var local in other)
+            {
+                if (local is null || (local is ICharSequence charSequence && !charSequence.HasValue) || !this.Contains(local))
                 {
-                    stringItem = item.ToString();
+                    return false;
                 }
+                otherCount++;
+            }
+            return this.Count == otherCount;
+        }
+
+        /// <summary>
+        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
+        /// in itself, the specified collection, or both.
+        /// <para/>
+        /// <b>NOTE:</b> If <c>ignoreCase</c> is <c>true</c> for this <see cref="CharArraySet"/>, the text arrays will be directly modified.
+        /// The user should never modify these text arrays after calling this method.
+        /// </summary>
+        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        /// <exception cref="NotSupportedException">This set instance is read-only.</exception>
+        public virtual bool UnionWith(IEnumerable<char[]> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+            if (IsReadOnly)
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+
+            bool modified = false;
+            foreach (var item in other)
+            {
+                modified |= Add(item);
+            }
+            return modified;
+        }
+
+        /// <summary>
+        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
+        /// in itself, the specified collection, or both.
+        /// </summary>
+        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call.</returns>
+        /// <exception cref="ArgumentNullException">
+        /// <paramref name="other"/> is <c>null</c>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// A given element within the collection is <c>null</c>.
+        /// <para/>
+        /// -or-
+        /// <para/>
+        /// The <see cref="ICharSequence.HasValue"/> property for a given element in the collection returns <c>false</c>.
+        /// </exception>
+        /// <exception cref="NotSupportedException">This set instance is read-only.</exception>
+        public virtual bool UnionWith(IEnumerable<ICharSequence> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+            if (IsReadOnly)
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+
+            bool modified = false;
+            foreach (var item in other)
+            {
+                modified |= Add(item);
+            }
+            return modified;
+        }
+
+        /// <summary>
+        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
+        /// in itself, the specified collection, or both.
+        /// </summary>
+        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        /// <exception cref="NotSupportedException">This set instance is read-only.</exception>
+        public virtual bool UnionWith(IEnumerable<string> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+            if (IsReadOnly)
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+
+            bool modified = false;
+            foreach (var item in other)
+            {
+                modified |= Add(item);
+            }
+            return modified;
+        }
+
+        void ISet<string>.UnionWith(IEnumerable<string> other)
+        {
+            UnionWith(other);
+        }
+
+        /// <summary>
+        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
+        /// in itself, the specified collection, or both.
+        /// </summary>
+        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        /// <exception cref="NotSupportedException">This set instance is read-only.</exception>
+        public virtual bool UnionWith<T>(IEnumerable<T> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+            if (IsReadOnly)
+                throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
+
+#if FEATURE_SPANFORMATTABLE
+            Span<char> buffer = stackalloc char[256];
+#else
+            Span<char> buffer = stackalloc char[1];
+#endif
 
-                if (Add(stringItem))
+            bool modified = false;
+            foreach (var item in other)
+            {
+                if (item is char[] charArray)
                 {
-                    modified = true;
+                    modified |= Add(charArray);
+                    continue;
                 }
+
+                // Convert the item to chars in the invariant culture
+                var returnType = CharArrayDictionary.ConvertObjectToChars(item, out char[] chars, out string s, buffer);
+                if (returnType == CharArrayDictionary.CharReturnType.String)
+                    modified |= Add(s);
+                else
+                    modified |= Add(chars);
             }
             return modified;
         }
 
         // LUCENENET - no modifications should be made outside of original
         // Java implmentation's methods.
-        [Obsolete("Not applicable in this class.")]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public void IntersectWith(IEnumerable<string> other) // LUCENENET TODO: API - make an explicit implementation that isn't public
+        void ISet<string>.IntersectWith(IEnumerable<string> other)
         {
-            throw UnsupportedOperationException.Create();
+            throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
         }
 
         // LUCENENET - no modifications should be made outside of original
         // Java implmentation's methods.
-        [Obsolete("Not applicable in this class.")]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public void ExceptWith(IEnumerable<string> other) // LUCENENET TODO: API - make an explicit implementation that isn't public
+        void ISet<string>.ExceptWith(IEnumerable<string> other)
         {
-            throw UnsupportedOperationException.Create();
+            throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
         }
 
         // LUCENENET - no modifications should be made outside of original
         // Java implmentation's methods.
-        [Obsolete("Not applicable in this class.")]
-        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
-        public void SymmetricExceptWith(IEnumerable<string> other) // LUCENENET TODO: API - make an explicit implementation that isn't public
+        void ISet<string>.SymmetricExceptWith(IEnumerable<string> other)
         {
-            throw UnsupportedOperationException.Create();
+            throw UnsupportedOperationException.Create(SR.NotSupported_ReadOnlyCollection);
         }
 
         /// <summary>
@@ -580,17 +1313,82 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
         public virtual bool IsSubsetOf(IEnumerable<string> other)
         {
             if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            if (this.Count == 0)
             {
+                return true;
+            }
+            CharArraySet? set = other as CharArraySet;
+            if (set != null)
+            {
+                if (this.Count > set.Count)
+                {
+                    return false;
+                }
+                return this.IsSubsetOfCharArraySet(set);
+            }
+            // we just need to return true if the other set
+            // contains all of the elements of the this set,
+            // but we need to use the comparison rules of the current set.
+            this.GetFoundAndUnfoundCounts(other, out int foundCount, out int _);
+            return foundCount == this.Count;
+        }
+
+        /// <summary>
+        /// Determines whether a <see cref="CharArraySet"/> object is a subset of the specified collection.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsSubsetOf(IEnumerable<char[]> other)
+        {
+            if (other is null)
                 throw new ArgumentNullException(nameof(other));
+
+            if (this.Count == 0)
+            {
+                return true;
+            }
+            CharArraySet? set = other as CharArraySet;
+            if (set != null)
+            {
+                if (this.Count > set.Count)
+                {
+                    return false;
+                }
+                return this.IsSubsetOfCharArraySet(set);
             }
+            // we just need to return true if the other set
+            // contains all of the elements of the this set,
+            // but we need to use the comparison rules of the current set.
+            this.GetFoundAndUnfoundCounts(other, out int foundCount, out int _);
+            return foundCount == this.Count;
+        }
+
+        /// <summary>
+        /// Determines whether a <see cref="CharArraySet"/> object is a subset of the specified collection.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsSubsetOf(IEnumerable<ICharSequence> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
             if (this.Count == 0)
             {
                 return true;
             }
-            CharArraySet set = other as CharArraySet;
+            CharArraySet? set = other as CharArraySet;
             if (set != null)
             {
                 if (this.Count > set.Count)
@@ -611,12 +1409,12 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
         public virtual bool IsSubsetOf<T>(IEnumerable<T> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
+
             if (this.Count == 0)
             {
                 return true;
@@ -633,26 +1431,83 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
         public virtual bool IsSupersetOf(IEnumerable<string> other)
         {
             if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            ICollection<string>? is2 = other as ICollection<string>;
+            if (is2 != null)
             {
+                if (is2.Count == 0)
+                {
+                    return true;
+                }
+                CharArraySet? set = other as CharArraySet;
+                if ((set != null) && (set.Count > this.Count))
+                {
+                    return false;
+                }
+            }
+            return this.ContainsAllElements(other);
+        }
+
+        /// <summary>
+        /// Determines whether a <see cref="CharArraySet"/> object is a superset of the specified collection.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsSupersetOf(IEnumerable<char[]> other)
+        {
+            if (other is null)
                 throw new ArgumentNullException(nameof(other));
+
+            ICollection<char[]>? is2 = other as ICollection<char[]>;
+            if (is2 != null)
+            {
+                if (is2.Count == 0)
+                {
+                    return true;
+                }
+                CharArraySet? set = other as CharArraySet;
+                if ((set != null) && (set.Count > this.Count))
+                {
+                    return false;
+                }
             }
-            ICollection<string> is2 = other as ICollection<string>;
+            return this.ContainsAllElements(other);
+        }
+
+        /// <summary>
+        /// Determines whether a <see cref="CharArraySet"/> object is a superset of the specified collection.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsSupersetOf(IEnumerable<ICharSequence> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            ICollection<ICharSequence>? is2 = other as ICollection<ICharSequence>;
             if (is2 != null)
             {
                 if (is2.Count == 0)
                 {
                     return true;
                 }
-                CharArraySet set = other as CharArraySet;
+                CharArraySet? set = other as CharArraySet;
                 if ((set != null) && (set.Count > this.Count))
                 {
                     return false;
                 }
             }
-            return this.ContainsAll(other);
+            return this.ContainsAllElements(other);
         }
 
         /// <summary>
@@ -660,18 +1515,91 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
         public virtual bool IsSupersetOf<T>(IEnumerable<T> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
-            ICollection<T> is2 = other as ICollection<T>;
+
+            ICollection<T>? is2 = other as ICollection<T>;
             if (is2 != null && is2.Count == 0)
             {
                 return true;
             }
-            return this.ContainsAll(other);
+            return this.ContainsAllElements(other);
+        }
+
+        /// <summary>
+        /// Determines whether a <see cref="CharArraySet"/> object is a proper subset of the specified collection.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsProperSubsetOf(IEnumerable<string> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            ICollection<string>? is2 = other as ICollection<string>;
+            if (is2 != null)
+            {
+                if (this.Count == 0)
+                {
+                    return (is2.Count > 0);
+                }
+                CharArraySet? set = other as CharArraySet;
+                if (set != null)
+                {
+                    if (this.Count >= set.Count)
+                    {
+                        return false;
+                    }
+                    return this.IsSubsetOfCharArraySet(set);
+                }
+            }
+            // we just need to return true if the other set
+            // contains all of the elements of the this set plus at least one more,
+            // but we need to use the comparison rules of the current set.
+            this.GetFoundAndUnfoundCounts(other, out int foundCount, out int unfoundCount);
+            return foundCount == this.Count && unfoundCount > 0;
+        }
+
+        /// <summary>
+        /// Determines whether a <see cref="CharArraySet"/> object is a proper subset of the specified collection.
+        /// </summary>
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsProperSubsetOf(IEnumerable<char[]> other)
+        {
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
+
+            ICollection<char[]>? is2 = other as ICollection<char[]>;
+            if (is2 != null)
+            {
+                if (this.Count == 0)
+                {
+                    return (is2.Count > 0);
+                }
+                CharArraySet? set = other as CharArraySet;
+                if (set != null)
+                {
+                    if (this.Count >= set.Count)
+                    {
+                        return false;
+                    }
+                    return this.IsSubsetOfCharArraySet(set);
+                }
+            }
+            // we just need to return true if the other set
+            // contains all of the elements of the this set plus at least one more,
+            // but we need to use the comparison rules of the current set.
+            this.GetFoundAndUnfoundCounts(other, out int foundCount, out int unfoundCount);
+            return foundCount == this.Count && unfoundCount > 0;
         }
 
         /// <summary>
@@ -679,20 +1607,21 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
-        public virtual bool IsProperSubsetOf(IEnumerable<string> other)
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsProperSubsetOf(IEnumerable<ICharSequence> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
-            ICollection<string> is2 = other as ICollection<string>;
+
+            ICollection<ICharSequence>? is2 = other as ICollection<ICharSequence>;
             if (is2 != null)
             {
                 if (this.Count == 0)
                 {
                     return (is2.Count > 0);
                 }
-                CharArraySet set = other as CharArraySet;
+                CharArraySet? set = other as CharArraySet;
                 if (set != null)
                 {
                     if (this.Count >= set.Count)
@@ -714,13 +1643,14 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper subset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
         public virtual bool IsProperSubsetOf<T>(IEnumerable<T> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
-            ICollection<T> is2 = other as ICollection<T>;
+
+            ICollection<T>? is2 = other as ICollection<T>;
             if (is2 != null && this.Count == 0)
             {
                 return (is2.Count > 0);
@@ -737,31 +1667,32 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
         public virtual bool IsProperSupersetOf(IEnumerable<string> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
+
             if (this.Count == 0)
             {
                 return false;
             }
-            ICollection<string> is2 = other as ICollection<string>;
+            ICollection<string>? is2 = other as ICollection<string>;
             if (is2 != null)
             {
                 if (is2.Count == 0)
                 {
                     return true;
                 }
-                CharArraySet set = other as CharArraySet;
+                CharArraySet? set = other as CharArraySet;
                 if (set != null)
                 {
                     if (set.Count >= this.Count)
                     {
                         return false;
                     }
-                    return this.ContainsAll(set);
+                    return this.ContainsAllElements(set);
                 }
             }
             this.GetFoundAndUnfoundCounts(other, out int foundCount, out int unfoundCount);
@@ -773,725 +1704,422 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
         /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
-        public virtual bool IsProperSupersetOf<T>(IEnumerable<T> other)
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsProperSupersetOf(IEnumerable<char[]> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
+
             if (this.Count == 0)
             {
                 return false;
             }
-            ICollection<T> is2 = other as ICollection<T>;
-            if (is2 != null && is2.Count == 0)
+            ICollection<char[]>? is2 = other as ICollection<char[]>;
+            if (is2 != null)
             {
-                return true;
+                if (is2.Count == 0)
+                {
+                    return true;
+                }
+                CharArraySet? set = other as CharArraySet;
+                if (set != null)
+                {
+                    if (set.Count >= this.Count)
+                    {
+                        return false;
+                    }
+                    return this.ContainsAllElements(set);
+                }
             }
             this.GetFoundAndUnfoundCounts(other, out int foundCount, out int unfoundCount);
             return foundCount < this.Count && unfoundCount == 0;
         }
 
         /// <summary>
-        /// Determines whether the current <see cref="CharArraySet"/> object and a specified collection share common elements.
+        /// Determines whether a <see cref="CharArraySet"/> object is a proper superset of the specified collection.
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
-        /// <returns><c>true</c> if the <see cref="CharArraySet"/> object and <paramref name="other"/> share at least one common element; otherwise, <c>false</c>.</returns>
-        public virtual bool Overlaps(IEnumerable<string> other)
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsProperSupersetOf(IEnumerable<ICharSequence> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
+
+            if (this.Count == 0)
+            {
+                return false;
             }
-            if (this.Count != 0)
+            ICollection<ICharSequence>? is2 = other as ICollection<ICharSequence>;
+            if (is2 != null)
             {
-                foreach (var local in other)
+                if (is2.Count == 0)
+                {
+                    return true;
+                }
+                CharArraySet? set = other as CharArraySet;
+                if (set != null)
                 {
-                    if (this.Contains(local))
+                    if (set.Count >= this.Count)
                     {
-                        return true;
+                        return false;
                     }
+                    return this.ContainsAllElements(set);
                 }
             }
-            return false;
+            this.GetFoundAndUnfoundCounts(other, out int foundCount, out int unfoundCount);
+            return foundCount < this.Count && unfoundCount == 0;
         }
 
         /// <summary>
-        /// Determines whether the current <see cref="CharArraySet"/> object and a specified collection share common elements.
+        /// Determines whether a <see cref="CharArraySet"/> object is a proper superset of the specified collection.
         /// </summary>
         /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object and <paramref name="other"/> share at least one common element; otherwise, <c>false</c>.</returns>
-        public virtual bool Overlaps<T>(IEnumerable<T> other)
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object is a proper superset of <paramref name="other"/>; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        [SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "Following Microsoft's coding style")]
+        public virtual bool IsProperSupersetOf<T>(IEnumerable<T> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
-            if (this.Count != 0)
+
+            if (this.Count == 0)
             {
-                foreach (var local in other)
-                {
-                    if (this.Contains(local))
-                    {
-                        return true;
-                    }
-                }
+                return false;
             }
-            return false;
-        }
-
-        /// <summary>
-        /// Returns <c>true</c> if this collection contains all of the elements
-        /// in the specified collection.
-        /// </summary>
-        /// <param name="other">collection to be checked for containment in this collection</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
-        public virtual bool ContainsAll(IEnumerable<string> other)
-        {
-            foreach (var local in other)
+            ICollection<T>? is2 = other as ICollection<T>;
+            if (is2 != null && is2.Count == 0)
             {
-                if (!this.Contains(local))
-                {
-                    return false;
-                }
+                return true;
             }
-            return true;
+            this.GetFoundAndUnfoundCounts(other, out int foundCount, out int unfoundCount);
+            return foundCount < this.Count && unfoundCount == 0;
         }
 
         /// <summary>
-        /// Returns <c>true</c> if this collection contains all of the elements
-        /// in the specified collection.
+        /// Determines whether the current <see cref="CharArraySet"/> object and a specified collection share common elements.
         /// </summary>
-        /// <param name="other">collection to be checked for containment in this collection</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
-        public virtual bool ContainsAll<T>(IEnumerable<T> other)
-        {
-            foreach (var local in other)
-            {
-                if (!this.Contains(local))
-                {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private bool IsSubsetOfCharArraySet(CharArraySet other)
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if the <see cref="CharArraySet"/> object and <paramref name="other"/> share at least one common element; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool Overlaps(IEnumerable<string> other)
         {
-            foreach (var local in this)
-            {
-                if (!other.Contains(local))
-                {
-                    return false;
-                }
-            }
-            return true;
-        }
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
 
-        private void GetFoundAndUnfoundCounts<T>(IEnumerable<T> other, out int foundCount, out int unfoundCount)
-        {
-            foundCount = 0;
-            unfoundCount = 0;
-            foreach (var item in other)
+            if (this.Count != 0)
             {
-                if (this.Contains(item))
-                {
-                    foundCount++;
-                }
-                else
+                foreach (var local in other)
                 {
-                    unfoundCount++;
-                }
-            }
-        }
-
-#endregion
-    }
-
-    /// <summary>
-    /// LUCENENET specific extension methods for CharArraySet
-    /// </summary>
-    public static class CharArraySetExtensions
-    {
-#region Add
-
-        /// <summary>
-        /// Add this <see cref="bool"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        public static bool Add(this CharArraySet set, bool text)
-        {
-            return set.map.Put(text.ToString());
-        }
-
-        /// <summary>
-        /// Add this <see cref="byte"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        public static bool Add(this CharArraySet set, byte text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// Add this <see cref="char"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        public static bool Add(this CharArraySet set, char text)
-        {
-            return set.map.Put("" + text);
-        }
-
-        ///// <summary>
-        ///// Add this <see cref="decimal"/> into the set
-        ///// </summary>
-        ///// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        //public static bool Add(this CharArraySet set, decimal text)
-        //{
-        //    return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        //}
-
-        ///// <summary>
-        ///// Add this <see cref="double"/> into the set
-        ///// </summary>
-        ///// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        //public static bool Add(this CharArraySet set, double text)
-        //{
-        //    return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        //}
-
-        ///// <summary>
-        ///// Add this <see cref="float"/> into the set
-        ///// </summary>
-        ///// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        //public static bool Add(this CharArraySet set, float text)
-        //{
-        //    return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        //}
-
-        /// <summary>
-        /// Add this <see cref="int"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        public static bool Add(this CharArraySet set, int text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// Add this <see cref="long"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        public static bool Add(this CharArraySet set, long text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// Add this <see cref="sbyte"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        [CLSCompliant(false)]
-        public static bool Add(this CharArraySet set, sbyte text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// Add this <see cref="short"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        public static bool Add(this CharArraySet set, short text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// Add this <see cref="uint"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        [CLSCompliant(false)]
-        public static bool Add(this CharArraySet set, uint text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// Add this <see cref="ulong"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        [CLSCompliant(false)]
-        public static bool Add(this CharArraySet set, ulong text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// Add this <see cref="ushort"/> into the set
-        /// </summary>
-        /// <returns><c>true</c> if <paramref name="text"/> was added to the set; <c>false</c> if it already existed prior to this call</returns>
-        [CLSCompliant(false)]
-        public static bool Add(this CharArraySet set, ushort text)
-        {
-            return set.map.Put(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-#endregion
-
-#region Contains
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="bool"/> is in the set
-        /// </summary>
-        public static bool Contains(this CharArraySet set, bool text)
-        {
-            return set.map.ContainsKey(text.ToString());
-        }
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="byte"/> is in the set
-        /// </summary>
-        public static bool Contains(this CharArraySet set, byte text)
-        {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="char"/> is in the set
-        /// </summary>
-        public static bool Contains(this CharArraySet set, char text)
-        {
-            return set.map.ContainsKey("" + text);
-        }
-
-        ///// <summary>
-        ///// <c>true</c> if the <see cref="decimal"/> is in the set
-        ///// </summary>
-        //public static bool Contains(this CharArraySet set, decimal text)
-        //{
-        //    return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        //}
-
-        ///// <summary>
-        ///// <c>true</c> if the <see cref="double"/> is in the set
-        ///// </summary>
-        //public static bool Contains(this CharArraySet set, double text)
-        //{
-        //    return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        //}
-
-        ///// <summary>
-        ///// <c>true</c> if the <see cref="float"/> is in the set
-        ///// </summary>
-        //public static bool Contains(this CharArraySet set, float text)
-        //{
-        //    return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        //}
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="int"/> is in the set
-        /// </summary>
-        public static bool Contains(this CharArraySet set, int text)
-        {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="long"/> is in the set
-        /// </summary>
-        public static bool Contains(this CharArraySet set, long text)
-        {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="sbyte"/> is in the set
-        /// </summary>
-        [CLSCompliant(false)]
-        public static bool Contains(this CharArraySet set, sbyte text)
-        {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="short"/> is in the set
-        /// </summary>
-        public static bool Contains(this CharArraySet set, short text)
-        {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        }
-
-        /// <summary>
-        /// <c>true</c> if the <see cref="uint"/> is in the set
-        /// </summary>
-        [CLSCompliant(false)]
-        public static bool Contains(this CharArraySet set, uint text)
-        {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
+                    if (local is not null && this.Contains(local))
+                    {
+                        return true;
+                    }
+                }
+            }
+            return false;
         }
 
         /// <summary>
-        /// <c>true</c> if the <see cref="ulong"/> is in the set
+        /// Determines whether the current <see cref="CharArraySet"/> object and a specified collection share common elements.
         /// </summary>
-        [CLSCompliant(false)]
-        public static bool Contains(this CharArraySet set, ulong text)
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if the <see cref="CharArraySet"/> object and <paramref name="other"/> share at least one common element; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool Overlaps(IEnumerable<char[]> other)
         {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
-        }
+            if (other is null)
+                throw new ArgumentNullException(nameof(other));
 
-        /// <summary>
-        /// <c>true</c> if the <see cref="ushort"/> is in the set
-        /// </summary>
-        [CLSCompliant(false)]
-        public static bool Contains(this CharArraySet set, ushort text)
-        {
-            return set.map.ContainsKey(text.ToString(CultureInfo.InvariantCulture));
+            if (this.Count != 0)
+            {
+                foreach (var local in other)
+                {
+                    if (local is not null && this.Contains(local))
+                    {
+                        return true;
+                    }
+                }
+            }
+            return false;
         }
 
-#endregion
-
-#region UnionWith
-
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Determines whether the current <see cref="CharArraySet"/> object and a specified collection share common elements.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public static bool UnionWith(this CharArraySet set, IEnumerable<byte> other)
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if the <see cref="CharArraySet"/> object and <paramref name="other"/> share at least one common element; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool Overlaps(IEnumerable<ICharSequence> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
-            if (set.IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+
+            if (this.Count != 0)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                foreach (var local in other)
                 {
-                    modified = true;
+                    if (local is not null && local.HasValue && this.Contains(local))
+                    {
+                        return true;
+                    }
                 }
             }
-            return modified;
+            return false;
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Determines whether the current <see cref="CharArraySet"/> object and a specified collection share common elements.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public static bool UnionWith(this CharArraySet set, IEnumerable<char> other)
+        /// <param name="other">The collection to compare to the current <see cref="CharArraySet"/> object.</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> object and <paramref name="other"/> share at least one common element; otherwise, <c>false</c>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="other"/> is <c>null</c>.</exception>
+        public virtual bool Overlaps<T>(IEnumerable<T> other)
         {
             if (other is null)
-            {
                 throw new ArgumentNullException(nameof(other));
-            }
-            if (set.IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+
+            if (this.Count != 0)
             {
-                if (set.Add("" + item))
+                foreach (var local in other)
                 {
-                    modified = true;
+                    if (local is not null && this.Contains(local))
+                    {
+                        return true;
+                    }
                 }
             }
-            return modified;
+            return false;
         }
 
-        ///// <summary>
-        ///// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        ///// in itself, the specified collection, or both.
-        ///// </summary>
-        ///// <param name="set">this <see cref="CharArraySet"/></param>
-        ///// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        ///// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        //public static bool UnionWith(this CharArraySet set, IEnumerable<decimal> other)
-        //{
-        //    if (other is null)
-        //    {
-        //        throw new ArgumentNullException(nameof(other));
-        //    }
-        //    if (set.IsReadOnly)
-        //    {
-        //        throw UnsupportedOperationException.Create("CharArraySet is readonly");
-        //    }
-        //    bool modified = false;
-        //    foreach (var item in other)
-        //    {
-        //        if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
-        //        {
-        //            modified = true;
-        //        }
-        //    }
-        //    return modified;
-        //}
-
-        ///// <summary>
-        ///// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        ///// in itself, the specified collection, or both.
-        ///// </summary>
-        ///// <param name="set">this <see cref="CharArraySet"/></param>
-        ///// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        ///// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        //public static bool UnionWith(this CharArraySet set, IEnumerable<double> other)
-        //{
-        //    if (other is null)
-        //    {
-        //        throw new ArgumentNullException(nameof(other));
-        //    }
-        //    if (set.IsReadOnly)
-        //    {
-        //        throw UnsupportedOperationException.Create("CharArraySet is readonly");
-        //    }
-        //    bool modified = false;
-        //    foreach (var item in other)
-        //    {
-        //        if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
-        //        {
-        //            modified = true;
-        //        }
-        //    }
-        //    return modified;
-        //}
-
-        ///// <summary>
-        ///// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        ///// in itself, the specified collection, or both.
-        ///// </summary>
-        ///// <param name="set">this <see cref="CharArraySet"/></param>
-        ///// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        ///// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        //public static bool UnionWith(this CharArraySet set, IEnumerable<float> other)
-        //{
-        //    if (other is null)
-        //    {
-        //        throw new ArgumentNullException(nameof(other));
-        //    }
-        //    if (set.IsReadOnly)
-        //    {
-        //        throw UnsupportedOperationException.Create("CharArraySet is readonly");
-        //    }
-        //    bool modified = false;
-        //    foreach (var item in other)
-        //    {
-        //        if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
-        //        {
-        //            modified = true;
-        //        }
-        //    }
-        //    return modified;
-        //}
-
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Returns <c>true</c> if this collection contains all of the elements
+        /// in the specified collection.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public static bool UnionWith(this CharArraySet set, IEnumerable<int> other)
+        /// <param name="other">collection to be checked for containment in this collection</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
+        [Obsolete("Use the IsSupersetOf() method instead. This method will be removed in 4.8.0 release candidate."), EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual bool ContainsAll(IEnumerable<string> other)
         {
-            if (other is null)
-            {
-                throw new ArgumentNullException(nameof(other));
-            }
-            if (set.IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+            foreach (var local in other)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                if (local is null || !this.Contains(local))
                 {
-                    modified = true;
+                    return false;
                 }
             }
-            return modified;
+            return true;
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Returns <c>true</c> if this collection contains all of the elements
+        /// in the specified collection.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public static bool UnionWith(this CharArraySet set, IEnumerable<long> other)
+        /// <param name="other">collection to be checked for containment in this collection</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
+        [Obsolete("Use the IsSupersetOf() method instead. This method will be removed in 4.8.0 release candidate."), EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual bool ContainsAll<T>(IEnumerable<T> other)
         {
-            if (other is null)
-            {
-                throw new ArgumentNullException(nameof(other));
-            }
-            if (set.IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+            foreach (var local in other)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                if (local is null || !this.Contains(local))
                 {
-                    modified = true;
+                    return false;
                 }
             }
-            return modified;
+            return true;
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Returns <c>true</c> if this collection contains all of the elements
+        /// in the specified collection.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        [CLSCompliant(false)]
-        public static bool UnionWith(this CharArraySet set, IEnumerable<sbyte> other)
+        /// <param name="other">collection to be checked for containment in this collection</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
+        private bool ContainsAllElements(IEnumerable<string> other)
         {
-            if (other is null)
-            {
-                throw new ArgumentNullException(nameof(other));
-            }
-            if (set.IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+            foreach (var local in other)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                if (local is null || !this.Contains(local))
                 {
-                    modified = true;
+                    return false;
                 }
             }
-            return modified;
+            return true;
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Returns <c>true</c> if this collection contains all of the elements
+        /// in the specified collection.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        public static bool UnionWith(this CharArraySet set, IEnumerable<short> other)
+        /// <param name="other">collection to be checked for containment in this collection</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
+        private bool ContainsAllElements(IEnumerable<char[]> other)
         {
-            if (other is null)
-            {
-                throw new ArgumentNullException(nameof(other));
-            }
-            if (set.IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+            foreach (var local in other)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                if (local is null || !this.Contains(local))
                 {
-                    modified = true;
+                    return false;
                 }
             }
-            return modified;
+            return true;
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Returns <c>true</c> if this collection contains all of the elements
+        /// in the specified collection.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        [CLSCompliant(false)]
-        public static bool UnionWith(this CharArraySet set, IEnumerable<uint> other)
+        /// <param name="other">collection to be checked for containment in this collection</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
+        private bool ContainsAllElements(IEnumerable<ICharSequence> other)
         {
-            if (other is null)
-            {
-                throw new ArgumentNullException(nameof(other));
-            }
-            if (set.IsReadOnly)
-            {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
-            }
-            bool modified = false;
-            foreach (var item in other)
+            foreach (var local in other)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                if (local is null || !local.HasValue || !this.Contains(local))
                 {
-                    modified = true;
+                    return false;
                 }
             }
-            return modified;
+            return true;
         }
 
         /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
+        /// Returns <c>true</c> if this collection contains all of the elements
+        /// in the specified collection.
         /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        [CLSCompliant(false)]
-        public static bool UnionWith(this CharArraySet set, IEnumerable<ulong> other)
+        /// <param name="other">collection to be checked for containment in this collection</param>
+        /// <returns><c>true</c> if this <see cref="CharArraySet"/> contains all of the elements in the specified collection; otherwise, <c>false</c>.</returns>
+        private bool ContainsAllElements<T>(IEnumerable<T> other)
         {
-            if (other is null)
+            foreach (var local in other)
             {
-                throw new ArgumentNullException(nameof(other));
+                if (local is null || (local is ICharSequence charSequence && !charSequence.HasValue) || !this.Contains(local))
+                {
+                    return false;
+                }
             }
-            if (set.IsReadOnly)
+            return true;
+        }
+
+        private bool IsSubsetOfCharArraySet(CharArraySet other)
+        {
+            foreach (var local in this)
             {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
+                if (local is null || !other.Contains(local))
+                {
+                    return false;
+                }
             }
-            bool modified = false;
+            return true;
+        }
+
+        private void GetFoundAndUnfoundCounts(IEnumerable<string> other, out int foundCount, out int unfoundCount)
+        {
+            foundCount = 0;
+            unfoundCount = 0;
             foreach (var item in other)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                if (item is not null && this.Contains(item))
+                {
+                    foundCount++;
+                }
+                else
                 {
-                    modified = true;
+                    unfoundCount++;
                 }
             }
-            return modified;
         }
 
-        /// <summary>
-        /// Modifies the current <see cref="CharArraySet"/> to contain all elements that are present 
-        /// in itself, the specified collection, or both.
-        /// </summary>
-        /// <param name="set">this <see cref="CharArraySet"/></param>
-        /// <param name="other">The collection whose elements should be merged into the <see cref="CharArraySet"/>.</param>
-        /// <returns><c>true</c> if this <see cref="CharArraySet"/> changed as a result of the call</returns>
-        [CLSCompliant(false)]
-        public static bool UnionWith(this CharArraySet set, IEnumerable<ushort> other)
+        private void GetFoundAndUnfoundCounts(IEnumerable<char[]> other, out int foundCount, out int unfoundCount)
         {
-            if (other is null)
+            foundCount = 0;
+            unfoundCount = 0;
+            foreach (var item in other)
             {
-                throw new ArgumentNullException(nameof(other));
+                if (item is not null && this.Contains(item))
+                {
+                    foundCount++;
+                }
+                else
+                {
+                    unfoundCount++;
+                }
             }
-            if (set.IsReadOnly)
+        }
+
+        private void GetFoundAndUnfoundCounts(IEnumerable<ICharSequence> other, out int foundCount, out int unfoundCount)
+        {
+            foundCount = 0;
+            unfoundCount = 0;
+            foreach (var item in other)
             {
-                throw UnsupportedOperationException.Create("CharArraySet is readonly");
+                if (item is not null && item.HasValue && this.Contains(item))
+                {
+                    foundCount++;
+                }
+                else
+                {
+                    unfoundCount++;
+                }
             }
-            bool modified = false;
+        }
+
+        private void GetFoundAndUnfoundCounts<T>(IEnumerable<T> other, out int foundCount, out int unfoundCount)
+        {
+            foundCount = 0;
+            unfoundCount = 0;
             foreach (var item in other)
             {
-                if (set.Add(item.ToString(CultureInfo.InvariantCulture)))
+                if (item is not null && this.Contains(item))
+                {
+                    foundCount++;
+                }
+                else
                 {
-                    modified = true;
+                    unfoundCount++;
                 }
             }
-            return modified;
         }
 
 #endregion
     }
+
+    /// <summary>
+    /// Extensions to <see cref="IEnumerable{T}"/> for <see cref="CharArraySet"/>.
+    /// </summary>
+    // LUCENENET specific
+    public static class EnumerableExtensions
+    {
+        /// <summary>
+        /// Returns a copy of this <see cref="IEnumerable{T}"/> as a new instance of <see cref="CharArraySet"/> with the
+        /// specified <paramref name="matchVersion"/> and ignoreCase set to <c>false</c>.
+        /// </summary>
+        /// <typeparam name="T">The type of collection. Typically a <see cref="string"/> or <see cref="T:char[]"/>.</typeparam>
+        /// <param name="collection">This collection.</param>
+        /// <param name="matchVersion">Compatibility match version.</param>
+        /// <returns>A copy of this <see cref="IEnumerable{T}"/> as a <see cref="CharArraySet"/>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
+        public static CharArraySet ToCharArraySet<T>(this IEnumerable<T> collection, LuceneVersion matchVersion)
+        {
+            return CharArraySet.CopySet(matchVersion, collection, ignoreCase: false);
+        }
+
+        /// <summary>
+        /// Returns a copy of this <see cref="IEnumerable{T}"/> as a new instance of <see cref="CharArraySet"/> with the
+        /// specified <paramref name="matchVersion"/> and <paramref name="ignoreCase"/>.
+        /// </summary>
+        /// <typeparam name="T">The type of collection. Typically a <see cref="string"/> or <see cref="T:char[]"/>.</typeparam>
+        /// <param name="collection">This collection.</param>
+        /// <param name="matchVersion">Compatibility match version.</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 this <see cref="IEnumerable{T}"/> as a <see cref="CharArraySet"/>.</returns>
+        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is <c>null</c>.</exception>
+        public static CharArraySet ToCharArraySet<T>(this IEnumerable<T> collection, LuceneVersion matchVersion, bool ignoreCase)
+        {
+            return CharArraySet.CopySet(matchVersion, collection, ignoreCase);
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Util/StopwordAnalyzerBase.cs b/src/Lucene.Net.Analysis.Common/Analysis/Util/StopwordAnalyzerBase.cs
index 6e8970615..cded2217b 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Util/StopwordAnalyzerBase.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Util/StopwordAnalyzerBase.cs
@@ -56,7 +56,7 @@ namespace Lucene.Net.Analysis.Util
         {
             m_matchVersion = version;
             // analyzers should use char array set for stopwords!
-            this.m_stopwords = stopwords is null ? CharArraySet.EMPTY_SET : CharArraySet.Copy(version, stopwords).AsReadOnly();
+            this.m_stopwords = stopwords is null ? CharArraySet.Empty : CharArraySet.Copy(version, stopwords).AsReadOnly();
         }
 
         /// <summary>
diff --git a/src/Lucene.Net.Analysis.Common/Analysis/Util/WordlistLoader.cs b/src/Lucene.Net.Analysis.Common/Analysis/Util/WordlistLoader.cs
index 0834d0fd4..0385da530 100644
--- a/src/Lucene.Net.Analysis.Common/Analysis/Util/WordlistLoader.cs
+++ b/src/Lucene.Net.Analysis.Common/Analysis/Util/WordlistLoader.cs
@@ -199,7 +199,7 @@ namespace Lucene.Net.Analysis.Util
         /// </summary>
         /// <returns> stem dictionary that overrules the stemming algorithm </returns>
         /// <exception cref="IOException"> If there is a low-level I/O error. </exception>
-        public static CharArrayMap<string> GetStemDict(TextReader reader, CharArrayMap<string> result)
+        public static CharArrayDictionary<string> GetStemDict(TextReader reader, CharArrayDictionary<string> result)
         {
             try
             { 
@@ -207,7 +207,7 @@ namespace Lucene.Net.Analysis.Util
                 while ((line = reader.ReadLine()) != null)
                 {
                     string[] wordstem = line.Split(new char[] { '\t' }, 2);
-                    result.Put(wordstem[0], wordstem[1]);
+                    result[wordstem[0]] = wordstem[1];
                 }
             }
             finally
diff --git a/src/Lucene.Net.Analysis.Morfologik/Uk/UkrainianMorfologikAnalyzer.cs b/src/Lucene.Net.Analysis.Morfologik/Uk/UkrainianMorfologikAnalyzer.cs
index 681c15ab4..79217e973 100644
--- a/src/Lucene.Net.Analysis.Morfologik/Uk/UkrainianMorfologikAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Morfologik/Uk/UkrainianMorfologikAnalyzer.cs
@@ -91,7 +91,7 @@ namespace Lucene.Net.Analysis.Uk
         /// <param name="matchVersion"><see cref="LuceneVersion"/> to match.</param>
         /// <param name="stopwords">A stopword set.</param>
         public UkrainianMorfologikAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Analysis.SmartCn/SmartChineseAnalyzer.cs b/src/Lucene.Net.Analysis.SmartCn/SmartChineseAnalyzer.cs
index 40da7a979..40890f3ca 100644
--- a/src/Lucene.Net.Analysis.SmartCn/SmartChineseAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.SmartCn/SmartChineseAnalyzer.cs
@@ -121,7 +121,7 @@ namespace Lucene.Net.Analysis.Cn.Smart
         public SmartChineseAnalyzer(LuceneVersion matchVersion, bool useDefaultStopWords)
         {
             stopWords = useDefaultStopWords ? DefaultSetHolder.DEFAULT_STOP_SET
-              : CharArraySet.EMPTY_SET;
+              : CharArraySet.Empty;
             this.matchVersion = matchVersion;
         }
 
@@ -137,7 +137,7 @@ namespace Lucene.Net.Analysis.Cn.Smart
         /// <param name="stopWords"><see cref="CharArraySet"/> of stopwords to use.</param>
         public SmartChineseAnalyzer(LuceneVersion matchVersion, CharArraySet stopWords)
         {
-            this.stopWords = stopWords ?? CharArraySet.EMPTY_SET;
+            this.stopWords = stopWords ?? CharArraySet.Empty;
             this.matchVersion = matchVersion;
         }
 
diff --git a/src/Lucene.Net.Analysis.Stempel/Pl/PolishAnalyzer.cs b/src/Lucene.Net.Analysis.Stempel/Pl/PolishAnalyzer.cs
index 505bb7da0..ac3dfaf77 100644
--- a/src/Lucene.Net.Analysis.Stempel/Pl/PolishAnalyzer.cs
+++ b/src/Lucene.Net.Analysis.Stempel/Pl/PolishAnalyzer.cs
@@ -115,7 +115,7 @@ namespace Lucene.Net.Analysis.Pl
         /// <param name="matchVersion">lucene compatibility version</param>
         /// <param name="stopwords">a stopword set</param>
         public PolishAnalyzer(LuceneVersion matchVersion, CharArraySet stopwords)
-            : this(matchVersion, stopwords, CharArraySet.EMPTY_SET)
+            : this(matchVersion, stopwords, CharArraySet.Empty)
         {
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ar/TestArabicAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ar/TestArabicAnalyzer.cs
index 65f41695c..f726e72a8 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ar/TestArabicAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ar/TestArabicAnalyzer.cs
@@ -96,12 +96,12 @@ namespace Lucene.Net.Analysis.Ar
         public virtual void TestWithStemExclusionSet()
         {
             CharArraySet set = new CharArraySet(TEST_VERSION_CURRENT, AsSet("ساهدهات"), false);
-            ArabicAnalyzer a = new ArabicAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            ArabicAnalyzer a = new ArabicAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(a, "كبيرة the quick ساهدهات", new string[] { "كبير", "the", "quick", "ساهدهات" });
             AssertAnalyzesTo(a, "كبيرة the quick ساهدهات", new string[] { "كبير", "the", "quick", "ساهدهات" });
 
 
-            a = new ArabicAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, CharArraySet.EMPTY_SET);
+            a = new ArabicAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, CharArraySet.Empty);
             AssertAnalyzesTo(a, "كبيرة the quick ساهدهات", new string[] { "كبير", "the", "quick", "ساهد" });
             AssertAnalyzesTo(a, "كبيرة the quick ساهدهات", new string[] { "كبير", "the", "quick", "ساهد" });
         }
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Bg/TestBulgarianAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Bg/TestBulgarianAnalyzer.cs
index 87e50e40e..d2c18767b 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Bg/TestBulgarianAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Bg/TestBulgarianAnalyzer.cs
@@ -46,7 +46,7 @@ namespace Lucene.Net.Analysis.Bg
         [Test]
         public virtual void TestCustomStopwords()
         {
-            Analyzer a = new BulgarianAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET);
+            Analyzer a = new BulgarianAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty);
             AssertAnalyzesTo(a, "Как се казваш?", new string[] { "как", "се", "казваш" });
         }
 
@@ -79,7 +79,7 @@ namespace Lucene.Net.Analysis.Bg
         {
             CharArraySet set = new CharArraySet(TEST_VERSION_CURRENT, 1, true);
             set.add("строеве");
-            Analyzer a = new BulgarianAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            Analyzer a = new BulgarianAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(a, "строевете строеве", new string[] { "строй", "строеве" });
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Br/TestBrazilianStemmer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Br/TestBrazilianStemmer.cs
index 9a12b9707..a5f0c6604 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Br/TestBrazilianStemmer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Br/TestBrazilianStemmer.cs
@@ -141,7 +141,7 @@ namespace Lucene.Net.Analysis.Br
         [Test]
         public virtual void TestStemExclusionTable()
         {
-            BrazilianAnalyzer a = new BrazilianAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, new CharArraySet(TEST_VERSION_CURRENT, AsSet("quintessência"), false));
+            BrazilianAnalyzer a = new BrazilianAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, new CharArraySet(TEST_VERSION_CURRENT, AsSet("quintessência"), false));
             checkReuse(a, "quintessência", "quintessência"); // excluded words will be completely unchanged.
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cjk/TestCJKAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cjk/TestCJKAnalyzer.cs
index 13bfccad8..5ba502e36 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cjk/TestCJKAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cjk/TestCJKAnalyzer.cs
@@ -180,7 +180,7 @@ namespace Lucene.Net.Analysis.Cjk
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenFilter filter = new FakeStandardTokenizer(tokenizer);
-                filter = new StopFilter(TEST_VERSION_CURRENT, filter, CharArraySet.EMPTY_SET);
+                filter = new StopFilter(TEST_VERSION_CURRENT, filter, CharArraySet.Empty);
                 filter = new CJKBigramFilter(filter);
                 return new TokenStreamComponents(tokenizer, filter);
             });
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ckb/TestSoraniAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ckb/TestSoraniAnalyzer.cs
index 48e2812a5..aa7d9df0d 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ckb/TestSoraniAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Ckb/TestSoraniAnalyzer.cs
@@ -1,4 +1,4 @@
-// Lucene version compatibility level 4.8.1
+// Lucene version compatibility level 4.8.1
 using Lucene.Net.Analysis.Util;
 using NUnit.Framework;
 
@@ -46,7 +46,7 @@ namespace Lucene.Net.Analysis.Ckb
         [Test]
         public virtual void TestCustomStopwords()
         {
-            Analyzer a = new SoraniAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET);
+            Analyzer a = new SoraniAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty);
             AssertAnalyzesTo(a, "ئەم پیاوە", new string[] { "ئەم", "پیاو" });
         }
 
@@ -63,7 +63,7 @@ namespace Lucene.Net.Analysis.Ckb
         {
             CharArraySet set = new CharArraySet(TEST_VERSION_CURRENT, 1, true);
             set.add("پیاوە");
-            Analyzer a = new SoraniAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            Analyzer a = new SoraniAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(a, "پیاوە", new string[] { "پیاوە" });
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Core/TestRandomChains.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Core/TestRandomChains.cs
index 391c9299f..a06ab1290 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Core/TestRandomChains.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Core/TestRandomChains.cs
@@ -304,7 +304,7 @@ namespace Lucene.Net.Analysis.Core
             { typeof(string), new StringArgProducer() },
             { typeof(NormalizeCharMap), new NormalizeCharMapArgProducer() },
             { typeof(CharacterRunAutomaton), new CharacterRunAutomatonArgProducer() },
-            { typeof(CharArrayMap<string>), new StringCharArrayMapArgProducer() },
+            { typeof(CharArrayDictionary<string>), new StringCharArrayMapArgProducer() },
             { typeof(StemmerOverrideFilter.StemmerOverrideMap), new StemmerOverrideMapArgProducer() },
             { typeof(SynonymMap), new SynonymMapArgProducer() },
             { typeof(WordDelimiterFlags), new AnonymousProducer((random) => {
@@ -648,11 +648,11 @@ namespace Lucene.Net.Analysis.Core
             public object Create(Random random)
             {
                 int num = random.nextInt(10);
-                CharArrayMap<string> map = new CharArrayMap<string>(TEST_VERSION_CURRENT, num, random.nextBoolean());
+                CharArrayDictionary<string> map = new CharArrayDictionary<string>(TEST_VERSION_CURRENT, num, random.nextBoolean());
                 for (int i = 0; i < num; i++)
                 {
                     // TODO: make nastier
-                    map.Put(TestUtil.RandomSimpleString(random), TestUtil.RandomSimpleString(random));
+                    map[TestUtil.RandomSimpleString(random)] = TestUtil.RandomSimpleString(random);
                 }
                 return map;
             }
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cz/TestCzechAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cz/TestCzechAnalyzer.cs
index 8ad713fc0..bc97a4fbb 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cz/TestCzechAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Cz/TestCzechAnalyzer.cs
@@ -69,7 +69,7 @@ namespace Lucene.Net.Analysis.Cz
         {
             CharArraySet set = new CharArraySet(TEST_VERSION_CURRENT, 1, true);
             set.add("hole");
-            CzechAnalyzer cz = new CzechAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            CzechAnalyzer cz = new CzechAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(cz, "hole desek", new string[] { "hole", "desk" });
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/De/TestGermanAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/De/TestGermanAnalyzer.cs
index bae8101a9..2944ad7f8 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/De/TestGermanAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/De/TestGermanAnalyzer.cs
@@ -48,7 +48,7 @@ namespace Lucene.Net.Analysis.De
         [Test]
         public virtual void TestStemExclusionTable()
         {
-            GermanAnalyzer a = new GermanAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, new CharArraySet(TEST_VERSION_CURRENT, AsSet("tischen"), false));
+            GermanAnalyzer a = new GermanAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, new CharArraySet(TEST_VERSION_CURRENT, AsSet("tischen"), false));
             CheckOneTerm(a, "tischen", "tischen");
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Fr/TestFrenchAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Fr/TestFrenchAnalyzer.cs
index b6ff3d6e1..cb5108fb6 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Fr/TestFrenchAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Fr/TestFrenchAnalyzer.cs
@@ -1,4 +1,4 @@
-// Lucene version compatibility level 4.8.1
+// Lucene version compatibility level 4.8.1
 using Lucene.Net.Analysis.Util;
 using Lucene.Net.Util;
 using NUnit.Framework;
@@ -135,10 +135,10 @@ namespace Lucene.Net.Analysis.Fr
         {
             CharArraySet set = new CharArraySet(TEST_VERSION_CURRENT, 1, true);
             set.add("habitable");
-            FrenchAnalyzer fa = new FrenchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            FrenchAnalyzer fa = new FrenchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(fa, "habitable chiste", new string[] { "habitable", "chist" });
 
-            fa = new FrenchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            fa = new FrenchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(fa, "habitable chiste", new string[] { "habitable", "chist" });
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Nl/TestDutchStemmer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Nl/TestDutchStemmer.cs
index 310565a18..af5e212d7 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Nl/TestDutchStemmer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Nl/TestDutchStemmer.cs
@@ -1,4 +1,4 @@
-// Lucene version compatibility level 4.8.1
+// Lucene version compatibility level 4.8.1
 using System;
 using NUnit.Framework;
 using Lucene.Net.Analysis.Util;
@@ -151,10 +151,10 @@ namespace Lucene.Net.Analysis.Nl
             CharArraySet set = new CharArraySet(LuceneVersion.LUCENE_30, 1, true);
 #pragma warning restore 612, 618
             set.add("lichamelijk");
-            DutchAnalyzer a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            DutchAnalyzer a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(a, "lichamelijk lichamelijke", new string[] { "lichamelijk", "licham" });
 
-            a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, set);
+            a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, set);
             AssertAnalyzesTo(a, "lichamelijk lichamelijke", new string[] { "lichamelijk", "licham" });
 
         }
@@ -166,7 +166,7 @@ namespace Lucene.Net.Analysis.Nl
         [Test]
         public virtual void TestStemOverrides()
         {
-            DutchAnalyzer a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET);
+            DutchAnalyzer a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty);
             CheckOneTerm(a, "fiets", "fiets");
         }
         /// <summary>
@@ -178,14 +178,14 @@ namespace Lucene.Net.Analysis.Nl
         {
             DutchAnalyzer a = new DutchAnalyzer(LuceneVersion.LUCENE_30);
             CheckOneTerm(a, "fiets", "fiets");
-            a = new DutchAnalyzer(LuceneVersion.LUCENE_30, CharArraySet.EMPTY_SET);
+            a = new DutchAnalyzer(LuceneVersion.LUCENE_30, CharArraySet.Empty);
             CheckOneTerm(a, "fiets", "fiet"); // only the default ctor populates the dict
         }
 
         [Test]
         public virtual void TestEmptyStemDictionary()
         {
-            DutchAnalyzer a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET, CharArraySet.EMPTY_SET, CharArrayMap<string>.EmptyMap());
+            DutchAnalyzer a = new DutchAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty, CharArraySet.Empty, CharArrayDictionary<string>.Empty);
             CheckOneTerm(a, "fiets", "fiet");
         }
 
@@ -197,7 +197,7 @@ namespace Lucene.Net.Analysis.Nl
         [Obsolete("(3.6) Remove this test in Lucene 5.0")]
         public virtual void TestBuggyStemOverrides()
         {
-            DutchAnalyzer a = new DutchAnalyzer(LuceneVersion.LUCENE_35, CharArraySet.EMPTY_SET);
+            DutchAnalyzer a = new DutchAnalyzer(LuceneVersion.LUCENE_35, CharArraySet.Empty);
             CheckOneTerm(a, "fiets", "fiet");
         }
 
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Synonym/TestSynonymMap.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Synonym/TestSynonymMap.cs
index 24fc697b0..d798f1d1f 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Synonym/TestSynonymMap.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Synonym/TestSynonymMap.cs
@@ -319,7 +319,7 @@ namespace Lucene.Net.Analysis.Synonym
 
         private void AssertTokIncludes(SlowSynonymMap map, string src, string exp)
         {
-            Token[] tokens = map.Submap.Get(src).Synonyms;
+            Token[] tokens = map.Submap[src].Synonyms;
             bool inc = false;
             foreach (Token token in tokens)
             {
@@ -333,7 +333,7 @@ namespace Lucene.Net.Analysis.Synonym
 
         private SlowSynonymMap GetSubSynonymMap(SlowSynonymMap map, string src)
         {
-            return map.Submap.Get(src);
+            return map.Submap.TryGetValue(src, out SlowSynonymMap result) ? result : null;
         }
     }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs
index 78e064a4b..53c4b28ae 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Th/TestThaiAnalyzer.cs
@@ -53,7 +53,7 @@ namespace Lucene.Net.Analysis.Th
         [Test]
         public virtual void TestOffsets()
         {
-            AssertAnalyzesTo(new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET), "การที่ได้ต้องแสดงว่างานดี", new string[] { "การ", "ที่", "ได้", "ต้อง", "แสดง", "ว่า", "งาน", "ดี" }, new int[] { 0, 3, 6, 9, 13, 17, 20, 23 }, new int[] { 3, 6, 9, 13, 17, 20, 23, 25 });
+            AssertAnalyzesTo(new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty), "การที่ได้ต้องแสดงว่างานดี", new string[] { "การ", "ที่", "ได้", "ต้อง", "แสดง", "ว่า", "งาน", "ดี" }, new int[] { 0, 3, 6, 9, 13, 17, 20, 23 }, new int[] { 3, 6, 9, 13, 17, 20, 23, 25 });
         }
 
         [Test]
@@ -158,7 +158,7 @@ namespace Lucene.Net.Analysis.Th
         [Test]
         public virtual void TestReusableTokenStream()
         {
-            ThaiAnalyzer analyzer = new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET);
+            ThaiAnalyzer analyzer = new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty);
             AssertAnalyzesTo(analyzer, "", new string[] { });
 
             AssertAnalyzesTo(analyzer, "การที่ได้ต้องแสดงว่างานดี", new string[] { "การ", "ที่", "ได้", "ต้อง", "แสดง", "ว่า", "งาน", "ดี" });
@@ -392,7 +392,7 @@ namespace Lucene.Net.Analysis.Th
         [Test]
         public virtual void TestTwoSentences()
         {
-            AssertAnalyzesTo(new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET), "This is a test. การที่ได้ต้องแสดงว่างานดี", new string[] { "this", "is", "a", "test", "การ", "ที่", "ได้", "ต้อง", "แสดง", "ว่า", "งาน", "ดี" }, new int[] { 0, 5, 8, 10, 16, 19, 22, 25, 29, 33, 36, 39 }, new int[] { 4, 7, 9, 14, 19, 22, 25, 29, 33, 36, 39, 41 });
+            AssertAnalyzesTo(new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty), "This is a test. การที่ได้ต้องแสดงว่างานดี", new string[] { "this", "is", "a", "test", "การ", "ที่", "ได้", "ต้อง", "แสดง", "ว่า", "งาน", "ดี" }, new int[] { 0, 5, 8, 10, 16, 19, 22, 25, 29, 33, 36, 39 }, new int[] { 4, 7, 9, 14, 19, 22, 25, 29, 33, 36, 39, 41 });
         }
 
         /// <summary>
@@ -401,7 +401,7 @@ namespace Lucene.Net.Analysis.Th
         [Test][LuceneNetSpecific]
         public virtual void TestNumeralBreaking()
         {
-            ThaiAnalyzer analyzer = new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.EMPTY_SET);
+            ThaiAnalyzer analyzer = new ThaiAnalyzer(TEST_VERSION_CURRENT, CharArraySet.Empty);
             AssertAnalyzesTo(analyzer, "๑๒๓456", new String[] { "๑๒๓456" });
         }
     }
diff --git a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Util/TestCharArrayMap.cs b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Util/TestCharArrayMap.cs
index 9cc53c11d..2e7085fec 100644
--- a/src/Lucene.Net.Tests.Analysis.Common/Analysis/Util/TestCharArrayMap.cs
+++ b/src/Lucene.Net.Tests.Analysis.Common/Analysis/Util/TestCharArrayMap.cs
@@ -1,10 +1,15 @@
 // Lucene version compatibility level 4.8.1
+using J2N.Collections;
+using J2N.Text;
 using Lucene.Net.Analysis.Util;
+using Lucene.Net.Attributes;
 using Lucene.Net.Support;
 using Lucene.Net.Util;
 using NUnit.Framework;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using System.Text;
 using JCG = J2N.Collections.Generic;
 
@@ -32,7 +37,7 @@ namespace Lucene.Net.Analysis.Util
     {
         public virtual void DoRandom(int iter, bool ignoreCase)
         {
-            CharArrayMap<int?> map = new CharArrayMap<int?>(TEST_VERSION_CURRENT, 1, ignoreCase);
+            CharArrayDictionary<int?> map = new CharArrayDictionary<int?>(TEST_VERSION_CURRENT, 1, ignoreCase);
             IDictionary<string, int?> hmap = new JCG.Dictionary<string, int?>();
 
             char[] key;
@@ -49,16 +54,16 @@ namespace Lucene.Net.Analysis.Util
 
                 int val = Random.Next();
 
-                object o1 = map.Put(key, val);
-                object o2 = hmap.Put(hmapKey, val);
+                map.Put(key, val, out int? o1);
+                var o2 = hmap.Put(hmapKey, val);
                 assertEquals(o1, o2);
 
                 // add it again with the string method
-                assertEquals(val, map.Put(keyStr, val));
+                assertEquals(val, map.Put(keyStr, val, out int? previousValue) ? null : previousValue);
 
-                assertEquals(val, map.Get(key, 0, key.Length));
-                assertEquals(val, map.Get(key));
-                assertEquals(val, map.Get(keyStr));
+                assertEquals(val, map[key, 0, key.Length]); // LUCENENET: Changed Get() to this[]
+                assertEquals(val, map[key]); // LUCENENET: Changed Get() to this[]
+                assertEquals(val, map[keyStr]); // LUCENENET: Changed Get() to this[]
 
                 assertEquals(hmap.Count, map.size());
             }
@@ -78,7 +83,7 @@ namespace Lucene.Net.Analysis.Util
         [Test]
         public virtual void TestMethods()
         {
-            CharArrayMap<int?> cm = new CharArrayMap<int?>(TEST_VERSION_CURRENT, 2, false);
+            CharArrayDictionary<int?> cm = new CharArrayDictionary<int?>(TEST_VERSION_CURRENT, 2, false);
             Dictionary<string, int?> hm = new Dictionary<string, int?>();
             hm["foo"] = 1;
             hm["bar"] = 2;
@@ -88,8 +93,7 @@ namespace Lucene.Net.Analysis.Util
             cm.PutAll(hm);
             assertEquals(hm.Count, cm.Count);
 
-            // LUCENENET: Need to cast here - no implicit conversion.
-            CharArraySet cs = cm.Keys as CharArraySet;
+            CharArraySet cs = cm.Keys;
             int n = 0;
             foreach (string o in cs)
             {
@@ -116,18 +120,16 @@ namespace Lucene.Net.Analysis.Util
             cm.PutAll(hm);
             assertEquals(hm.Count, cs.Count);
             assertEquals(cm.Count, cs.Count);
-            // LUCENENET: Need to cast here - no implicit conversion
-            IEnumerator<KeyValuePair<string, int?>> iter1 = (IEnumerator<KeyValuePair<string, int?>>)cm.EntrySet().GetEnumerator();
+            CharArrayDictionary<int?>.Enumerator iter1 = cm.GetEnumerator();
             n = 0;
             while (iter1.MoveNext())
             {
                 KeyValuePair<string, int?> entry = iter1.Current;
                 object key = entry.Key;
                 int? val = entry.Value;
-                assertEquals(cm.Get(key), val);
-                // LUCENENET: Need a cast to get to this method because it is not part of the IEnumerator<T> interface
-                ((CharArrayMap<int?>.EntryIterator)iter1).SetValue(val * 100);
-                assertEquals(val * 100, (int)cm.Get(key));
+                assertEquals(cm[key], val); // LUCENENET: Changed Get() to this[]
+                iter1.SetValue(val * 100);
+                assertEquals(val * 100, (int)cm[key]); // LUCENENET: Changed Get() to this[]
                 n++;
             }
             assertEquals(hm.Count, n);
@@ -135,7 +137,7 @@ namespace Lucene.Net.Analysis.Util
             cm.PutAll(hm);
             assertEquals(cm.size(), n);
 
-            CharArrayMap<int?>.EntryIterator iter2 = cm.EntrySet().GetEnumerator() as CharArrayMap<int?>.EntryIterator;
+            CharArrayDictionary<int?>.Enumerator iter2 = cm.GetEnumerator();
             n = 0;
             while (iter2.MoveNext())
             {
@@ -143,72 +145,73 @@ namespace Lucene.Net.Analysis.Util
                 int? val = iter2.Current.Value;
                 assertEquals(hm[keyc], val);
                 iter2.SetValue(val * 100);
-                assertEquals(val * 100, (int)cm.Get(keyc));
+                assertEquals(val * 100, (int)cm[keyc]); // LUCENENET: Changed Get() to this[]
                 n++;
             }
             assertEquals(hm.Count, n);
 
-            cm.EntrySet().Clear();
+            //cm.EntrySet().Clear(); // LUCENENET: Removed EntrySet() method because .NET uses the dictionary instance
+            cm.Clear();
             assertEquals(0, cm.size());
-            assertEquals(0, cm.EntrySet().size());
+            //assertEquals(0, cm.EntrySet().size()); // LUCENENET: Removed EntrySet() method because .NET uses the dictionary instance
             assertTrue(cm.Count == 0);
         }
 
         [Test]
         public void TestModifyOnUnmodifiable()
         {
-            CharArrayMap<int?> map = new CharArrayMap<int?>(TEST_VERSION_CURRENT, 2, false);
-            map.Put("foo", 1);
-            map.Put("bar", 2);
+            CharArrayDictionary<int?> map = new CharArrayDictionary<int?>(TEST_VERSION_CURRENT, 2, false);
+            map.Put("foo", 1, out _);
+            map.Put("bar", 2, out _);
             int size = map.Count;
             assertEquals(2, size);
             assertTrue(map.ContainsKey("foo"));
-            assertEquals(1, map.Get("foo"));
+            assertEquals(1, map["foo"]); // LUCENENET: Changed Get() to this[]
             assertTrue(map.ContainsKey("bar"));
-            assertEquals(2, map.Get("bar"));
+            assertEquals(2, map["bar"]); // LUCENENET: Changed Get() to this[]
 
             map = map.AsReadOnly();
             assertEquals("Map size changed due to unmodifiableMap call", size, map.Count);
             var NOT_IN_MAP = "SirGallahad";
             assertFalse("Test String already exists in map", map.ContainsKey(NOT_IN_MAP));
-            assertNull("Test String already exists in map", map.Get(NOT_IN_MAP));
+            assertFalse("Test String already exists in map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
 
             try
             {
-                map.Put(NOT_IN_MAP.ToCharArray(), 3);
+                map.Put(NOT_IN_MAP.ToCharArray(), 3, out _);
                 fail("Modified unmodifiable map");
             }
             catch (Exception e) when (e.IsUnsupportedOperationException())
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.Count);
             }
 
             try
             {
-                map.Put(NOT_IN_MAP, 3);
+                map.Put(NOT_IN_MAP, 3, out _);
                 fail("Modified unmodifiable map");
             }
             catch (Exception e) when (e.IsUnsupportedOperationException())
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.Count);
             }
 
             try
             {
-                map.Put(new StringBuilder(NOT_IN_MAP), 3);
+                map.Put(new StringBuilder(NOT_IN_MAP), 3, out _);
                 fail("Modified unmodifiable map");
             }
             catch (Exception e) when (e.IsUnsupportedOperationException())
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.Count);
             }
 
@@ -222,20 +225,20 @@ namespace Lucene.Net.Analysis.Util
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.Count);
             }
 
             try
             {
-                map.Add(new KeyValuePair<string, int?>(NOT_IN_MAP, 3));
+                ((IDictionary<string, int?>)map).Add(new KeyValuePair<string, int?>(NOT_IN_MAP, 3));
                 fail("Modified unmodifiable map");
             }
             catch (Exception e) when (e.IsUnsupportedOperationException())
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.Count);
             }
 
@@ -248,15 +251,13 @@ namespace Lucene.Net.Analysis.Util
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.Count);
             }
 
             try
             {
-#pragma warning disable 612, 618
-                map.Remove(new KeyValuePair<string, int?>("foo", 1));
-#pragma warning restore 612, 618
+                ((IDictionary<string, int?>)map).Remove(new KeyValuePair<string, int?>("foo", 1));
                 fail("Modified unmodifiable map");
             }
             catch (Exception e) when (e.IsUnsupportedOperationException())
@@ -279,7 +280,8 @@ namespace Lucene.Net.Analysis.Util
 
             try
             {
-                map.EntrySet().Clear();
+                //map.EntrySet().Clear(); // LUCENENET: Removed EntrySet() method because .NET uses the dictionary instance
+                map.Clear();
                 fail("Modified unmodifiable map");
             }
             catch (Exception e) when (e.IsUnsupportedOperationException())
@@ -301,14 +303,14 @@ namespace Lucene.Net.Analysis.Util
 
             try
             {
-                map.Put((object)NOT_IN_MAP, 3);
+                map.Put((object)NOT_IN_MAP, 3, out _);
                 fail("Modified unmodifiable map");
             }
             catch (Exception e) when (e.IsUnsupportedOperationException())
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.size());
             }
 
@@ -321,29 +323,364 @@ namespace Lucene.Net.Analysis.Util
             {
                 // expected
                 assertFalse("Test String has been added to unmodifiable map", map.ContainsKey(NOT_IN_MAP));
-                assertNull("Test String has been added to unmodifiable map", map.Get(NOT_IN_MAP));
+                assertFalse("Test String has been added to unmodifiable map", map.TryGetValue(NOT_IN_MAP, out int? _)); // LUCENENET: Changed Get() to TryGetValue()
                 assertEquals("Size of unmodifiable map has changed", size, map.size());
             }
 
             assertTrue(map.ContainsKey("foo"));
-            assertEquals(1, map.Get("foo"));
+            assertEquals(1, map["foo"]); // LUCENENET: Changed Get() to this[]
             assertTrue(map.ContainsKey("bar"));
-            assertEquals(2, map.Get("bar"));
+            assertEquals(2, map["bar"]); // LUCENENET: Changed Get() to this[]
         }
 
         [Test]
         public virtual void TestToString()
         {
-            CharArrayMap<int?> cm = new CharArrayMap<int?>(TEST_VERSION_CURRENT, Collections.SingletonMap<string, int?>("test", 1), false);
+            CharArrayDictionary<int?> cm = new CharArrayDictionary<int?>(TEST_VERSION_CURRENT, Collections.SingletonMap<string, int?>("test", 1), false);
             assertEquals("[test]", cm.Keys.ToString());
             assertEquals("[1]", cm.Values.ToString());
-            assertEquals("[test=1]", cm.EntrySet().ToString());
+            //assertEquals("[test=1]", cm.EntrySet().ToString()); // LUCENENET: Removed EntrySet() method because .NET uses the dictionary instance
             assertEquals("{test=1}", cm.ToString());
-            cm.Put("test2", 2);
+            cm["test2"] = 2; // LUCENENET: Changed Put() to this[]
             assertTrue(cm.Keys.ToString().Contains(", "));
             assertTrue(cm.Values.ToString().Contains(", "));
-            assertTrue(cm.EntrySet().ToString().Contains(", "));
+            //assertTrue(cm.EntrySet().ToString().Contains(", ")); // LUCENENET: Removed EntrySet() method because .NET uses the dictionary instance
             assertTrue(cm.ToString().Contains(", "));
         }
+
+        [Test, LuceneNetSpecific]
+        public virtual void TestIsReadOnly()
+        {
+            CharArrayDictionary<int?> target = new CharArrayDictionary<int?>(TEST_VERSION_CURRENT, Collections.SingletonMap<string, int?>("test", 1), false);
+            CharArrayDictionary<int?> readOnlyTarget = target.AsReadOnly();
+
+            assertFalse(target.IsReadOnly);
+            assertTrue(target.Keys.IsReadOnly); // KeyCollection is always read-only
+            assertTrue(readOnlyTarget.IsReadOnly);
+            assertTrue(readOnlyTarget.Keys.IsReadOnly);
+        }
+
+        [Test, LuceneNetSpecific]
+        public virtual void TestEnumeratorExceptions()
+        {
+            CharArrayDictionary<int?> map = new CharArrayDictionary<int?>(TEST_VERSION_CURRENT, 3, ignoreCase: false)
+            {
+                ["foo"] = 0,
+                ["bar"] = 0,
+                ["baz"] = 0,
+            };
+
+            // Checks to ensure our Current property throws when outside of the enumeration
+            using (var iter = map.GetEnumerator())
+            {
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.Current; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentKey; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentKeyCharSequence; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentKeyString; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentValue; });
+
+                while (iter.MoveNext())
+                {
+                    Assert.DoesNotThrow(() => { var _ = iter.Current; });
+                    Assert.DoesNotThrow(() => { var _ = iter.CurrentKey; });
+                    Assert.DoesNotThrow(() => { var _ = iter.CurrentKeyCharSequence; });
+                    Assert.DoesNotThrow(() => { var _ = iter.CurrentKeyString; });
+                    Assert.DoesNotThrow(() => { var _ = iter.CurrentValue; });
+                }
+
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.Current; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentKey; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentKeyCharSequence; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentKeyString; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentValue; });
+            }
+
+            using (var ours = map.GetEnumerator())
+            {
+                using var theirs = map.GetEnumerator();
+
+                assertTrue(ours.MoveNext());
+                Assert.DoesNotThrow(() => theirs.MoveNext());
+
+                assertTrue(ours.MoveNext());
+                ours.SetValue(1);
+                Assert.Throws<InvalidOperationException>(() => theirs.MoveNext());
+
+                Assert.DoesNotThrow(() => ours.MoveNext());
+                Assert.DoesNotThrow(() => ours.SetValue(1));
+                Assert.Throws<InvalidOperationException>(() => theirs.MoveNext());
+            }
+
+            using (var ours = map.GetEnumerator())
+            {
+                using var theirs = map.GetEnumerator();
+
+                assertTrue(ours.MoveNext());
+                ours.SetValue(1);
+                Assert.Throws<InvalidOperationException>(() => theirs.MoveNext());
+
+                Assert.DoesNotThrow(() => ours.MoveNext());
+                Assert.DoesNotThrow(() => { map["baz"] = 2; });
+                Assert.Throws<InvalidOperationException>(() => theirs.MoveNext());
+                Assert.Throws<InvalidOperationException>(() => ours.MoveNext());
+            }
+
+            using (var ours = map.GetEnumerator())
+            {
+                using var theirs = map.GetEnumerator();
+
+                assertTrue(ours.MoveNext());
+                ours.SetValue(1);
+                Assert.Throws<InvalidOperationException>(() => theirs.MoveNext());
+
+                Assert.DoesNotThrow(() => ours.MoveNext());
+                Assert.DoesNotThrow(() => { map.Clear(); });
+                Assert.Throws<InvalidOperationException>(() => theirs.MoveNext());
+                Assert.Throws<InvalidOperationException>(() => ours.MoveNext());
+
+                Assert.Throws<InvalidOperationException>(() => { var _ = ours.Current; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = ours.CurrentKey; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = ours.CurrentKeyCharSequence; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = ours.CurrentKeyString; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = ours.CurrentValue; });
+            }
+        }
+
+        [Test, LuceneNetSpecific]
+        public virtual void TestKeyCollectionEnumeratorExceptions()
+        {
+            var map = new CharArrayDictionary<int?>(TEST_VERSION_CURRENT, 3, ignoreCase: false)
+            {
+                ["foo"] = 0,
+                ["bar"] = 0,
+                ["baz"] = 0,
+            };
+
+
+            // Checks to ensure our Current property throws when outside of the enumeration
+            using (var iter = map.Keys.GetEnumerator())
+            {
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.Current; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentValue; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentValueCharSequence; });
+
+                while (iter.MoveNext())
+                {
+                    Assert.DoesNotThrow(() => { var _ = iter.Current; });
+                    Assert.DoesNotThrow(() => { var _ = iter.CurrentValue; });
+                    Assert.DoesNotThrow(() => { var _ = iter.CurrentValueCharSequence; });
+                }
+
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.Current; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentValue; });
+                Assert.Throws<InvalidOperationException>(() => { var _ = iter.CurrentValueCharSequence; });
+            }
+
+            using (var ours = map.Keys.GetEnumerator())
... 1335 lines suppressed ...