PERFORMANCE: Lucene.Net.Search.FieldCacheImpl: Reduced casting/boxing by making FieldCacheImpl.Cache and FieldCacheImpl.CacheKey classes generic. Removed unnecessary loops in PurgeByCacheKey() and GetCacheEntries().
diff --git a/src/Lucene.Net/Search/FieldCache.cs b/src/Lucene.Net/Search/FieldCache.cs
index 2675541..7acd2b1 100644
--- a/src/Lucene.Net/Search/FieldCache.cs
+++ b/src/Lucene.Net/Search/FieldCache.cs
@@ -995,8 +995,15 @@
 
             public Type CacheType => cacheType;
 
+            [Obsolete("Use Parser and AcceptableOverheadRatio instead to eliminate boxing. This property will be removed in 4.8.0 release candidate."), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
             public object Custom => custom;
 
+            // LUCENENET specific - Added property for when custom is an IParser
+            public IParser Parser => custom as IParser;
+
+            // LUCENENET specific - Added AcceptableOverheadRatio to eliminate boxing/unboxing
+            public float? AcceptableOverheadRatio => custom is AcceptableOverheadRatio ratio ? ratio.Value : (float?)null;
+
             public object Value => value;
 
             /// <summary>
@@ -1020,11 +1027,11 @@
                 StringBuilder b = new StringBuilder();
                 b.Append("'").Append(ReaderKey).Append("'=>");
                 b.Append("'").Append(FieldName).Append("',");
-                b.Append(CacheType).Append(",").Append(Custom);
+                b.Append(CacheType).Append(",").Append(custom is null ? "null" : custom.ToString()); // LUCENENET specific: use field instead of property
                 b.Append("=>").Append(Value.GetType().FullName).Append("#");
                 b.Append(RuntimeHelpers.GetHashCode(Value));
 
-                String s = EstimatedSize;
+                string s = EstimatedSize;
                 if (null != s)
                 {
                     b.Append(" (size =~ ").Append(s).Append(')');
@@ -1033,5 +1040,39 @@
                 return b.ToString();
             }
         }
+
+        // LUCENENET specific reference type to be used to store float value in the field cache
+        // to avoid boxing/unboxing.
+        internal class AcceptableOverheadRatio
+        {
+            public AcceptableOverheadRatio(float value)
+            {
+                Value = value;
+            }
+
+            public float Value { get; }
+
+            public override bool Equals(object obj)
+            {
+                if (obj is AcceptableOverheadRatio)
+                {
+#pragma warning disable IDE0020 // Use pattern matching
+                    AcceptableOverheadRatio other = (AcceptableOverheadRatio)obj;
+#pragma warning restore IDE0020 // Use pattern matching
+                    return Value.Equals(other.Value);
+                }
+                return false;
+            }
+
+            public override int GetHashCode()
+            {
+                return Value.GetHashCode();
+            }
+
+            public override string ToString()
+            {
+                return Value.ToString();
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net/Search/FieldCacheImpl.cs b/src/Lucene.Net/Search/FieldCacheImpl.cs
index 26908d5..f5bb0dd 100644
--- a/src/Lucene.Net/Search/FieldCacheImpl.cs
+++ b/src/Lucene.Net/Search/FieldCacheImpl.cs
@@ -59,16 +59,16 @@
     internal class FieldCacheImpl : IFieldCache
     {
         // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-        private Cache caches_typeof_sbyte;
-        private Cache caches_typeof_short;
-        private Cache caches_typeof_int;
-        private Cache caches_typeof_float;
-        private Cache caches_typeof_long;
-        private Cache caches_typeof_double;
-        private Cache caches_typeof_BinaryDocValues;
-        private Cache caches_typeof_SortedDocValues;
-        private Cache caches_typeof_DocTermOrds;
-        private Cache caches_typeof_DocsWithFieldCache;
+        private ByteCache caches_typeof_sbyte;
+        private Int16Cache caches_typeof_short;
+        private Int32Cache caches_typeof_int;
+        private SingleCache caches_typeof_float;
+        private Int64Cache caches_typeof_long;
+        private DoubleCache caches_typeof_double;
+        private BinaryDocValuesCache caches_typeof_BinaryDocValues;
+        private SortedDocValuesCache caches_typeof_SortedDocValues;
+        private DocTermOrdsCache caches_typeof_DocTermOrds;
+        private DocsWithFieldCache caches_typeof_DocsWithFieldCache;
         internal FieldCacheImpl()
         {
             Init();
@@ -80,20 +80,19 @@
 
         private void Init()
         {
-            lock (this)
-            {
-                // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                caches_typeof_sbyte              = new ByteCache(this);
-                caches_typeof_short              = new Int16Cache(this);
-                caches_typeof_int                = new Int32Cache(this);
-                caches_typeof_float              = new SingleCache(this);
-                caches_typeof_long               = new Int64Cache(this);
-                caches_typeof_double             = new DoubleCache(this);
-                caches_typeof_BinaryDocValues    = new BinaryDocValuesCache(this);
-                caches_typeof_SortedDocValues    = new SortedDocValuesCache(this);
-                caches_typeof_DocTermOrds        = new DocTermOrdsCache(this);
-                caches_typeof_DocsWithFieldCache = new DocsWithFieldCache(this);
-            }
+            // LUCENENET specific - removed unnecessary lock during construction
+
+            // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
+            caches_typeof_sbyte              = new ByteCache(this);
+            caches_typeof_short              = new Int16Cache(this);
+            caches_typeof_int                = new Int32Cache(this);
+            caches_typeof_float              = new SingleCache(this);
+            caches_typeof_long               = new Int64Cache(this);
+            caches_typeof_double             = new DoubleCache(this);
+            caches_typeof_BinaryDocValues    = new BinaryDocValuesCache(this);
+            caches_typeof_SortedDocValues    = new SortedDocValuesCache(this);
+            caches_typeof_DocTermOrds        = new DocTermOrdsCache(this);
+            caches_typeof_DocsWithFieldCache = new DocsWithFieldCache(this);
         }
 
         public virtual void PurgeAllCaches()
@@ -104,66 +103,65 @@
             }
         }
 
-        // LUCENENET specific - added GetCaches() to allow looping over the caches even though they are no longer in a collection
-        private IEnumerable<KeyValuePair<Type, Cache>> GetCaches()
-        {
-            yield return new KeyValuePair<Type, Cache>(typeof(sbyte), caches_typeof_sbyte);
-            yield return new KeyValuePair<Type, Cache>(typeof(short), caches_typeof_short);
-            yield return new KeyValuePair<Type, Cache>(typeof(int), caches_typeof_int);
-            yield return new KeyValuePair<Type, Cache>(typeof(float), caches_typeof_float);
-            yield return new KeyValuePair<Type, Cache>(typeof(long), caches_typeof_long);
-            yield return new KeyValuePair<Type, Cache>(typeof(double), caches_typeof_double);
-            yield return new KeyValuePair<Type, Cache>(typeof(BinaryDocValues), caches_typeof_BinaryDocValues);
-            yield return new KeyValuePair<Type, Cache>(typeof(SortedDocValues), caches_typeof_SortedDocValues);
-            yield return new KeyValuePair<Type, Cache>(typeof(DocTermOrds), caches_typeof_DocTermOrds);
-            yield return new KeyValuePair<Type, Cache>(typeof(DocsWithFieldCache), caches_typeof_DocsWithFieldCache);
-        }
-
         public virtual void PurgeByCacheKey(object coreCacheKey)
         {
             lock (this)
             {
-                // LUCENENET specific - added GetCaches() to allow looping over the caches even though they are no longer in a collection
-                foreach (var kv in GetCaches())
-                {
-                    kv.Value.PurgeByCacheKey(coreCacheKey);
-                }
+                // LUCENENET specific - removed unnecessary Dictionary and loop
+                caches_typeof_sbyte.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_short.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_int.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_float.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_long.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_double.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_BinaryDocValues.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_SortedDocValues.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_DocTermOrds.PurgeByCacheKey(coreCacheKey);
+                caches_typeof_DocsWithFieldCache.PurgeByCacheKey(coreCacheKey);
             }
         }
 
         public virtual FieldCache.CacheEntry[] GetCacheEntries()
         {
+            // LUCENENET specific - instantiate/ToArray() outside of lock to improve performance
+            IList<FieldCache.CacheEntry> result = new List<FieldCache.CacheEntry>(17);
             lock (this)
             {
-                IList<FieldCache.CacheEntry> result = new List<FieldCache.CacheEntry>(17);
-                // LUCENENET specific - added GetCaches() to allow looping over the caches even though they are no longer in a collection
-                foreach (var cacheEntry in GetCaches())
+                // LUCENENET specific - refactored to use generic CacheKey to reduce casting and removed unnecessary Dictionary/loop
+                AddCacheEntries(result, typeof(sbyte), caches_typeof_sbyte);
+                AddCacheEntries(result, typeof(short), caches_typeof_short);
+                AddCacheEntries(result, typeof(int), caches_typeof_int);
+                AddCacheEntries(result, typeof(float), caches_typeof_float);
+                AddCacheEntries(result, typeof(long), caches_typeof_long);
+                AddCacheEntries(result, typeof(double), caches_typeof_double);
+                AddCacheEntries(result, typeof(BinaryDocValues), caches_typeof_BinaryDocValues);
+                AddCacheEntries(result, typeof(SortedDocValues), caches_typeof_SortedDocValues);
+                AddCacheEntries(result, typeof(DocTermOrds), caches_typeof_DocTermOrds);
+                AddCacheEntries(result, typeof(DocsWithFieldCache), caches_typeof_DocsWithFieldCache);
+            }
+            return result.ToArray();
+        }
+
+        private void AddCacheEntries<TCacheKey>(IList<FieldCache.CacheEntry> result, Type cacheType, Cache<TCacheKey> cache) where TCacheKey : CacheKey
+        {
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+            lock (cache.readerCache)
+#endif
+            {
+                foreach (var readerCacheEntry in cache.readerCache)
                 {
-                    Cache cache = cacheEntry.Value;
-                    Type cacheType = cacheEntry.Key;
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-                    lock (cache.readerCache)
+                    object readerKey = readerCacheEntry.Key;
+                    if (readerKey is null)
                     {
-#endif
-                        foreach (var readerCacheEntry in cache.readerCache)
-                        {
-                            object readerKey = readerCacheEntry.Key;
-                            if (readerKey == null)
-                            {
-                                continue;
-                            }
-                            IDictionary<CacheKey, object> innerCache = readerCacheEntry.Value;
-                            foreach (KeyValuePair<CacheKey, object> mapEntry in innerCache)
-                            {
-                                CacheKey entry = mapEntry.Key;
-                                result.Add(new FieldCache.CacheEntry(readerKey, entry.field, cacheType, entry.custom, mapEntry.Value));
-                            }
-                        }
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+                        continue;
                     }
-#endif
+                    IDictionary<TCacheKey, object> innerCache = readerCacheEntry.Value;
+                    foreach (KeyValuePair<TCacheKey, object> mapEntry in innerCache)
+                    {
+                        TCacheKey entry = mapEntry.Key;
+                        result.Add(new FieldCache.CacheEntry(readerKey, entry.field, cacheType, entry.Custom, mapEntry.Value));
+                    }
                 }
-                return result.ToArray();
             }
         }
 
@@ -229,7 +227,7 @@
 
         /// <summary>
         /// Expert: Internal cache. </summary>
-        internal abstract class Cache
+        internal abstract class Cache<TCacheKey> where TCacheKey : CacheKey
         {
             internal Cache(FieldCacheImpl wrapper)
             {
@@ -239,12 +237,12 @@
             internal readonly FieldCacheImpl wrapper;
 
 #if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-            internal ConditionalWeakTable<object, ConcurrentDictionary<CacheKey, object>> readerCache = new ConditionalWeakTable<object, ConcurrentDictionary<CacheKey, object>>();
+            internal ConditionalWeakTable<object, ConcurrentDictionary<TCacheKey, object>> readerCache = new ConditionalWeakTable<object, ConcurrentDictionary<TCacheKey, object>>();
 #else
-            internal WeakDictionary<object, ConcurrentDictionary<CacheKey, object>> readerCache = new WeakDictionary<object, ConcurrentDictionary<CacheKey, object>>();
+            internal WeakDictionary<object, ConcurrentDictionary<TCacheKey, object>> readerCache = new WeakDictionary<object, ConcurrentDictionary<TCacheKey, object>>();
 #endif
 
-            protected abstract object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField);
+            protected abstract object CreateValue(AtomicReader reader, TCacheKey key, bool setDocsWithField);
 
             /// <summary>
             /// Remove this reader from the cache, if present. </summary>
@@ -260,16 +258,16 @@
             /// Sets the key to the value for the provided reader;
             /// if the key is already set then this doesn't change it.
             /// </summary>
-            public virtual void Put(AtomicReader reader, CacheKey key, object value)
+            public virtual void Put(AtomicReader reader, TCacheKey key, object value)
             {
-                ConcurrentDictionary<CacheKey, object> innerCache;
+                ConcurrentDictionary<TCacheKey, object> innerCache;
                 object readerKey = reader.CoreCacheKey;
 #if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
                 innerCache = readerCache.GetValue(readerKey, (readerKey) =>
                 {
                     // First time this reader is using FieldCache
                     wrapper.InitReader(reader);
-                    return new ConcurrentDictionary<CacheKey, object>
+                    return new ConcurrentDictionary<TCacheKey, object>
                     {
                         [key] = value
                     };
@@ -280,7 +278,7 @@
                     if (!readerCache.TryGetValue(readerKey, out innerCache) || innerCache == null)
                     {
                         // First time this reader is using FieldCache
-                        innerCache = new ConcurrentDictionary<CacheKey, object>
+                        innerCache = new ConcurrentDictionary<TCacheKey, object>
                         {
                             [key] = value
                         };
@@ -293,16 +291,16 @@
                 innerCache.TryAdd(key, value);
             }
 
-            public virtual object Get(AtomicReader reader, CacheKey key, bool setDocsWithField)
+            public virtual object Get(AtomicReader reader, TCacheKey key, bool setDocsWithField)
             {
-                ConcurrentDictionary<CacheKey, object> innerCache;
+                ConcurrentDictionary<TCacheKey, object> innerCache;
                 object readerKey = reader.CoreCacheKey;
 #if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
                 innerCache = readerCache.GetValue(readerKey, (readerKey) =>
                 {
                     // First time this reader is using FieldCache
                     wrapper.InitReader(reader);
-                    return new ConcurrentDictionary<CacheKey, object>
+                    return new ConcurrentDictionary<TCacheKey, object>
                     {
                         [key] = new FieldCache.CreationPlaceholder()
                     };
@@ -314,7 +312,7 @@
                     if (!readerCache.TryGetValue(readerKey, out innerCache) || innerCache == null)
                     {
                         // First time this reader is using FieldCache
-                        innerCache = new ConcurrentDictionary<CacheKey, object>
+                        innerCache = new ConcurrentDictionary<TCacheKey, object>
                         {
                             [key] = new FieldCache.CreationPlaceholder()
                         };
@@ -336,7 +334,7 @@
                                 // Only check if key.custom (the parser) is
                                 // non-null; else, we check twice for a single
                                 // call to FieldCache.getXXX
-                                if (key.custom != null && wrapper != null)
+                                if (!(key.Custom is null) && wrapper != null)
                                 {
                                     TextWriter infoStream = wrapper.InfoStream;
                                     if (infoStream != null)
@@ -378,32 +376,85 @@
         /// Expert: Every composite-key in the internal cache is of this type. </summary>
         internal class CacheKey
         {
-            internal readonly string field; // which Field 
-            internal readonly object custom; // which custom comparer or parser 
+            internal readonly string field; // which Field
+            // LUCENENET specific - moved 'custom' to generic class so we don't have to deal with casting/boxing
 
             /// <summary>
             /// Creates one of these objects for a custom comparer/parser. </summary>
-            internal CacheKey(string field, object custom)
+            internal CacheKey(string field)
             {
                 this.field = field;
-                this.custom = custom;
             }
 
+            // LUCENENET specific - Added this property to add this value to a FieldCache.CacheEntry without
+            // knowing its generic closing type.
+            public virtual object Custom => null;
+
             /// <summary>
             /// Two of these are equal if they reference the same field and type. </summary>
             public override bool Equals(object o)
             {
                 if (o is CacheKey)
                 {
+#pragma warning disable IDE0020 // Use pattern matching
                     CacheKey other = (CacheKey)o;
+#pragma warning restore IDE0020 // Use pattern matching
                     if (other.field.Equals(field, StringComparison.Ordinal))
                     {
-                        if (other.custom == null)
+                        if (other.Custom is null)
                         {
-                            if (custom == null)
-                            {
-                                return true;
-                            }
+                            return Custom is null;
+                        }
+                        else if (other.Custom.Equals(Custom))
+                        {
+                            return true;
+                        }
+                    }
+                }
+                return false;
+            }
+
+            /// <summary>
+            /// Composes a hashcode based on the field and type. </summary>
+#pragma warning disable IDE0070 // Use 'System.HashCode'
+            public override int GetHashCode()
+#pragma warning restore IDE0070 // Use 'System.HashCode'
+            {
+                return field.GetHashCode();
+            }
+        }
+
+        /// <summary>
+        /// Expert: Every composite-key in the internal cache is of this type. </summary>
+        // LUCENENET specific - Added generic parameter to eliminate casting/boxing
+        internal class CacheKey<TCustom> : CacheKey
+        {
+            internal readonly TCustom custom; // which custom comparer or parser 
+
+            /// <summary>
+            /// Creates one of these objects for a custom comparer/parser. </summary>
+            internal CacheKey(string field, TCustom custom)
+                : base(field)
+            {
+                this.custom = custom;
+            }
+
+            public override object Custom => custom;
+
+            /// <summary>
+            /// Two of these are equal if they reference the same field and type. </summary>
+            public override bool Equals(object o)
+            {
+                if (o is CacheKey<TCustom>)
+                {
+#pragma warning disable IDE0020 // Use pattern matching
+                    CacheKey<TCustom> other = (CacheKey<TCustom>)o;
+#pragma warning restore IDE0020 // Use pattern matching
+                    if (other.field.Equals(field, StringComparison.Ordinal))
+                    {
+                        if (other.custom is null)
+                        {
+                            return custom is null;
                         }
                         else if (other.custom.Equals(custom))
                         {
@@ -509,7 +560,7 @@
                 bits = docsWithField;
             }
             // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-            caches_typeof_DocsWithFieldCache.Put(reader, new CacheKey(field, null), bits);
+            caches_typeof_DocsWithFieldCache.Put(reader, new CacheKey(field), bits);
         }
 
         /// <summary>
@@ -556,7 +607,9 @@
                     return FieldCache.Bytes.EMPTY;
                 }
                 // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                return (FieldCache.Bytes)caches_typeof_sbyte.Get(reader, new CacheKey(field, parser), setDocsWithField);
+#pragma warning disable CS0612 // Type or member is obsolete
+                return (FieldCache.Bytes)caches_typeof_sbyte.Get(reader, new CacheKey<FieldCache.IByteParser>(field, parser), setDocsWithField);
+#pragma warning restore CS0612 // Type or member is obsolete
             }
         }
 
@@ -593,19 +646,23 @@
             }
         }
 
-        internal sealed class ByteCache : Cache
+#pragma warning disable CS0612 // Type or member is obsolete
+        internal sealed class ByteCache : Cache<CacheKey<FieldCache.IByteParser>>
+#pragma warning restore CS0612 // Type or member is obsolete
         {
             internal ByteCache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField)
+#pragma warning disable CS0612 // Type or member is obsolete
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.IByteParser> key, bool setDocsWithField)
+#pragma warning restore CS0612 // Type or member is obsolete
             {
                 int maxDoc = reader.MaxDoc;
                 sbyte[] values;
 #pragma warning disable 612, 618
-                FieldCache.IByteParser parser = (FieldCache.IByteParser)key.custom;
+                FieldCache.IByteParser parser = key.custom;
 #pragma warning restore 612, 618
                 if (parser == null)
                 {
@@ -726,7 +783,7 @@
                     return FieldCache.Int16s.EMPTY;
                 }
                 // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                return (FieldCache.Int16s)caches_typeof_short.Get(reader, new CacheKey(field, parser), setDocsWithField);
+                return (FieldCache.Int16s)caches_typeof_short.Get(reader, new CacheKey<FieldCache.IInt16Parser>(field, parser), setDocsWithField);
             }
         }
 
@@ -769,19 +826,23 @@
         /// <summary>
         /// NOTE: This was ShortCache in Lucene
         /// </summary>
-        internal sealed class Int16Cache : Cache
+#pragma warning disable CS0612 // Type or member is obsolete
+        internal sealed class Int16Cache : Cache<CacheKey<FieldCache.IInt16Parser>>
+#pragma warning restore CS0612 // Type or member is obsolete
         {
             internal Int16Cache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField)
+#pragma warning disable CS0612 // Type or member is obsolete
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.IInt16Parser> key, bool setDocsWithField)
+#pragma warning restore CS0612 // Type or member is obsolete
             {
                 int maxDoc = reader.MaxDoc;
                 short[] values;
 #pragma warning disable 612, 618
-                FieldCache.IInt16Parser parser = (FieldCache.IInt16Parser)key.custom;
+                FieldCache.IInt16Parser parser = key.custom;
                 if (parser == null)
                 {
                     // Confusing: must delegate to wrapper (vs simply
@@ -900,7 +961,7 @@
                     return FieldCache.Int32s.EMPTY;
                 }
                 // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                return (FieldCache.Int32s)caches_typeof_int.Get(reader, new CacheKey(field, parser), setDocsWithField);
+                return (FieldCache.Int32s)caches_typeof_int.Get(reader, new CacheKey<FieldCache.IInt32Parser>(field, parser), setDocsWithField);
             }
         }
 
@@ -974,16 +1035,16 @@
         /// <summary>
         /// NOTE: This was IntCache in Lucene
         /// </summary>
-        internal sealed class Int32Cache : Cache
+        internal sealed class Int32Cache : Cache<CacheKey<FieldCache.IInt32Parser>>
         {
             internal Int32Cache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField)
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.IInt32Parser> key, bool setDocsWithField)
             {
-                FieldCache.IInt32Parser parser = (FieldCache.IInt32Parser)key.custom;
+                FieldCache.IInt32Parser parser = key.custom;
                 if (parser == null)
                 {
                     // Confusing: must delegate to wrapper (vs simply
@@ -1100,10 +1161,10 @@
                 return new Lucene.Net.Util.Bits.MatchNoBits(reader.MaxDoc);
             }
             // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-            return (IBits)caches_typeof_DocsWithFieldCache.Get(reader, new CacheKey(field, null), false);
+            return (IBits)caches_typeof_DocsWithFieldCache.Get(reader, new CacheKey(field), false);
         }
 
-        internal sealed class DocsWithFieldCache : Cache
+        internal sealed class DocsWithFieldCache : Cache<CacheKey>
         {
             internal DocsWithFieldCache(FieldCacheImpl wrapper)
                 : base(wrapper)
@@ -1201,7 +1262,7 @@
                     return FieldCache.Singles.EMPTY;
                 }
                 // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                return (FieldCache.Singles)caches_typeof_float.Get(reader, new CacheKey(field, parser), setDocsWithField);
+                return (FieldCache.Singles)caches_typeof_float.Get(reader, new CacheKey<FieldCache.ISingleParser>(field, parser), setDocsWithField);
             }
         }
 
@@ -1244,16 +1305,16 @@
         /// <summary>
         /// NOTE: This was FloatCache in Lucene
         /// </summary>
-        internal sealed class SingleCache : Cache
+        internal sealed class SingleCache : Cache<CacheKey<FieldCache.ISingleParser>>
         {
             internal SingleCache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField)
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.ISingleParser> key, bool setDocsWithField)
             {
-                FieldCache.ISingleParser parser = (FieldCache.ISingleParser)key.custom;
+                FieldCache.ISingleParser parser = key.custom;
                 if (parser == null)
                 {
                     // Confusing: must delegate to wrapper (vs simply
@@ -1373,7 +1434,7 @@
                     return FieldCache.Int64s.EMPTY;
                 }
                 // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                return (FieldCache.Int64s)caches_typeof_long.Get(reader, new CacheKey(field, parser), setDocsWithField);
+                return (FieldCache.Int64s)caches_typeof_long.Get(reader, new CacheKey<FieldCache.IInt64Parser>(field, parser), setDocsWithField);
             }
         }
 
@@ -1418,16 +1479,16 @@
         /// <summary>
         /// NOTE: This was LongCache in Lucene
         /// </summary>
-        internal sealed class Int64Cache : Cache
+        internal sealed class Int64Cache : Cache<CacheKey<FieldCache.IInt64Parser>>
         {
             internal Int64Cache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField)
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.IInt64Parser> key, bool setDocsWithField)
             {
-                FieldCache.IInt64Parser parser = (FieldCache.IInt64Parser)key.custom;
+                FieldCache.IInt64Parser parser = key.custom;
                 if (parser == null)
                 {
                     // Confusing: must delegate to wrapper (vs simply
@@ -1557,7 +1618,7 @@
                     return FieldCache.Doubles.EMPTY;
                 }
                 // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                return (FieldCache.Doubles)caches_typeof_double.Get(reader, new CacheKey(field, parser), setDocsWithField);
+                return (FieldCache.Doubles)caches_typeof_double.Get(reader, new CacheKey<FieldCache.IDoubleParser>(field, parser), setDocsWithField);
             }
         }
 
@@ -1594,16 +1655,16 @@
             }
         }
 
-        internal sealed class DoubleCache : Cache
+        internal sealed class DoubleCache : Cache<CacheKey<FieldCache.IDoubleParser>>
         {
             internal DoubleCache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField)
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.IDoubleParser> key, bool setDocsWithField)
             {
-                FieldCache.IDoubleParser parser = (FieldCache.IDoubleParser)key.custom;
+                FieldCache.IDoubleParser parser = key.custom;
                 if (parser == null)
                 {
                     // Confusing: must delegate to wrapper (vs simply
@@ -1753,24 +1814,24 @@
                     return DocValues.EMPTY_SORTED;
                 }
                 // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-                return (SortedDocValues)caches_typeof_SortedDocValues.Get(reader, new CacheKey(field, acceptableOverheadRatio), false);
+                return (SortedDocValues)caches_typeof_SortedDocValues.Get(reader, new CacheKey<FieldCache.AcceptableOverheadRatio>(field, new FieldCache.AcceptableOverheadRatio(acceptableOverheadRatio)), false);
             }
         }
 
-        internal class SortedDocValuesCache : Cache
+        internal class SortedDocValuesCache : Cache<CacheKey<FieldCache.AcceptableOverheadRatio>>
         {
             internal SortedDocValuesCache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField) // ignored
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.AcceptableOverheadRatio> key, bool setDocsWithField) // ignored
             {
                 int maxDoc = reader.MaxDoc;
 
                 Terms terms = reader.GetTerms(key.field);
 
-                float acceptableOverheadRatio = (float)((float?)key.custom);
+                float acceptableOverheadRatio = key.custom.Value;
 
                 PagedBytes bytes = new PagedBytes(15);
 
@@ -1920,17 +1981,17 @@
             }
 
             // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-            return (BinaryDocValues)caches_typeof_BinaryDocValues.Get(reader, new CacheKey(field, acceptableOverheadRatio), setDocsWithField);
+            return (BinaryDocValues)caches_typeof_BinaryDocValues.Get(reader, new CacheKey<FieldCache.AcceptableOverheadRatio>(field, new FieldCache.AcceptableOverheadRatio(acceptableOverheadRatio)), setDocsWithField);
         }
 
-        internal sealed class BinaryDocValuesCache : Cache
+        internal sealed class BinaryDocValuesCache : Cache<CacheKey<FieldCache.AcceptableOverheadRatio>>
         {
             internal BinaryDocValuesCache(FieldCacheImpl wrapper)
                 : base(wrapper)
             {
             }
 
-            protected override object CreateValue(AtomicReader reader, CacheKey key, bool setDocsWithField)
+            protected override object CreateValue(AtomicReader reader, CacheKey<FieldCache.AcceptableOverheadRatio> key, bool setDocsWithField)
             {
                 // TODO: would be nice to first check if DocTermsIndex
                 // was already cached for this field and then return
@@ -1939,7 +2000,7 @@
                 int maxDoc = reader.MaxDoc;
                 Terms terms = reader.GetTerms(key.field);
 
-                float acceptableOverheadRatio = (float)((float?)key.custom);
+                float acceptableOverheadRatio = key.custom.Value;
 
                 int termCountHardLimit = maxDoc;
 
@@ -2073,11 +2134,11 @@
             }
 
             // LUCENENET specific - eliminated unnecessary Dictionary lookup by declaring each cache as a member variable
-            DocTermOrds dto = (DocTermOrds)caches_typeof_DocTermOrds.Get(reader, new CacheKey(field, null), false);
+            DocTermOrds dto = (DocTermOrds)caches_typeof_DocTermOrds.Get(reader, new CacheKey(field), false);
             return dto.GetIterator(reader);
         }
 
-        internal sealed class DocTermOrdsCache : Cache
+        internal sealed class DocTermOrdsCache : Cache<CacheKey>
         {
             internal DocTermOrdsCache(FieldCacheImpl wrapper)
                 : base(wrapper)