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 b7f8a50  Factored out WeakDictionary in favor of weak events using 
Prism.Core (#613)
b7f8a50 is described below

commit b7f8a501f9096b911112f4c48b7eec5b7af5fb9b
Author: Shad Storhaug <[email protected]>
AuthorDate: Wed Feb 9 11:47:44 2022 +0700

    Factored out WeakDictionary in favor of weak events using Prism.Core (#613)
    
    * Lucene.Net.Runtime.CompilerServices: Added ConditionalWeakTableExtensions 
class to provide a workaround for the lack of AddOrUpdate on .NET Standard  2.0 
and prior
    
    * Lucene.Net.TestFramework.Index.ThreadedIndexingAndSearchingTestCase: 
Factored out WeakDictionary and use external locking to fixup AddOrUpdate where 
it is not supported.
    
    * Lucene.Net.TestFramework.Search.AssertingScorer: Factored out 
WeakDictionary and use external locking to fixup AddOrUpdate where it is not 
supported.
    
    * Lucene.Net.Util: Added ExcludeFromRamUsageEstimation attribute to exclude 
fields from being considered in the calculation
    
    * Lucene.Net.csproj: Added package reference on Prism.Core
    
    * Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader: Use Prism event 
aggregator to handle getting estimated RAM usage on .NET Standard 2.0 and .NET 
Framework
    
    * Lucene.Net.Search.CachingWrapperFilter: Use Prism event aggregator to 
retrieve weak DocIdSet references on .NET Standard 2.0 and .NET Framework
    
    * Lucene.Net.Index.IndexReader: Use Prism event aggregator to handle 
getting parent readers weakly on .NET Standard 2.0 and .NET Framework
    
    * Lucene.Net.Search.FieldCacheImpl: Use Prism event aggregator to handle 
AddCacheEntries by retrieving the keys via weak events and then updating the 
values one at a time on .NET Standard 2.0 and .NET Framework.
    
    * Deleted Lucene.Net.Support.WeakDictionary + tests (closes #256, closes 
#604, closes #605)
    
    * Lucene.Net.Support.Util: Created Events static class and migrated 
GetParentReadersEvent and GetCacheKeysEvent declarations to decouple references 
between consumers.
    
    * Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader: Changed to use 
GetCacheKeysEvent to avoid the need for a finalizer on CachedOrds and 
additional event declaration
    
    * Lucene.Net.Search.CachingWrapperFilter: Changed to use GetCacheKeysEvent 
to avoid the need for a finalizer on DocIdSet and an additional event 
declaration
    
    * Lucene.Net.Index.IndexReader: Reworked Prism events so they can be 
registered multiple times, and will track the registrations to clean them up 
during Dispose()
---
 .build/dependencies.props                          |   1 +
 .../Taxonomy/CachedOrdinalsReader.cs               |  51 +++-
 .../Index/ThreadedIndexingAndSearchingTestCase.cs  |  42 ++-
 .../Search/AssertingScorer.cs                      |  58 ++--
 src/Lucene.Net.Tests/Support/TestWeakDictionary.cs | 150 -----------
 .../Support/TestWeakDictionaryBehavior.cs          | 294 --------------------
 .../Support/TestWeakDictionaryPerformance.cs       | 133 ---------
 src/Lucene.Net/Index/IndexReader.cs                |  90 ++++++-
 src/Lucene.Net/Lucene.Net.csproj                   |   4 +-
 src/Lucene.Net/Search/CachingWrapperFilter.cs      |  65 +++--
 src/Lucene.Net/Search/DocIdSet.cs                  |   2 +-
 src/Lucene.Net/Search/FieldCacheImpl.cs            |  37 ++-
 .../ConditionalWeakTableExtensions.cs              |  53 ++++
 src/Lucene.Net/Support/Util/Events.cs              |  62 +++++
 .../Util/ExcludeFromRamUsageEstimationAttribute.cs |  29 ++
 src/Lucene.Net/Support/WeakDictionary.cs           | 300 ---------------------
 src/Lucene.Net/Util/RamUsageEstimator.cs           |   3 +-
 17 files changed, 419 insertions(+), 955 deletions(-)

diff --git a/.build/dependencies.props b/.build/dependencies.props
index 04da283..3245a77 100644
--- a/.build/dependencies.props
+++ b/.build/dependencies.props
@@ -71,6 +71,7 @@
     <NUnit3TestAdapterPackageVersion>3.17.0</NUnit3TestAdapterPackageVersion>
     <NUnitPackageVersion>3.13.1</NUnitPackageVersion>
     <OpenNLPNETPackageVersion>1.9.1.1</OpenNLPNETPackageVersion>
+    <PrismCorePackageVersion>7.2.0.1422</PrismCorePackageVersion>
     
<RandomizedTestingGeneratorsPackageVersion>2.7.8</RandomizedTestingGeneratorsPackageVersion>
     <SharpZipLibPackageVersion>1.1.0</SharpZipLibPackageVersion>
     <Spatial4nCorePackageVersion>0.4.1</Spatial4nCorePackageVersion>
diff --git a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs 
b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
index c9a9748..cd02336 100644
--- a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
+++ b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
@@ -1,10 +1,14 @@
 // Lucene version compatibility level 4.8.1
 using Lucene.Net.Support;
 using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
 using System;
+using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
-using System.Threading;
 
 namespace Lucene.Net.Facet.Taxonomy
 {
@@ -65,11 +69,12 @@ namespace Lucene.Net.Facet.Taxonomy
     {
         private readonly OrdinalsReader source;
 
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-        private readonly ConditionalWeakTable<object, CachedOrds> ordsCache = 
new ConditionalWeakTable<object, CachedOrds>();
-#else
-        private readonly WeakDictionary<object, CachedOrds> ordsCache = new 
WeakDictionary<object, CachedOrds>();
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+        // LUCENENET specific: Add weak event handler for .NET Standard 2.0 
and .NET Framework, since we don't have an enumerator to use
+        private readonly IEventAggregator eventAggregator = new 
EventAggregator();
 #endif
+
+        private readonly ConditionalWeakTable<object, CachedOrds> ordsCache = 
new ConditionalWeakTable<object, CachedOrds>();
         private readonly object syncLock = new object();
 
         /// <summary>
@@ -96,6 +101,10 @@ namespace Lucene.Net.Facet.Taxonomy
                     // which also means we don't need conditional compilation 
because ConditionalWeakTable
                     // doesn't support this[index].
                     ordsCache.Add(cacheKey, ords);
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+                    // LUCENENET specific: Add weak event handler for .NET 
Standard 2.0 and .NET Framework, since we don't have an enumerator to use
+                    
context.Reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<Events.GetCacheKeysEvent>());
+#endif
                 }
                 return ords;
             }
@@ -204,21 +213,41 @@ namespace Lucene.Net.Facet.Taxonomy
 
         public virtual long RamBytesUsed()
         {
+            // LUCENENET specific - moved Reflection calls outside of the lock 
(similar to CachingWrapperFilter) to improve performance.
+
+            // Sync only to pull the current set of values:
+            IList<CachedOrds> cachedOrdsList;
             UninterruptableMonitor.Enter(syncLock);
             try
             {
-                long bytes = 0;
+                cachedOrdsList = new List<CachedOrds>();
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
                 foreach (var pair in ordsCache)
-                {
-                    bytes += pair.Value.RamBytesUsed();
-                }
-
-                return bytes;
+                    cachedOrdsList.Add(pair.Value);
+#else
+                // LUCENENET specific - since .NET Standard 2.0 and .NET 
Framework don't have a CondtionalWeakTable enumerator,
+                // we use a weak event to retrieve the CachedOrds instances. 
We look each of these up here to avoid the need
+                // to attach events to the CachedOrds instances themselves 
(thus using the existing IndexReader.Dispose()
+                // method to detach the events rather than using a finalizer 
in CachedOrds to ensure they are cleaned up).
+                var e = new Events.GetCacheKeysEventArgs();
+                
eventAggregator.GetEvent<Events.GetCacheKeysEvent>().Publish(e);
+                foreach (var key in e.CacheKeys)
+                    if (ordsCache.TryGetValue(key, out CachedOrds value))
+                        cachedOrdsList.Add(value);
+#endif
             }
             finally
             {
                 UninterruptableMonitor.Exit(syncLock);
             }
+
+            long total = 0;
+            foreach (CachedOrds ord in cachedOrdsList)
+            {
+                total += ord.RamBytesUsed();
+            }
+
+            return total;
         }
     }
 }
\ No newline at end of file
diff --git 
a/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs 
b/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
index daf3fda..b2c2d82 100644
--- a/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
+++ b/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
@@ -4,6 +4,7 @@ using Lucene.Net.Analysis;
 using Lucene.Net.Diagnostics;
 using Lucene.Net.Documents;
 using Lucene.Net.Index.Extensions;
+using Lucene.Net.Runtime.CompilerServices;
 using Lucene.Net.Search;
 using Lucene.Net.Store;
 using Lucene.Net.Support;
@@ -461,9 +462,21 @@ namespace Lucene.Net.Index
                                 assertNotNull(source);
                                 if (source.Equals("merge", 
StringComparison.Ordinal))
                                 {
-                                    assertTrue("sub reader " + sub + " wasn't 
warmed: warmed=" + outerInstance.warmed + " diagnostics=" + diagnostics + " 
si=" + segReader.SegmentInfo,
-                                        // LUCENENET: ConditionalWeakTable 
doesn't have ContainsKey, so we normalize to TryGetValue
-                                        
!outerInstance.m_assertMergedSegmentsWarmed || 
outerInstance.warmed.TryGetValue(segReader.core, out BooleanRef _));
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+                                    
UninterruptableMonitor.Enter(outerInstance.warmedLock);
+                                    try
+                                    {
+#endif
+                                        assertTrue("sub reader " + sub + " 
wasn't warmed: warmed=" + outerInstance.warmed + " diagnostics=" + diagnostics 
+ " si=" + segReader.SegmentInfo,
+                                            // LUCENENET: ConditionalWeakTable 
doesn't have ContainsKey, so we normalize to TryGetValue
+                                            
!outerInstance.m_assertMergedSegmentsWarmed || 
outerInstance.warmed.TryGetValue(segReader.core, out BooleanRef _));
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+                                    }
+                                    finally
+                                    {
+                                        
UninterruptableMonitor.Exit(outerInstance.warmedLock);
+                                    }
+#endif
                                 }
                             }
                             if (s.IndexReader.NumDocs > 0)
@@ -541,11 +554,10 @@ namespace Lucene.Net.Index
 
         protected bool m_assertMergedSegmentsWarmed = true;
 
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
-        private readonly ConditionalWeakTable<SegmentCoreReaders, BooleanRef> 
warmed = new ConditionalWeakTable<SegmentCoreReaders, BooleanRef>();
-#else
-        private readonly IDictionary<SegmentCoreReaders, BooleanRef> warmed = 
new WeakDictionary<SegmentCoreReaders, BooleanRef>().AsConcurrent();
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+        private readonly object warmedLock = new object();
 #endif
+        private readonly ConditionalWeakTable<SegmentCoreReaders, BooleanRef> 
warmed = new ConditionalWeakTable<SegmentCoreReaders, BooleanRef>();
 
         public virtual void RunTest(string testName)
         {
@@ -799,10 +811,18 @@ namespace Lucene.Net.Index
                 {
                     Console.WriteLine("TEST: now warm merged reader=" + 
reader);
                 }
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
-                outerInstance.warmed.AddOrUpdate(((SegmentReader)reader).core, 
true);
-#else
-                outerInstance.warmed[((SegmentReader)reader).core] = true;
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+                UninterruptableMonitor.Enter(outerInstance.warmedLock);
+                try
+                {
+#endif
+                    
outerInstance.warmed.AddOrUpdate(((SegmentReader)reader).core, true);
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+                }
+                finally
+                {
+                    UninterruptableMonitor.Exit(outerInstance.warmedLock);
+                }
 #endif
                 int maxDoc = reader.MaxDoc;
                 IBits liveDocs = reader.LiveDocs;
diff --git a/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs 
b/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs
index 67ea723..4cb0d49 100644
--- a/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs
+++ b/src/Lucene.Net.TestFramework/Search/AssertingScorer.cs
@@ -1,6 +1,8 @@
 using Lucene.Net.Diagnostics;
 using Lucene.Net.Index;
+using Lucene.Net.Runtime.CompilerServices;
 using Lucene.Net.Support;
+using Lucene.Net.Support.Threading;
 using System;
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
@@ -32,13 +34,11 @@ namespace Lucene.Net.Search
         // we need to track scorers using a weak hash map because otherwise we
         // could loose references because of eg.
         // AssertingScorer.Score(Collector) which needs to delegate to work 
correctly
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
-        private static readonly ConditionalWeakTable<Scorer, 
WeakReference<AssertingScorer>> ASSERTING_INSTANCES =
-            new ConditionalWeakTable<Scorer, WeakReference<AssertingScorer>>();
-#else
-        private static readonly IDictionary<Scorer, 
WeakReference<AssertingScorer>> ASSERTING_INSTANCES = 
-            new WeakDictionary<Scorer, 
WeakReference<AssertingScorer>>().AsConcurrent();
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+        private static readonly object assertingInstancesLock = new object();
 #endif
+        private static readonly ConditionalWeakTable<Scorer, 
WeakReference<AssertingScorer>> assertingInstances =
+            new ConditionalWeakTable<Scorer, WeakReference<AssertingScorer>>();
 
         public static Scorer Wrap(Random random, Scorer other)
         {
@@ -47,10 +47,18 @@ namespace Lucene.Net.Search
                 return other;
             }
             AssertingScorer assertScorer = new AssertingScorer(random, other);
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
-            ASSERTING_INSTANCES.AddOrUpdate(other, new 
WeakReference<AssertingScorer>(assertScorer));
-#else
-            ASSERTING_INSTANCES[other] = new 
WeakReference<AssertingScorer>(assertScorer);
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+            UninterruptableMonitor.Enter(assertingInstancesLock);
+            try
+            {
+#endif
+                assertingInstances.AddOrUpdate(other, new 
WeakReference<AssertingScorer>(assertScorer));
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+            }
+            finally
+            {
+                UninterruptableMonitor.Exit(assertingInstancesLock);
+            }
 #endif
 
             return assertScorer;
@@ -62,19 +70,31 @@ namespace Lucene.Net.Search
             {
                 return other;
             }
-            if (!ASSERTING_INSTANCES.TryGetValue(other, out 
WeakReference<AssertingScorer> assertingScorerRef) || assertingScorerRef == 
null ||
-                !assertingScorerRef.TryGetTarget(out AssertingScorer 
assertingScorer) || assertingScorer == null)
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+            UninterruptableMonitor.Enter(assertingInstancesLock);
+            try
             {
-                // can happen in case of memory pressure or if
-                // scorer1.Score(collector) calls
-                // collector.setScorer(scorer2) with scorer1 != scorer2, such 
as
-                // BooleanScorer. In that case we can't enable all assertions
-                return new AssertingScorer(random, other);
+#endif
+                if (!assertingInstances.TryGetValue(other, out 
WeakReference<AssertingScorer> assertingScorerRef) || assertingScorerRef == 
null ||
+                    !assertingScorerRef.TryGetTarget(out AssertingScorer 
assertingScorer) || assertingScorer == null)
+                {
+                    // can happen in case of memory pressure or if
+                    // scorer1.Score(collector) calls
+                    // collector.setScorer(scorer2) with scorer1 != scorer2, 
such as
+                    // BooleanScorer. In that case we can't enable all 
assertions
+                    return new AssertingScorer(random, other);
+                }
+                else
+                {
+                    return assertingScorer;
+                }
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
             }
-            else
+            finally
             {
-                return assertingScorer;
+                UninterruptableMonitor.Exit(assertingInstancesLock);
             }
+#endif
         }
 
         internal readonly Random random;
diff --git a/src/Lucene.Net.Tests/Support/TestWeakDictionary.cs 
b/src/Lucene.Net.Tests/Support/TestWeakDictionary.cs
deleted file mode 100644
index f059139..0000000
--- a/src/Lucene.Net.Tests/Support/TestWeakDictionary.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Lucene.Net.Attributes;
-using Lucene.Net.Util;
-using NUnit.Framework;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using Assert = Lucene.Net.TestFramework.Assert;
-
-namespace Lucene.Net.Support
-{
-    /*
-     * Licensed to the Apache Software Foundation (ASF) under one or more
-     * contributor license agreements.  See the NOTICE file distributed with
-     * this work for additional information regarding copyright ownership.
-     * The ASF licenses this file to You under the Apache License, Version 2.0
-     * (the "License"); you may not use this file except in compliance with
-     * the License.  You may obtain a copy of the License at
-     *
-     *     http://www.apache.org/licenses/LICENSE-2.0
-     *
-     * Unless required by applicable law or agreed to in writing, software
-     * distributed under the License is distributed on an "AS IS" BASIS,
-     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     * See the License for the specific language governing permissions and
-     * limitations under the License.
-     */
-
-    [TestFixture]
-    public class TestWeakDictionary : LuceneTestCase
-    {
-        [Test, LuceneNetSpecific]
-        public void A_TestBasicOps()
-        {
-            IDictionary<object, object> weakDictionary = 
TestWeakDictionaryBehavior.CreateDictionary();// new 
SupportClass.TjWeakHashTable();
-            Hashtable realHashTable = new Hashtable();
-
-            SmallObject[] so = new SmallObject[100];
-            for (int i = 0; i < 20000; i++)
-            {
-                SmallObject key = new SmallObject(i);
-                SmallObject value = key;
-                so[i / 200] = key;
-                realHashTable.Add(key, value);
-                weakDictionary.Add(key, value);
-            }
-
-            Assert.AreEqual(weakDictionary.Count, realHashTable.Count);
-
-            ICollection keys = (ICollection)realHashTable.Keys;
-
-            foreach (SmallObject key in keys)
-            {
-                Assert.AreEqual(((SmallObject)realHashTable[key]).i,
-                                ((SmallObject)weakDictionary[key]).i);
-
-                Assert.IsTrue(realHashTable[key].Equals(weakDictionary[key]));
-            }
-
-
-            ICollection values1 = (ICollection)weakDictionary.Values;
-            ICollection values2 = (ICollection)realHashTable.Values;
-            Assert.AreEqual(values1.Count, values2.Count);
-
-            realHashTable.Remove(new SmallObject(10000));
-            weakDictionary.Remove(new SmallObject(10000));
-            Assert.AreEqual(weakDictionary.Count, 20000);
-            Assert.AreEqual(realHashTable.Count, 20000);
-
-            for (int i = 0; i < so.Length; i++)
-            {
-                realHashTable.Remove(so[i]);
-                weakDictionary.Remove(so[i]);
-                Assert.AreEqual(weakDictionary.Count, 20000 - i - 1);
-                Assert.AreEqual(realHashTable.Count, 20000 - i - 1);
-            }
-
-            //After removals, compare the collections again.
-            ICollection keys2 = (ICollection)realHashTable.Keys;
-            foreach (SmallObject o in keys2)
-            {
-                Assert.AreEqual(((SmallObject)realHashTable[o]).i,
-                                ((SmallObject)weakDictionary[o]).i);
-                Assert.IsTrue(realHashTable[o].Equals(weakDictionary[o]));
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        [Slow]
-        public void B_TestOutOfMemory()
-        {
-            var wht = TestWeakDictionaryBehavior.CreateDictionary();
-            int OOMECount = 0;
-
-            for (int i = 0; i < 1024 * 24 + 32; i++) // total requested Mem. > 
24GB
-            {
-                try
-                {
-                    wht.Add(new BigObject(i), i);
-                    if (i % 1024 == 0) Console.WriteLine("Requested Mem: " + 
i.ToString() + " MB");
-                    OOMECount = 0;
-                }
-                catch (Exception oom) when (oom.IsOutOfMemoryError())
-                {
-                    if (OOMECount++ > 10) throw new Exception("Memory 
Allocation Error in B_TestOutOfMemory");
-                    //Try Again. GC will eventually release some memory.
-                    Console.WriteLine("OOME WHEN i=" + i.ToString() + ". Try 
Again");
-                    System.Threading.Thread.Sleep(10);
-                    i--;
-                    continue;
-                }
-            }
-
-            GC.Collect();
-            Console.WriteLine("Passed out of memory exception.");
-        }
-
-        private long GetMemUsageInKB()
-        {
-            return System.Diagnostics.Process.GetCurrentProcess().WorkingSet64 
/ 1024;
-        }
-
-        [Test, LuceneNetSpecific]
-        [Slow]
-        public void C_TestMemLeakage()
-        {
-
-            var wht = TestWeakDictionaryBehavior.CreateDictionary(); //new 
SupportClass.TjWeakHashTable();
-
-            GC.Collect();
-            long initialMemUsage = GetMemUsageInKB();
-
-            Console.WriteLine("Initial MemUsage=" + initialMemUsage);
-            for (int i = 0; i < 10000; i++)
-            {
-                wht.Add(new BigObject(i), i);
-                if (i % 100 == 0)
-                {
-                    long mu = GetMemUsageInKB();
-                    Console.WriteLine(i.ToString() + ") MemUsage=" + mu);
-                }
-            }
-
-            GC.Collect();
-            long memUsage = GetMemUsageInKB();
-            Assert.IsFalse(memUsage > initialMemUsage * 2, "Memory 
Leakage.MemUsage = " + memUsage);
-        }
-    }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/TestWeakDictionaryBehavior.cs 
b/src/Lucene.Net.Tests/Support/TestWeakDictionaryBehavior.cs
deleted file mode 100644
index 40dbfba..0000000
--- a/src/Lucene.Net.Tests/Support/TestWeakDictionaryBehavior.cs
+++ /dev/null
@@ -1,294 +0,0 @@
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Lucene.Net.Attributes;
-using Lucene.Net.Util;
-using NUnit.Framework;
-using System;
-using System.Collections.Generic;
-using Assert = Lucene.Net.TestFramework.Assert;
-
-namespace Lucene.Net.Support
-{
-    /*
-     * Licensed to the Apache Software Foundation (ASF) under one or more
-     * contributor license agreements.  See the NOTICE file distributed with
-     * this work for additional information regarding copyright ownership.
-     * The ASF licenses this file to You under the Apache License, Version 2.0
-     * (the "License"); you may not use this file except in compliance with
-     * the License.  You may obtain a copy of the License at
-     *
-     *     http://www.apache.org/licenses/LICENSE-2.0
-     *
-     * Unless required by applicable law or agreed to in writing, software
-     * distributed under the License is distributed on an "AS IS" BASIS,
-     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     * See the License for the specific language governing permissions and
-     * limitations under the License.
-     */
-
-    [TestFixture]
-    public class TestWeakDictionaryBehavior : LuceneTestCase
-    {
-        IDictionary<object, object> dictionary;
-
-        public static IDictionary<object, object> CreateDictionary()
-        {
-            return new WeakDictionary<object, object>();
-        }
-
-
-        private void CallGC()
-        {
-            for (int i = 0; i < 10; i++)
-            {
-                GC.Collect();
-                GC.WaitForPendingFinalizers();
-            }
-        }
-
-        [SetUp]
-        public void Setup()
-        {
-            dictionary = CreateDictionary();
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_Add()
-        {
-            string key = "A";
-
-            dictionary.Add(key, "value");
-            Assert.IsTrue(dictionary.ContainsKey(key));
-            Assert.AreEqual("value", dictionary[key]);
-            Assert.AreEqual(1, dictionary.Count);
-
-            CollectionAssert.AreEquivalent(dictionary.Values, new object[] { 
"value" });
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_Add_2()
-        {
-            string key = "A";
-            string key2 = "B";
-
-            dictionary.Add(key, "value");
-            dictionary.Add(key2, "value2");
-            Assert.IsTrue(dictionary.ContainsKey(key));
-            Assert.IsTrue(dictionary.ContainsKey(key2));
-            Assert.AreEqual("value", dictionary[key]);
-            Assert.AreEqual("value2", dictionary[key2]);
-            Assert.AreEqual(2, dictionary.Count);
-
-            CollectionAssert.AreEquivalent(dictionary.Values, new object[] { 
"value", "value2" });
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Keys()
-        {
-            string key = "A";
-            string key2 = "B";
-
-            dictionary.Add(key, "value");
-            CollectionAssert.AreEquivalent(dictionary.Keys, new object[] { key 
});
-
-            dictionary.Add(key2, "value2");
-            CollectionAssert.AreEquivalent(dictionary.Keys, new object[] { 
key, key2 });
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_Add_Null()
-        {
-            Assert.Throws<ArgumentNullException>(() => dictionary.Add(null, 
"value"));
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_Set_Null()
-        {
-            Assert.Throws<ArgumentNullException>(() => dictionary[null] = 
"value");
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_AddReplace()
-        {
-            string key = "A";
-            string key2 = "a".ToUpperInvariant();
-
-            dictionary.Add(key, "value");
-            dictionary[key2] = "value2";
-
-            Assert.AreEqual(1, dictionary.Count);
-            Assert.IsTrue(dictionary.ContainsKey(key));
-            Assert.AreEqual("value2", dictionary[key]);
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_AddRemove()
-        {
-            string key = "A";
-
-            dictionary.Add(key, "value");
-            dictionary.Remove(key);
-
-            Assert.AreEqual(0, dictionary.Count);
-            Assert.IsFalse(dictionary.ContainsKey(key));
-            Assert.IsFalse(dictionary.TryGetValue(key, out _));
-            Assert.Throws<KeyNotFoundException>(() => { var x = 
dictionary[key]; });
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_Clear()
-        {
-            string key = "A";
-
-            dictionary.Add(key, "value");
-            dictionary.Clear();
-
-            Assert.AreEqual(0, dictionary.Count);
-            Assert.IsFalse(dictionary.ContainsKey(key));
-            Assert.IsFalse(dictionary.TryGetValue(key, out _));
-            Assert.Throws<KeyNotFoundException>(() => { var x = 
dictionary[key]; });
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_AddRemove_2()
-        {
-            string key = "A";
-
-            dictionary.Add(key, "value");
-            dictionary.Remove(key);
-            dictionary.Remove(key);
-
-            Assert.AreEqual(0, dictionary.Count);
-            Assert.IsFalse(dictionary.ContainsKey(key));
-            Assert.IsFalse(dictionary.TryGetValue(key, out _));
-            Assert.Throws<KeyNotFoundException>(() => { var x = 
dictionary[key]; });
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_Get_Null()
-        {
-            object value;
-            Assert.Throws<ArgumentNullException>(() => value = 
dictionary[null]);
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_Remove_Null()
-        {
-            Assert.Throws<ArgumentNullException>(() => 
dictionary.Remove(null));
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_GetEnumerator()
-        {
-            string key = "A";
-
-            dictionary.Add(key, "value");
-
-            var de = dictionary.GetEnumerator();
-            Assert.IsTrue(de.MoveNext());
-            Assert.AreEqual(key, de.Current.Key);
-            Assert.AreEqual("value", de.Current.Value);
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Dictionary_ForEach()
-        {
-            string key = "A";
-
-            dictionary.Add(key, "value");
-
-            foreach (var de in dictionary)
-            {
-                Assert.AreEqual(key, de.Key);
-                Assert.AreEqual("value", de.Value);
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Collisions()
-        {
-            //Create 2 keys with same hashcode but that are not equal
-            CollisionTester key1 = new CollisionTester(1, 100);
-            CollisionTester key2 = new CollisionTester(2, 100);
-
-            dictionary.Add(key1, "value1");
-            dictionary.Add(key2, "value2");
-
-            Assert.AreEqual("value1", dictionary[key1]);
-            Assert.AreEqual("value2", dictionary[key2]);
-
-            dictionary.Remove(key1);
-            Assert.IsFalse(dictionary.TryGetValue(key1, out _));
-            Assert.Throws<KeyNotFoundException>(() => { var x = 
dictionary[key1]; });
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Weak_1()
-        {
-            BigObject key = new BigObject(1);
-            BigObject key2 = new BigObject(2);
-
-            dictionary.Add(key, "value");
-            Assert.AreEqual("value", dictionary[key]);
-
-            key = null;
-            CallGC();
-
-            dictionary.Add(key2, "value2");
-            Assert.AreEqual(1, dictionary.Count);
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Weak_2()
-        {
-            BigObject key = new BigObject(1);
-            BigObject key2 = new BigObject(2);
-            BigObject key3 = new BigObject(3);
-
-            dictionary.Add(key, "value");
-            dictionary.Add(key2, "value2");
-            Assert.AreEqual("value", dictionary[key]);
-
-            key = null;
-            CallGC();
-
-            dictionary.Add(key3, "value3");
-
-            Assert.AreEqual(2, dictionary.Count);
-            Assert.IsNotNull(key2);
-        }
-
-        [Test, LuceneNetSpecific]
-        [Slow]
-        public void Test_Weak_ForEach()
-        {
-            BigObject[] keys1 = new BigObject[20];
-            BigObject[] keys2 = new BigObject[20];
-
-            for (int i = 0; i < keys1.Length; i++)
-            {
-                keys1[i] = new BigObject(i);
-                dictionary.Add(keys1[i], "value");
-            }
-            for (int i = 0; i < keys2.Length; i++)
-            {
-                keys2[i] = new BigObject(i);
-                dictionary.Add(keys2[i], "value");
-            }
-
-            Assert.AreEqual(40, dictionary.Count);
-
-            keys2 = null;
-            int count = 0;
-            foreach (var de in dictionary)
-            {
-                CallGC();
-                count++;
-            }
-
-            Assert.LessOrEqual(20, count);
-            Assert.Greater(40, count);
-            Assert.IsNotNull(keys1);
-        }
-    }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/TestWeakDictionaryPerformance.cs 
b/src/Lucene.Net.Tests/Support/TestWeakDictionaryPerformance.cs
deleted file mode 100644
index 68e1827..0000000
--- a/src/Lucene.Net.Tests/Support/TestWeakDictionaryPerformance.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Lucene.Net.Attributes;
-using NUnit.Framework;
-using System.Collections.Generic;
-
-namespace Lucene.Net.Support
-{
-    /*
-     * Licensed to the Apache Software Foundation (ASF) under one or more
-     * contributor license agreements.  See the NOTICE file distributed with
-     * this work for additional information regarding copyright ownership.
-     * The ASF licenses this file to You under the Apache License, Version 2.0
-     * (the "License"); you may not use this file except in compliance with
-     * the License.  You may obtain a copy of the License at
-     *
-     *     http://www.apache.org/licenses/LICENSE-2.0
-     *
-     * Unless required by applicable law or agreed to in writing, software
-     * distributed under the License is distributed on an "AS IS" BASIS,
-     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     * See the License for the specific language governing permissions and
-     * limitations under the License.
-     */
-
-    [TestFixture]
-    public class TestWeakDictionaryPerformance
-    {
-        IDictionary<object, object> dictionary;
-        SmallObject[] keys;
-
-
-        [SetUp]
-        public void Setup()
-        {
-            dictionary = TestWeakDictionaryBehavior.CreateDictionary();
-        }
-
-        private void Fill(IDictionary<object, object> dictionary)
-        {
-            foreach (SmallObject key in keys)
-                dictionary.Add(key, "value");
-        }
-
-        [OneTimeSetUp]
-        public void TestSetup()
-        {
-            keys = new SmallObject[100000];
-            for (int i = 0; i < keys.Length; i++)
-                keys[i] = new SmallObject(i);
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Performance_Add()
-        {
-            for (int i = 0; i < 10; i++)
-            {
-                dictionary.Clear();
-                Fill(dictionary);
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Performance_Remove()
-        {
-            for (int i = 0; i < 10; i++)
-            {
-                Fill(dictionary);
-                foreach (SmallObject key in keys)
-                    dictionary.Remove(key);
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Performance_Replace()
-        {
-            for (int i = 0; i < 10; i++)
-            {
-                foreach (SmallObject key in keys)
-                    dictionary[key] = "value2";
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Performance_Access()
-        {
-            Fill(dictionary);
-            for (int i = 0; i < 10; i++)
-            {
-                foreach (SmallObject key in keys)
-                {
-                    object value = dictionary[key];
-                }
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Performance_Contains()
-        {
-            Fill(dictionary);
-            for (int i = 0; i < 10; i++)
-            {
-                foreach (SmallObject key in keys)
-                {
-                    dictionary.ContainsKey(key);
-                }
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Performance_Keys()
-        {
-            Fill(dictionary);
-            for (int i = 0; i < 100; i++)
-            {
-                var keys = dictionary.Keys;
-            }
-        }
-
-        [Test, LuceneNetSpecific]
-        public void Test_Performance_ForEach()
-        {
-            Fill(dictionary);
-            for (int i = 0; i < 10; i++)
-            {
-                foreach (var de in dictionary)
-                {
-
-                }
-            }
-        }
-    }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Index/IndexReader.cs 
b/src/Lucene.Net/Index/IndexReader.cs
index 77777ec..1dfaeee 100644
--- a/src/Lucene.Net/Index/IndexReader.cs
+++ b/src/Lucene.Net/Index/IndexReader.cs
@@ -3,6 +3,9 @@ using Lucene.Net.Documents;
 using Lucene.Net.Support;
 using Lucene.Net.Support.Threading;
 using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
 using System;
 using System.Collections;
 using System.Collections.Generic;
@@ -86,6 +89,11 @@ namespace Lucene.Net.Index
             }
         }
 
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+        // LUCENENET specific: Add weak event handler for .NET Standard 2.0 
and .NET Framework, since we don't have an enumerator to use
+        private readonly IEventAggregator eventAggregator = new 
EventAggregator();
+#endif
+
         /// <summary>
         /// A custom listener that's invoked when the <see cref="IndexReader"/>
         /// is closed.
@@ -101,12 +109,9 @@ namespace Lucene.Net.Index
 
         private readonly ISet<IReaderClosedListener> readerClosedListeners = 
new JCG.LinkedHashSet<IReaderClosedListener>().AsConcurrent();
 
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
         private readonly ConditionalWeakTable<IndexReader, object> 
parentReaders = new ConditionalWeakTable<IndexReader, object>();
-#else
-        private readonly WeakDictionary<IndexReader, object> parentReaders = 
new WeakDictionary<IndexReader, object>();
-#endif
-        // LUCENENET specific - since neither WeakDictionary nor 
ConditionalWeakTable synchronize
+
+        // LUCENENET specific - since ConditionalWeakTable doesn't synchronize
         // on the enumerator, we need to do external synchronization to make 
them threadsafe.
         private readonly object parentReadersLock = new object();
 
@@ -144,16 +149,24 @@ namespace Lucene.Net.Index
         public void RegisterParentReader(IndexReader reader)
         {
             EnsureOpen();
-            // LUCENENET specific - since neither WeakDictionary nor 
ConditionalWeakTable synchronize
+            // LUCENENET specific - since ConditionalWeakTable doesn't 
synchronize
             // on the enumerator, we need to do external synchronization to 
make them threadsafe.
             UninterruptableMonitor.Enter(parentReadersLock);
             try
             {
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
                 // LUCENENET: Since there is a set Add operation (unique) in 
Lucene, the equivalent
                 // operation in .NET is AddOrUpdate, which effectively does 
nothing if the key exists.
                 // Null is passed as a value, since it is not used anyway and 
.NET doesn't have a boolean
                 // reference type.
                 parentReaders.AddOrUpdate(key: reader, value: null);
+#else
+                if (!parentReaders.TryGetValue(key: reader, out _))
+                {
+                    parentReaders.Add(key: reader, value: null);
+                    
reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent<Events.GetParentReadersEvent>());
+                }
+#endif
             }
             finally
             {
@@ -195,15 +208,21 @@ namespace Lucene.Net.Index
 
         private void ReportCloseToParentReaders()
         {
-            // LUCENENET specific - since neither WeakDictionary nor 
ConditionalWeakTable synchronize
+            // LUCENENET specific - since ConditionalWeakTable doesn't 
synchronize
             // on the enumerator, we need to do external synchronization to 
make them threadsafe.
             UninterruptableMonitor.Enter(parentReadersLock);
             try
             {
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
                 foreach (var kvp in parentReaders)
                 {
                     IndexReader target = kvp.Key;
-
+#else
+                var e = new Events.GetParentReadersEventArgs();
+                
eventAggregator.GetEvent<Events.GetParentReadersEvent>().Publish(e);
+                foreach (var target in e.ParentReaders)
+                {
+#endif
                     // LUCENENET: This probably can't happen, but we are being 
defensive to avoid exceptions
                     if (target != null)
                     {
@@ -602,12 +621,65 @@ namespace Lucene.Net.Index
                     UninterruptableMonitor.Exit(this);
                 }
             }
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+            // LUCENENET specific - since .NET Standard 2.0 and .NET Framework 
don't have a CondtionalWeakTable enumerator,
+            // we use a weak event to retrieve the ConditionalWeakTable items
+            foreach (var getParentReadersEvent in getParentReadersEvents)
+                getParentReadersEvent.Unsubscribe(OnGetParentReaders);
+            getParentReadersEvents.Clear();
+
+            foreach (var getCacheKeysEvent in getCacheKeysEvents)
+                getCacheKeysEvent.Unsubscribe(OnGetCacheKeys);
+            getCacheKeysEvents.Clear();
+#endif
         }
 
         /// <summary>
         /// Implements close. </summary>
         protected internal abstract void DoClose();
 
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+        // LUCENENET specific - since .NET Standard 2.0 and .NET Framework 
don't have a CondtionalWeakTable enumerator,
+        // we use a weak event to retrieve the ConditionalWeakTable items
+        [ExcludeFromRamUsageEstimation]
+        private readonly ISet<Events.GetParentReadersEvent> 
getParentReadersEvents = new JCG.HashSet<Events.GetParentReadersEvent>();
+        [ExcludeFromRamUsageEstimation]
+        private readonly ISet<Events.GetCacheKeysEvent> getCacheKeysEvents = 
new JCG.HashSet<Events.GetCacheKeysEvent>();
+        internal void 
SubscribeToGetParentReadersEvent(Events.GetParentReadersEvent 
getParentReadersEvent)
+        {
+            if (getParentReadersEvent is null)
+                throw new ArgumentNullException(nameof(getParentReadersEvent));
+            if (getParentReadersEvents.Add(getParentReadersEvent))
+                getParentReadersEvent.Subscribe(OnGetParentReaders);
+        }
+
+        internal void SubscribeToGetCacheKeysEvent(Events.GetCacheKeysEvent 
getCacheKeysEvent)
+        {
+            if (getCacheKeysEvent is null)
+                throw new ArgumentNullException(nameof(getCacheKeysEvent));
+            if (getCacheKeysEvents.Add(getCacheKeysEvent))
+                getCacheKeysEvent.Subscribe(OnGetCacheKeys);
+        }
+
+        // LUCENENET specific: Clean up the weak event handler if this class 
goes out of scope
+        ~IndexReader()
+        {
+            Dispose(false);
+        }
+
+        // LUCENENET specific: Add weak event handler for .NET Standard 2.0 
and .NET Framework, since we don't have an enumerator to use
+        private void OnGetParentReaders(Events.GetParentReadersEventArgs e)
+        {
+            e.ParentReaders.Add(this);
+        }
+
+        private void OnGetCacheKeys(Events.GetCacheKeysEventArgs e)
+        {
+            e.CacheKeys.Add(this.CoreCacheKey);
+        }
+#endif
+
         /// <summary>
         /// Expert: Returns the root <see cref="IndexReaderContext"/> for this
         /// <see cref="IndexReader"/>'s sub-reader tree.
@@ -638,7 +710,7 @@ namespace Lucene.Net.Index
 
         /// <summary>
         /// Expert: Returns a key for this <see cref="IndexReader"/>, so 
-        /// <see cref="Search.IFieldCache"/>/<see 
cref="Search.CachingWrapperFilter"/> can find
+        /// <see cref="Search.FieldCache"/>/<see 
cref="Search.CachingWrapperFilter"/> can find
         /// it again.
         /// This key must not have Equals()/GetHashCode() methods, 
         /// so &quot;equals&quot; means &quot;identical&quot;.
diff --git a/src/Lucene.Net/Lucene.Net.csproj b/src/Lucene.Net/Lucene.Net.csproj
index 09d76cb..cd9dc3f 100644
--- a/src/Lucene.Net/Lucene.Net.csproj
+++ b/src/Lucene.Net/Lucene.Net.csproj
@@ -43,7 +43,7 @@
   </PropertyGroup>
 
   <ItemGroup Label="NuGet Package Files">
-    <None Include="readme-nuget.md" Pack="true" PackagePath="\readme.md"/>
+    <None Include="readme-nuget.md" Pack="true" PackagePath="\readme.md" />
     <None Include="$(LuceneNetCodeAnalysisToolsDir)*.ps1" Pack="true" 
PackagePath="tools" />
     <None Include="$(LuceneNetCodeAnalysisCSAssemblyFile)" Pack="true" 
PackagePath="analyzers/dotnet/cs" Visible="false" />
     <None Include="$(LuceneNetCodeAnalysisVBAssemblyFile)" Pack="true" 
PackagePath="analyzers/dotnet/vb" Visible="false" />
@@ -63,10 +63,12 @@
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
     <PackageReference 
Include="Microsoft.Extensions.Configuration.Abstractions" 
Version="$(MicrosoftExtensionsConfigurationAbstractionsPackageVersion)" />
+    <PackageReference Include="Prism.Core" 
Version="$(PrismCorePackageVersion)" />
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
     <PackageReference 
Include="Microsoft.Extensions.Configuration.Abstractions" 
Version="$(MicrosoftExtensionsConfigurationAbstractionsPackageVersion)" />
+    <PackageReference Include="Prism.Core" 
Version="$(PrismCorePackageVersion)" />
   </ItemGroup>
   
   <ItemGroup>
diff --git a/src/Lucene.Net/Search/CachingWrapperFilter.cs 
b/src/Lucene.Net/Search/CachingWrapperFilter.cs
index b338430..eeacbae 100644
--- a/src/Lucene.Net/Search/CachingWrapperFilter.cs
+++ b/src/Lucene.Net/Search/CachingWrapperFilter.cs
@@ -1,6 +1,10 @@
 using Lucene.Net.Diagnostics;
-using Lucene.Net.Support;
+using Lucene.Net.Runtime.CompilerServices;
 using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
 using JCG = J2N.Collections.Generic;
@@ -37,26 +41,26 @@ namespace Lucene.Net.Search
     /// </summary>
     public class CachingWrapperFilter : Filter
     {
-        private readonly Filter _filter;
+        private readonly Filter filter;
 
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
-        private readonly ConditionalWeakTable<object, DocIdSet> _cache = new 
ConditionalWeakTable<object, DocIdSet>();
-#else
-        private readonly IDictionary<object, DocIdSet> _cache = new 
WeakDictionary<object, DocIdSet>().AsConcurrent();
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+        // LUCENENET specific: Add weak event handler for .NET Standard 2.0 
and .NET Framework, since we don't have an enumerator to use
+        private readonly IEventAggregator eventAggregator = new 
EventAggregator();
 #endif
+        private readonly ConditionalWeakTable<object, DocIdSet> cache = new 
ConditionalWeakTable<object, DocIdSet>();
 
         /// <summary>
         /// Wraps another filter's result and caches it. </summary>
         /// <param name="filter"> Filter to cache results of </param>
         public CachingWrapperFilter(Filter filter)
         {
-            this._filter = filter;
+            this.filter = filter;
         }
 
         /// <summary>
         /// Gets the contained filter. </summary>
         /// <returns> the contained filter. </returns>
-        public virtual Filter Filter => _filter;
+        public virtual Filter Filter => filter;
 
         /// <summary>
         /// Provide the <see cref="DocIdSet"/> to be cached, using the <see 
cref="DocIdSet"/> provided
@@ -114,19 +118,30 @@ namespace Lucene.Net.Search
             var reader = context.AtomicReader;
             object key = reader.CoreCacheKey;
 
-            if (_cache.TryGetValue(key, out DocIdSet docIdSet) && docIdSet != 
null)
+            if (cache.TryGetValue(key, out DocIdSet docIdSet))
             {
                 hitCount++;
             }
             else
             {
                 missCount++;
-                docIdSet = DocIdSetToCache(_filter.GetDocIdSet(context, null), 
reader);
+                docIdSet = DocIdSetToCache(filter.GetDocIdSet(context, null), 
reader);
                 if (Debugging.AssertsEnabled) 
Debugging.Assert(docIdSet.IsCacheable);
-#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
-                _cache.AddOrUpdate(key, docIdSet);
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+                cache.AddOrUpdate(key, docIdSet);
 #else
-                _cache[key] = docIdSet;
+                UninterruptableMonitor.Enter(cache);
+                try
+                {
+                    cache.AddOrUpdate(key, docIdSet);
+                    // LUCENENET specific - since .NET Standard 2.0 and .NET 
Framework don't have a CondtionalWeakTable enumerator,
+                    // we use a weak event to retrieve the DocIdSet instances
+                    
reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<Events.GetCacheKeysEvent>());
+                }
+                finally
+                {
+                    UninterruptableMonitor.Exit(cache);
+                }
 #endif
             }
 
@@ -135,19 +150,19 @@ namespace Lucene.Net.Search
 
         public override string ToString()
         {
-            return this.GetType().Name + "(" + _filter + ")";
+            return this.GetType().Name + "(" + filter + ")";
         }
 
         public override bool Equals(object o)
         {
             if (o is null) return false;
             if (!(o is CachingWrapperFilter other)) return false;
-            return _filter.Equals(other._filter);
+            return filter.Equals(other.filter);
         }
 
         public override int GetHashCode()
         {
-            return (_filter.GetHashCode() ^ this.GetType().GetHashCode());
+            return (filter.GetHashCode() ^ this.GetType().GetHashCode());
         }
 
         /// <summary>
@@ -173,20 +188,28 @@ namespace Lucene.Net.Search
         {
             // Sync only to pull the current set of values:
             IList<DocIdSet> docIdSets;
-            UninterruptableMonitor.Enter(_cache);
+            UninterruptableMonitor.Enter(cache);
             try
             {
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
                 docIdSets = new JCG.List<DocIdSet>();
-                foreach (var pair in _cache)
+#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+                foreach (var pair in cache)
                     docIdSets.Add(pair.Value);
 #else
-                docIdSets = new JCG.List<DocIdSet>(_cache.Values);
+                // LUCENENET specific - since .NET Standard 2.0 and .NET 
Framework don't have a CondtionalWeakTable enumerator,
+                // we use a weak event to retrieve the DocIdSet instances. We 
look each of these up here to avoid the need
+                // to attach events to the DocIdSet instances themselves (thus 
using the existing IndexReader.Dispose()
+                // method to detach the events rather than using a finalizer 
in DocIdSet to ensure they are cleaned up).
+                var e = new Events.GetCacheKeysEventArgs();
+                
eventAggregator.GetEvent<Events.GetCacheKeysEvent>().Publish(e);
+                foreach (var key in e.CacheKeys)
+                    if (cache.TryGetValue(key, out DocIdSet value))
+                        docIdSets.Add(value);
 #endif
             }
             finally
             {
-                UninterruptableMonitor.Exit(_cache);
+                UninterruptableMonitor.Exit(cache);
             }
 
             long total = 0;
diff --git a/src/Lucene.Net/Search/DocIdSet.cs 
b/src/Lucene.Net/Search/DocIdSet.cs
index 9c78d24..418fab4 100644
--- a/src/Lucene.Net/Search/DocIdSet.cs
+++ b/src/Lucene.Net/Search/DocIdSet.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.IO;
 
 namespace Lucene.Net.Search
diff --git a/src/Lucene.Net/Search/FieldCacheImpl.cs 
b/src/Lucene.Net/Search/FieldCacheImpl.cs
index 0628aba..cc86ee1 100644
--- a/src/Lucene.Net/Search/FieldCacheImpl.cs
+++ b/src/Lucene.Net/Search/FieldCacheImpl.cs
@@ -1,9 +1,14 @@
 using J2N.Collections.Generic.Extensions;
 using Lucene.Net.Diagnostics;
 using Lucene.Net.Index;
+using Lucene.Net.Runtime.CompilerServices;
 using Lucene.Net.Support;
 using Lucene.Net.Support.IO;
 using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Prism.Events;
+#endif
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -163,6 +168,7 @@ namespace Lucene.Net.Search
             UninterruptableMonitor.Enter(cache.readerCache);
             try
             {
+#if FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
                 foreach (var readerCacheEntry in cache.readerCache)
                 {
                     object readerKey = readerCacheEntry.Key;
@@ -177,6 +183,23 @@ namespace Lucene.Net.Search
                         result.Add(new FieldCache.CacheEntry(readerKey, 
entry.field, cacheType, entry.Custom, mapEntry.Value));
                     }
                 }
+#else
+                // LUCENENET specific - since .NET Standard 2.0 and .NET 
Framework don't have a CondtionalWeakTable enumerator,
+                // we use a weak event to retrieve the readerKey instances and 
then lookup the values in the table one by one.
+                var e = new Events.GetCacheKeysEventArgs();
+                
eventAggregator.GetEvent<Events.GetCacheKeysEvent>().Publish(e);
+                foreach (object readerKey in e.CacheKeys)
+                {
+                    if (cache.readerCache.TryGetValue(readerKey, out 
IDictionary<TKey, object> innerCache))
+                    {
+                        foreach (KeyValuePair<TKey, object> mapEntry in 
innerCache)
+                        {
+                            TKey entry = mapEntry.Key;
+                            result.Add(new FieldCache.CacheEntry(readerKey, 
entry.field, cacheType, entry.Custom, mapEntry.Value));
+                        }
+                    }
+                }
+#endif
             }
             finally
             {
@@ -242,8 +265,18 @@ namespace Lucene.Net.Search
                     reader.AddReaderClosedListener(purgeReader);
                 }
             }
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+            // LUCENENET specific - since .NET Standard 2.0 and .NET Framework 
don't have a CondtionalWeakTable enumerator,
+            // we use a weak event to retrieve the readerKey instances
+            
reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<Events.GetCacheKeysEvent>());
+#endif
         }
 
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+        // LUCENENET specific: Add weak event handler for .NET Standard 2.0 
and .NET Framework, since we don't have an enumerator to use
+        private readonly IEventAggregator eventAggregator = new 
EventAggregator();
+#endif
+
         /// <summary>
         /// Expert: Internal cache. </summary>
         internal abstract class Cache<TKey, TValue> where TKey : CacheKey
@@ -255,11 +288,7 @@ namespace Lucene.Net.Search
 
             internal readonly FieldCacheImpl wrapper;
 
-#if FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
             internal ConditionalWeakTable<object, IDictionary<TKey, object>> 
readerCache = new ConditionalWeakTable<object, IDictionary<TKey, object>>();
-#else
-            internal WeakDictionary<object, IDictionary<TKey, object>> 
readerCache = new WeakDictionary<object, IDictionary<TKey, object>>();
-#endif
 
             protected abstract TValue CreateValue(AtomicReader reader, TKey 
key, bool setDocsWithField);
 
diff --git 
a/src/Lucene.Net/Support/Runtime/CompilerServices/ConditionalWeakTableExtensions.cs
 
b/src/Lucene.Net/Support/Runtime/CompilerServices/ConditionalWeakTableExtensions.cs
new file mode 100644
index 0000000..c629d32
--- /dev/null
+++ 
b/src/Lucene.Net/Support/Runtime/CompilerServices/ConditionalWeakTableExtensions.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Lucene.Net.Runtime.CompilerServices
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    internal static class ConditionalWeakTableExtensions
+    {
+#if !FEATURE_CONDITIONALWEAKTABLE_ADDORUPDATE
+        /// <summary>
+        /// AddOrUpdate-like patch for .NET Standard 2.0 and .NET Framework. 
Note this method is not threadsafe,
+        /// so will require external locking to synchronize with other <see 
cref="ConditionalWeakTable{TKey, TValue}"/> operations.
+        /// </summary>
+        /// <typeparam name="TKey">The key type.</typeparam>
+        /// <typeparam name="TValue">The value type.</typeparam>
+        /// <param name="table">This <see cref="ConditionalWeakTable{TKey, 
TValue}"/>.</param>
+        /// <param name="key">The key to add or update. May not be 
<c>null</c>.</param>
+        /// <param name="value">The value to associate with key.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="table"/> 
or <paramref name="key"/> is <c>null</c>.</exception>
+        public static void AddOrUpdate<TKey, TValue>(this 
ConditionalWeakTable<TKey, TValue> table, TKey key, TValue value)
+            where TKey: class
+            where TValue: class
+        {
+            if (table is null)
+                throw new ArgumentNullException(nameof(table));
+
+            if (table.TryGetValue(key, out _))
+                table.Remove(key);
+            table.Add(key, value);
+        }
+#endif
+    }
+}
diff --git a/src/Lucene.Net/Support/Util/Events.cs 
b/src/Lucene.Net/Support/Util/Events.cs
new file mode 100644
index 0000000..3d1203c
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events.cs
@@ -0,0 +1,62 @@
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+using Lucene.Net.Index;
+using Prism.Events;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Lucene.Net.Util
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// Events are used in Lucene.NET to work around the fact that <see 
cref="System.Runtime.CompilerServices.ConditionalWeakTable{TKey, TValue}"/>
+    /// doesn't have an enumerator in .NET Framework or .NET Standard prior to 
2.1. They are declared in this static class to avoid adding coupling.
+    /// </summary>
+    internal static class Events
+    {
+        #region GetParentReaders
+
+        public class GetParentReadersEventArgs
+        {
+            public IList<IndexReader> ParentReaders { get; } = new 
List<IndexReader>();
+        }
+
+        /// <summary>
+        /// Gets strong references to the parent readers of an <see 
cref="IndexReader"/>
+        /// from a <see cref="ConditionalWeakTable{TKey, TValue}"/>.
+        /// </summary>
+        public class GetParentReadersEvent : 
PubSubEvent<GetParentReadersEventArgs> { }
+
+        #endregion GetParentReaders
+
+        #region GetCacheKeys
+
+        public class GetCacheKeysEventArgs
+        {
+            public IList<object> CacheKeys { get; } = new List<object>();
+        }
+
+        /// <summary>
+        /// Gets strong references to the cache keys in a <see 
cref="ConditionalWeakTable{TKey, TValue}"/>.
+        /// </summary>
+        public class GetCacheKeysEvent : PubSubEvent<GetCacheKeysEventArgs> { }
+
+        #endregion GetCacheKeys
+    }
+}
+#endif
\ No newline at end of file
diff --git 
a/src/Lucene.Net/Support/Util/ExcludeFromRamUsageEstimationAttribute.cs 
b/src/Lucene.Net/Support/Util/ExcludeFromRamUsageEstimationAttribute.cs
new file mode 100644
index 0000000..aee34c0
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/ExcludeFromRamUsageEstimationAttribute.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Lucene.Net.Util
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// Marks a field exempt from the <see 
cref="RamUsageEstimator.SizeOf(object)"/> calculation.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
+    public class ExcludeFromRamUsageEstimationAttribute : System.Attribute
+    {
+    }
+}
diff --git a/src/Lucene.Net/Support/WeakDictionary.cs 
b/src/Lucene.Net/Support/WeakDictionary.cs
deleted file mode 100644
index 1633499..0000000
--- a/src/Lucene.Net/Support/WeakDictionary.cs
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *
-*/
-
-#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using JCG = J2N.Collections.Generic;
-
-namespace Lucene.Net.Support
-{
-#if FEATURE_SERIALIZABLE
-    [Serializable]
-#endif
-    internal sealed class WeakDictionary<TKey, TValue> : IDictionary<TKey, 
TValue> where TKey : class 
-    {
-        private IDictionary<WeakKey<TKey>, TValue> _hm;
-        private int _gcCollections = 0;
-
-        public WeakDictionary(int initialCapacity)
-            : this(initialCapacity, Arrays.Empty<KeyValuePair<TKey, TValue>>())
-        { }
-
-        public WeakDictionary()
-            : this(32, Arrays.Empty<KeyValuePair<TKey, TValue>>())
-        { }
-
-        public WeakDictionary(IEnumerable<KeyValuePair<TKey, TValue>> 
otherDictionary)
-            : this(32, otherDictionary)
-        { }
-
-        private WeakDictionary(int initialCapacity, 
IEnumerable<KeyValuePair<TKey, TValue>> otherDict)
-        {
-            _hm = new JCG.Dictionary<WeakKey<TKey>, TValue>(initialCapacity);
-            foreach (var kvp in otherDict)
-            {
-                _hm.Add(new WeakKey<TKey>(kvp.Key), kvp.Value);
-            }
-        }
-
-        // LUCENENET NOTE: Added AddOrUpdate method so we don't need so many 
conditional compilation blocks.
-        // This is just to cascade the call to this[key] = value
-        public void AddOrUpdate(TKey key, TValue value) => this[key] = value;
-
-        private void Clean()
-        {
-            if (_hm.Count == 0) return;
-            var newHm = new JCG.Dictionary<WeakKey<TKey>, TValue>(_hm.Count);
-            foreach (var kvp in _hm)
-            {
-                if (kvp.Key.TryGetTarget(out TKey _))
-                {
-                    // LUCENENET: There is a tiny chance that a call to remove 
the item
-                    // from the dictionary can happen before this line is 
executed. Therefore,
-                    // just discard the reference and add it as is, even if it 
is no longer valid
-                    // in this edge case. It is far more efficient to re-use 
the same instances, anyway.
-                    newHm.Add(kvp.Key, kvp.Value);
-                }
-            }
-            _hm = newHm;
-        }
-
-        private void CleanIfNeeded()
-        {
-            int currentColCount = GC.CollectionCount(0);
-            if (currentColCount > _gcCollections)
-            {
-                Clean();
-                _gcCollections = currentColCount;
-            }
-        }
-
-        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
-        {
-            foreach (var kvp in _hm)
-            {
-                if (kvp.Key.TryGetTarget(out TKey key))
-                {
-                    yield return new KeyValuePair<TKey, TValue>(key, 
kvp.Value);
-                }
-            }
-        }
-
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return GetEnumerator();
-        }
-
-        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, 
TValue> item)
-        {
-            CleanIfNeeded();
-            ((ICollection<KeyValuePair<WeakKey<TKey>, TValue>>)_hm).Add(
-                new KeyValuePair<WeakKey<TKey>, TValue>(new 
WeakKey<TKey>(item.Key), item.Value));
-        }
-
-        public void Clear()
-        {
-            _hm.Clear();
-        }
-
-        bool ICollection<KeyValuePair<TKey, 
TValue>>.Contains(KeyValuePair<TKey, TValue> item)
-        {
-            return ((ICollection<KeyValuePair<WeakKey<TKey>, 
TValue>>)_hm).Contains(
-                new KeyValuePair<WeakKey<TKey>, TValue>(new 
WeakKey<TKey>(item.Key), item.Value));
-        }
-
-        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, 
TValue> item)
-        {
-            return ((ICollection<KeyValuePair<WeakKey<TKey>, 
TValue>>)_hm).Remove(
-                new KeyValuePair<WeakKey<TKey>, TValue>(new 
WeakKey<TKey>(item.Key), item.Value));
-        }
-
-        public int Count
-        {
-            get
-            {
-                CleanIfNeeded();
-                return _hm.Count;
-            }
-        }
-
-        public bool IsReadOnly => false;
-
-        public bool ContainsKey(TKey key)
-        {
-            return _hm.ContainsKey(new WeakKey<TKey>(key));
-        }
-
-        public void Add(TKey key, TValue value)
-        {
-            CleanIfNeeded();
-            _hm.Add(new WeakKey<TKey>(key), value);
-        }
-
-        public bool Remove(TKey key)
-        {
-            return _hm.Remove(new WeakKey<TKey>(key));
-        }
-
-        public bool TryGetValue(TKey key, out TValue value)
-        {
-            return _hm.TryGetValue(new WeakKey<TKey>(key), out value);
-        }
-
-        public TValue this[TKey key]
-        {
-            get => _hm[new WeakKey<TKey>(key)];
-            set
-            {
-                CleanIfNeeded();
-                _hm[new WeakKey<TKey>(key)] = value;
-            }
-        }
-
-        public ICollection<TKey> Keys
-        {
-            get
-            {
-                CleanIfNeeded();
-                return new KeyCollection(_hm);
-            }
-        }
-
-        public ICollection<TValue> Values
-        {
-            get
-            {
-                CleanIfNeeded();
-                return _hm.Values;
-            }
-        }
-
-        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, 
TValue>[] array, int arrayIndex)
-        {
-            throw new NotSupportedException();
-        }
-
-        #region Nested Class: KeyCollection
-
-        private class KeyCollection : ICollection<TKey>
-        {
-            private readonly IDictionary<WeakKey<TKey>, TValue> _internalDict;
-
-            public KeyCollection(IDictionary<WeakKey<TKey>, TValue> dict)
-            {
-                _internalDict = dict;
-            }
-
-            public IEnumerator<TKey> GetEnumerator()
-            {
-                foreach (var key in _internalDict.Keys)
-                {
-                    if (key.TryGetTarget(out TKey target))
-                        yield return target;
-                }
-            }
-
-            IEnumerator IEnumerable.GetEnumerator()
-            {
-                return GetEnumerator();
-            }
-
-            public void CopyTo(TKey[] array, int arrayIndex)
-            {
-                throw new NotImplementedException("Implement this as needed");
-            }
-
-            public int Count => _internalDict.Count + 1;
-
-            public bool IsReadOnly => true;
-
-            #region Explicit Interface Definitions
-
-            bool ICollection<TKey>.Contains(TKey item)
-            {
-                throw new NotSupportedException();
-            }
-
-            void ICollection<TKey>.Add(TKey item)
-            {
-                throw new NotSupportedException();
-            }
-
-            void ICollection<TKey>.Clear()
-            {
-                throw new NotSupportedException();
-            }
-
-            bool ICollection<TKey>.Remove(TKey item)
-            {
-                throw new NotSupportedException();
-            }
-
-#endregion Explicit Interface Definitions
-        }
-
-        #endregion Nested Class: KeyCollection
-
-        /// <summary>
-        /// A weak reference wrapper for the hashtable keys. Whenever a 
key\value pair
-        /// is added to the hashtable, the key is wrapped using a WeakKey. 
WeakKey saves the
-        /// value of the original object hashcode for fast comparison.
-        /// </summary>
-        private class WeakKey<T> where T : class
-        {
-            private readonly WeakReference<T> reference;
-            private readonly int hashCode;
-
-            public WeakKey(T key)
-            {
-                if (key is null)
-                    throw new ArgumentNullException(nameof(key));
-
-                hashCode = key is null ? 0 : key.GetHashCode();
-                reference = new WeakReference<T>(key);
-            }
-
-            public override int GetHashCode()
-            {
-                return hashCode;
-            }
-
-            public override bool Equals(object obj)
-            {
-                if (obj is null) return false;
-                if (obj is WeakKey<T> other)
-                {
-                    bool gotThis = this.TryGetTarget(out T thisTarget), 
gotOther = other.TryGetTarget(out T otherTarget);
-                    if (gotThis && gotOther && thisTarget.Equals(otherTarget))
-                        return true;
-                    else if (gotThis == false && gotOther == false)
-                        return true;
-                }
-
-                return false;
-            }
-
-            public bool TryGetTarget(out T target) => 
reference.TryGetTarget(out target);
-        }
-    }
-}
-#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Util/RamUsageEstimator.cs 
b/src/Lucene.Net/Util/RamUsageEstimator.cs
index e1b210a..4cdd125 100644
--- a/src/Lucene.Net/Util/RamUsageEstimator.cs
+++ b/src/Lucene.Net/Util/RamUsageEstimator.cs
@@ -627,7 +627,8 @@ namespace Lucene.Net.Util
                     BindingFlags.Static);
                 foreach (FieldInfo f in fields)
                 {
-                    if (!f.IsStatic)
+                    // LUCENENET specific - exclude fields that are marked 
with the ExcludeFromRamUsageEstimationAttribute
+                    if (!f.IsStatic && 
f.GetCustomAttribute<ExcludeFromRamUsageEstimationAttribute>(inherit: false) is 
null)
                     {
                         shallowInstanceSize = 
AdjustForField(shallowInstanceSize, f);
 

Reply via email to