You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2023/10/18 23:46:54 UTC

[lucenenet] branch master updated: Removed dependency on Prism.Core (Fixes #872) (#875)

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 96b68625c Removed dependency on Prism.Core (Fixes #872) (#875)
96b68625c is described below

commit 96b68625cf7289672285376d1d188f9e33f22ddd
Author: Shad Storhaug <sh...@shadstorhaug.com>
AuthorDate: Thu Oct 19 06:46:47 2023 +0700

    Removed dependency on Prism.Core (Fixes #872) (#875)
    
    * Lucene.Net.Util: Renamed Events class > WeakEvents
    
    * SWEEP: Removed dependency on Prism.Core and brought the Prism.Events namespace into Lucene.Net.Util.Events including tests
---
 .build/dependencies.props                          |   1 -
 .rat-excludes                                      |   1 +
 .../Taxonomy/CachedOrdinalsReader.cs               |   8 +-
 .../Support/Util/Events/MockDelegateReference.cs   |  43 ++
 .../Util/Events/TestBackgroundEventSubscription.cs |  91 +++
 .../Support/Util/Events/TestDelegateReference.cs   | 214 +++++++
 .../Util/Events/TestDispatcherEventSubscription.cs | 123 ++++
 .../Support/Util/Events/TestEventAggregator.cs     |  45 ++
 .../Support/Util/Events/TestEventBase.cs           | 138 ++++
 .../Support/Util/Events/TestEventSubscription.cs   | 347 ++++++++++
 .../Support/Util/Events/TestPubSubEvent.cs         | 712 +++++++++++++++++++++
 src/Lucene.Net/Index/IndexReader.cs                |  20 +-
 src/Lucene.Net/Lucene.Net.csproj                   |   2 -
 src/Lucene.Net/Search/CachingWrapperFilter.cs      |   8 +-
 src/Lucene.Net/Search/FieldCacheImpl.cs            |   8 +-
 .../Util/Events/BackgroundEventSubscription.cs     |  84 +++
 .../Support/Util/Events/DelegateReference.cs       | 117 ++++
 .../Util/Events/DispatcherEventSubscription.cs     |  95 +++
 .../Support/Util/Events/EventAggregator.cs         |  68 ++
 src/Lucene.Net/Support/Util/Events/EventBase.cs    | 163 +++++
 .../Support/Util/Events/EventSubscription.cs       | 214 +++++++
 .../Support/Util/Events/IDelegateReference.cs      |  40 ++
 .../Support/Util/Events/IEventAggregator.cs        |  40 ++
 .../Support/Util/Events/IEventSubscription.cs      |  47 ++
 src/Lucene.Net/Support/Util/Events/PubSubEvent.cs  | 329 ++++++++++
 src/Lucene.Net/Support/Util/Events/Resources.cs    |  32 +
 .../Support/Util/Events/SubscriptionToken.cs       | 106 +++
 src/Lucene.Net/Support/Util/Events/ThreadOption.cs |  47 ++
 .../Support/Util/Events/WeakDelegatesManager.cs    |  71 ++
 .../Support/Util/{Events.cs => WeakEvents.cs}      |   4 +-
 30 files changed, 3191 insertions(+), 27 deletions(-)

diff --git a/.build/dependencies.props b/.build/dependencies.props
index f97e58774..cac54299e 100644
--- a/.build/dependencies.props
+++ b/.build/dependencies.props
@@ -69,7 +69,6 @@
     <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>
     <Spatial4nPackageVersion>0.4.1.1</Spatial4nPackageVersion>
diff --git a/.rat-excludes b/.rat-excludes
index 257b68536..cddb9ba01 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -27,6 +27,7 @@ psake/*
 KStemData\d+\.cs
 Snowball/*
 Egothor.Stemmer/*
+Events/*
 Sax/*
 JaspellTernarySearchTrie\.cs
 RectangularArrays\.cs
diff --git a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
index 3d58fadc5..19f85a64d 100644
--- a/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
+++ b/src/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
@@ -3,7 +3,7 @@ using Lucene.Net.Support;
 using Lucene.Net.Support.Threading;
 using Lucene.Net.Util;
 #if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
 #endif
 using System;
 using System.Collections.Generic;
@@ -103,7 +103,7 @@ namespace Lucene.Net.Facet.Taxonomy
                     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>());
+                    context.Reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>());
 #endif
                 }
                 return ords;
@@ -229,8 +229,8 @@ namespace Lucene.Net.Facet.Taxonomy
                 // 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);
+                var e = new WeakEvents.GetCacheKeysEventArgs();
+                eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>().Publish(e);
                 foreach (var key in e.CacheKeys)
                     if (ordsCache.TryGetValue(key, out CachedOrds value))
                         cachedOrdsList.Add(value);
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/MockDelegateReference.cs b/src/Lucene.Net.Tests/Support/Util/Events/MockDelegateReference.cs
new file mode 100644
index 000000000..7565e2f54
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/MockDelegateReference.cs
@@ -0,0 +1,43 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+    class MockDelegateReference : IDelegateReference
+    {
+        public Delegate Target { get; set; }
+
+        public MockDelegateReference()
+        {
+
+        }
+
+        public MockDelegateReference(Delegate target)
+        {
+            Target = target;
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestBackgroundEventSubscription.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestBackgroundEventSubscription.cs
new file mode 100644
index 000000000..d959c297a
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestBackgroundEventSubscription.cs
@@ -0,0 +1,91 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Threading;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+    [TestFixture]
+    public class TestBackgroundEventSubscription
+    {
+        [Test]
+        public void ShouldReceiveDelegateOnDifferentThread()
+        {
+            ManualResetEvent completeEvent = new ManualResetEvent(false);
+            SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+            SynchronizationContext calledSyncContext = null;
+            Action<object> action = delegate
+            {
+                calledSyncContext = SynchronizationContext.Current;
+                completeEvent.Set();
+            };
+
+            IDelegateReference actionDelegateReference = new MockDelegateReference() { Target = action };
+            IDelegateReference filterDelegateReference = new MockDelegateReference() { Target = (Predicate<object>)delegate { return true; } };
+
+            var eventSubscription = new BackgroundEventSubscription<object>(actionDelegateReference, filterDelegateReference);
+
+
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.NotNull(publishAction);
+
+            publishAction.Invoke(null);
+
+            completeEvent.WaitOne(5000);
+
+            Assert.AreNotEqual(SynchronizationContext.Current, calledSyncContext);
+        }
+
+        [Test]
+        public void ShouldReceiveDelegateOnDifferentThreadNonGeneric()
+        {
+            var completeEvent = new ManualResetEvent(false);
+            SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+            SynchronizationContext calledSyncContext = null;
+            Action action = delegate
+            {
+                calledSyncContext = SynchronizationContext.Current;
+                completeEvent.Set();
+            };
+
+            IDelegateReference actionDelegateReference = new MockDelegateReference() { Target = action };
+
+            var eventSubscription = new BackgroundEventSubscription(actionDelegateReference);
+
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.NotNull(publishAction);
+
+            publishAction.Invoke(null);
+
+            completeEvent.WaitOne(5000);
+
+            Assert.AreNotEqual(SynchronizationContext.Current, calledSyncContext);
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestDelegateReference.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestDelegateReference.cs
new file mode 100644
index 000000000..2d6ed5bfe
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestDelegateReference.cs
@@ -0,0 +1,214 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Threading.Tasks;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+    [TestFixture]
+    public class TestDelegateReference
+    {
+        [Test]
+        public void KeepAlivePreventsDelegateFromBeingCollected()
+        {
+            var delegates = new SomeClassHandler();
+            var delegateReference = new DelegateReference((Action<string>)delegates.DoEvent, true);
+
+            delegates = null;
+            GC.Collect();
+
+            Assert.NotNull(delegateReference.Target);
+        }
+
+        [Test]
+        public async Task NotKeepAliveAllowsDelegateToBeCollected()
+        {
+            var delegates = new SomeClassHandler();
+            var delegateReference = new DelegateReference((Action<string>)delegates.DoEvent, false);
+
+            delegates = null;
+            await Task.Delay(100);
+            GC.Collect();
+
+            Assert.Null(delegateReference.Target);
+        }
+
+        [Test]
+        public async Task NotKeepAliveKeepsDelegateIfStillAlive()
+        {
+            var delegates = new SomeClassHandler();
+            var delegateReference = new DelegateReference((Action<string>)delegates.DoEvent, false);
+
+            GC.Collect();
+
+            Assert.NotNull(delegateReference.Target);
+
+            GC.KeepAlive(delegates);  //Makes delegates ineligible for garbage collection until this point (to prevent oompiler optimizations that may release the referenced object prematurely).
+            delegates = null;
+            await Task.Delay(100);
+            GC.Collect();
+
+            Assert.Null(delegateReference.Target);
+        }
+
+        [Test]
+        public void TargetShouldReturnAction()
+        {
+            var classHandler = new SomeClassHandler();
+            Action<string> myAction = new Action<string>(classHandler.MyAction);
+
+            var weakAction = new DelegateReference(myAction, false);
+
+            ((Action<string>)weakAction.Target)("payload");
+            Assert.AreEqual("payload", classHandler.MyActionArg);
+        }
+
+        [Test]
+        public async Task ShouldAllowCollectionOfOriginalDelegate()
+        {
+            var classHandler = new SomeClassHandler();
+            Action<string> myAction = new Action<string>(classHandler.MyAction);
+
+            var weakAction = new DelegateReference(myAction, false);
+
+            var originalAction = new WeakReference(myAction);
+            myAction = null;
+            await Task.Delay(100);
+            GC.Collect();
+            Assert.False(originalAction.IsAlive);
+
+            ((Action<string>)weakAction.Target)("payload");
+            Assert.AreEqual("payload", classHandler.MyActionArg);
+        }
+
+        [Test]
+        public async Task ShouldReturnNullIfTargetNotAlive()
+        {
+            SomeClassHandler handler = new SomeClassHandler();
+            var weakHandlerRef = new WeakReference(handler);
+
+            var action = new DelegateReference((Action<string>)handler.DoEvent, false);
+
+            handler = null;
+            await Task.Delay(100);
+            GC.Collect();
+            Assert.False(weakHandlerRef.IsAlive);
+
+            Assert.Null(action.Target);
+        }
+
+        [Test]
+        public void WeakDelegateWorksWithStaticMethodDelegates()
+        {
+            var action = new DelegateReference((Action)SomeClassHandler.StaticMethod, false);
+
+            Assert.NotNull(action.Target);
+        }
+
+        [Test]
+        public void TargetEqualsActionShouldReturnTrue()
+        {
+            var classHandler = new SomeClassHandler();
+            Action<string> myAction = new Action<string>(classHandler.MyAction);
+
+            var weakAction = new DelegateReference(myAction, false);
+
+            Assert.True(weakAction.TargetEquals(new Action<string>(classHandler.MyAction)));
+        }
+
+        [Test]
+        public async Task TargetEqualsNullShouldReturnTrueIfTargetNotAlive()
+        {
+            SomeClassHandler handler = new SomeClassHandler();
+            var weakHandlerRef = new WeakReference(handler);
+
+            var action = new DelegateReference((Action<string>)handler.DoEvent, false);
+
+            handler = null;
+
+            // Intentional delay to encourage Garbage Collection to actually occur
+            await Task.Delay(100);
+            GC.Collect();
+            Assert.False(weakHandlerRef.IsAlive);
+
+            Assert.True(action.TargetEquals(null));
+        }
+
+        [Test]
+        public void TargetEqualsNullShouldReturnFalseIfTargetAlive()
+        {
+            SomeClassHandler handler = new SomeClassHandler();
+            var weakHandlerRef = new WeakReference(handler);
+
+            var action = new DelegateReference((Action<string>)handler.DoEvent, false);
+
+            Assert.False(action.TargetEquals(null));
+            Assert.True(weakHandlerRef.IsAlive);
+            GC.KeepAlive(handler);
+        }
+
+        [Test]
+        public void TargetEqualsWorksWithStaticMethodDelegates()
+        {
+            var action = new DelegateReference((Action)SomeClassHandler.StaticMethod, false);
+
+            Assert.True(action.TargetEquals((Action)SomeClassHandler.StaticMethod));
+        }
+
+        //todo: fix
+        //[Test]
+        //public void NullDelegateThrows()
+        //{
+        //    Assert.ThrowsException<ArgumentNullException>(() =>
+        //    {
+        //        var action = new DelegateReference(null, true);
+        //    });
+        //}
+
+        public class SomeClassHandler
+        {
+            public string MyActionArg;
+
+            public void DoEvent(string value)
+            {
+                string myValue = value;
+            }
+
+            public static void StaticMethod()
+            {
+#pragma warning disable 0219
+                int i = 0;
+#pragma warning restore 0219
+            }
+
+            public void MyAction(string arg)
+            {
+                MyActionArg = arg;
+            }
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestDispatcherEventSubscription.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestDispatcherEventSubscription.cs
new file mode 100644
index 000000000..51eed98a8
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestDispatcherEventSubscription.cs
@@ -0,0 +1,123 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Threading;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+    [TestFixture]
+    public class TestDispatcherEventSubscription
+    {
+        [Test]
+        public void ShouldCallInvokeOnDispatcher()
+        {
+            DispatcherEventSubscription<object> eventSubscription = null;
+
+            IDelegateReference actionDelegateReference = new MockDelegateReference()
+            {
+                Target = (Action<object>)(arg =>
+                {
+                    return;
+                })
+            };
+
+            IDelegateReference filterDelegateReference = new MockDelegateReference
+            {
+                Target = (Predicate<object>)(arg => true)
+            };
+            var mockSyncContext = new MockSynchronizationContext();
+
+            eventSubscription = new DispatcherEventSubscription<object>(actionDelegateReference, filterDelegateReference, mockSyncContext);
+
+            eventSubscription.GetExecutionStrategy().Invoke(new object[0]);
+
+            Assert.True(mockSyncContext.InvokeCalled);
+        }
+
+        [Test]
+        public void ShouldCallInvokeOnDispatcherNonGeneric()
+        {
+            DispatcherEventSubscription eventSubscription = null;
+
+            IDelegateReference actionDelegateReference = new MockDelegateReference()
+            {
+                Target = (Action)(() =>
+                { })
+            };
+
+            var mockSyncContext = new MockSynchronizationContext();
+
+            eventSubscription = new DispatcherEventSubscription(actionDelegateReference, mockSyncContext);
+
+            eventSubscription.GetExecutionStrategy().Invoke(new object[0]);
+
+            Assert.True(mockSyncContext.InvokeCalled);
+        }
+
+        [Test]
+        public void ShouldPassParametersCorrectly()
+        {
+            IDelegateReference actionDelegateReference = new MockDelegateReference()
+            {
+                Target =
+                    (Action<object>)(arg1 =>
+                    {
+                        return;
+                    })
+            };
+            IDelegateReference filterDelegateReference = new MockDelegateReference
+            {
+                Target = (Predicate<object>)(arg => true)
+            };
+
+            var mockSyncContext = new MockSynchronizationContext();
+
+            DispatcherEventSubscription<object> eventSubscription = new DispatcherEventSubscription<object>(actionDelegateReference, filterDelegateReference, mockSyncContext);
+
+            var executionStrategy = eventSubscription.GetExecutionStrategy();
+            Assert.NotNull(executionStrategy);
+
+            object argument1 = new object();
+
+            executionStrategy.Invoke(new[] { argument1 });
+
+            Assert.AreSame(argument1, mockSyncContext.InvokeArg);
+        }
+    }
+
+    internal class MockSynchronizationContext : SynchronizationContext
+    {
+        public bool InvokeCalled;
+        public object InvokeArg;
+
+        public override void Post(SendOrPostCallback d, object state)
+        {
+            InvokeCalled = true;
+            InvokeArg = state;
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestEventAggregator.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestEventAggregator.cs
new file mode 100644
index 000000000..5f945013f
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestEventAggregator.cs
@@ -0,0 +1,45 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+    [TestFixture]
+    public class TestEventAggregator
+    {
+        [Test]
+        public void GetReturnsSingleInstancesOfSameEventType()
+        {
+            var eventAggregator = new EventAggregator();
+            var instance1 = eventAggregator.GetEvent<MockEventBase>();
+            var instance2 = eventAggregator.GetEvent<MockEventBase>();
+
+            Assert.AreSame(instance2, instance1);
+        }
+
+        internal class MockEventBase : EventBase { }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestEventBase.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestEventBase.cs
new file mode 100644
index 000000000..c71ddcb3a
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestEventBase.cs
@@ -0,0 +1,138 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+    [TestFixture]
+    public class TestEventBase
+    {
+        [Test]
+        public void CanPublishSimpleEvents()
+        {
+            var eventBase = new TestableEventBase();
+            var eventSubscription = new MockEventSubscription();
+            bool eventPublished = false;
+            eventSubscription.GetPublishActionReturnValue = delegate
+            {
+                eventPublished = true;
+            };
+            eventBase.Subscribe(eventSubscription);
+
+            eventBase.Publish();
+
+            Assert.True(eventSubscription.GetPublishActionCalled);
+            Assert.True(eventPublished);
+        }
+
+        [Test]
+        public void CanHaveMultipleSubscribersAndRaiseCustomEvent()
+        {
+            var customEvent = new TestableEventBase();
+            Payload payload = new Payload();
+            object[] received1 = null;
+            object[] received2 = null;
+            var eventSubscription1 = new MockEventSubscription();
+            eventSubscription1.GetPublishActionReturnValue = delegate (object[] args) { received1 = args; };
+            var eventSubscription2 = new MockEventSubscription();
+            eventSubscription2.GetPublishActionReturnValue = delegate (object[] args) { received2 = args; };
+
+            customEvent.Subscribe(eventSubscription1);
+            customEvent.Subscribe(eventSubscription2);
+
+            customEvent.Publish(payload);
+
+            Assert.AreEqual(1, received1.Length);
+            Assert.AreSame(received1[0], payload);
+
+            Assert.AreEqual(1, received2.Length);
+            Assert.AreSame(received2[0], payload);
+        }
+
+        [Test]
+        public void ShouldSubscribeAndUnsubscribe()
+        {
+            var eventBase = new TestableEventBase();
+
+            var eventSubscription = new MockEventSubscription();
+            eventBase.Subscribe(eventSubscription);
+
+            Assert.NotNull(eventSubscription.SubscriptionToken);
+            Assert.True(eventBase.Contains(eventSubscription.SubscriptionToken));
+
+            eventBase.Unsubscribe(eventSubscription.SubscriptionToken);
+
+            Assert.False(eventBase.Contains(eventSubscription.SubscriptionToken));
+        }
+
+        [Test]
+        public void WhenEventSubscriptionActionIsNullPruneItFromList()
+        {
+            var eventBase = new TestableEventBase();
+
+            var eventSubscription = new MockEventSubscription();
+            eventSubscription.GetPublishActionReturnValue = null;
+
+            var token = eventBase.Subscribe(eventSubscription);
+
+            eventBase.Publish();
+
+            Assert.False(eventBase.Contains(token));
+        }
+
+
+        class TestableEventBase : EventBase
+        {
+            public SubscriptionToken Subscribe(IEventSubscription subscription)
+            {
+                return base.InternalSubscribe(subscription);
+            }
+
+            public void Publish(params object[] arguments)
+            {
+                base.InternalPublish(arguments);
+            }
+        }
+
+        class MockEventSubscription : IEventSubscription
+        {
+            public Action<object[]> GetPublishActionReturnValue;
+            public bool GetPublishActionCalled;
+
+            public Action<object[]> GetExecutionStrategy()
+            {
+                GetPublishActionCalled = true;
+                return GetPublishActionReturnValue;
+            }
+
+            public SubscriptionToken SubscriptionToken { get; set; }
+        }
+
+        class Payload { }
+
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestEventSubscription.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestEventSubscription.cs
new file mode 100644
index 000000000..dc78cdfa0
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestEventSubscription.cs
@@ -0,0 +1,347 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+    [TestFixture]
+    public class TestEventSubscription
+    {
+        [Test]
+        public void NullTargetInActionThrows()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var actionDelegateReference = new MockDelegateReference()
+                {
+                    Target = null
+                };
+                var filterDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Predicate<object>)(arg =>
+                    {
+                        return true;
+                    })
+                };
+                var eventSubscription = new EventSubscription<object>(actionDelegateReference,
+                                                                                filterDelegateReference);
+            });
+
+        }
+
+        [Test]
+        public void NullTargetInActionThrowsNonGeneric()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var actionDelegateReference = new MockDelegateReference()
+                {
+                    Target = null
+                };
+                var eventSubscription = new EventSubscription(actionDelegateReference);
+            });
+        }
+
+        [Test]
+        public void DifferentTargetTypeInActionThrows()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var actionDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Action<int>)delegate { }
+                };
+                var filterDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Predicate<string>)(arg =>
+                    {
+                        return true;
+                    })
+                };
+                var eventSubscription = new EventSubscription<string>(actionDelegateReference,
+                                                                                filterDelegateReference);
+            });
+        }
+
+        [Test]
+        public void DifferentTargetTypeInActionThrowsNonGeneric()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var actionDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Action<int>)delegate { }
+                };
+
+                var eventSubscription = new EventSubscription(actionDelegateReference);
+            });
+        }
+
+        [Test]
+        public void NullActionThrows()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                var filterDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Predicate<object>)(arg =>
+                    {
+                        return true;
+                    })
+                };
+                var eventSubscription = new EventSubscription<object>(null, filterDelegateReference);
+            });
+        }
+
+        [Test]
+        public void NullActionThrowsNonGeneric()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                var eventSubscription = new EventSubscription(null);
+            });
+        }
+
+        [Test]
+        public void NullTargetInFilterThrows()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var actionDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Action<object>)delegate { }
+                };
+
+                var filterDelegateReference = new MockDelegateReference()
+                {
+                    Target = null
+                };
+                var eventSubscription = new EventSubscription<object>(actionDelegateReference,
+                                                                                filterDelegateReference);
+            });
+        }
+
+
+        [Test]
+        public void DifferentTargetTypeInFilterThrows()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var actionDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Action<string>)delegate { }
+                };
+
+                var filterDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Predicate<int>)(arg =>
+                    {
+                        return true;
+                    })
+                };
+
+                var eventSubscription = new EventSubscription<string>(actionDelegateReference,
+                                                                                filterDelegateReference);
+            });
+        }
+
+        [Test]
+        public void NullFilterThrows()
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                var actionDelegateReference = new MockDelegateReference()
+                {
+                    Target = (Action<object>)delegate { }
+                };
+
+                var eventSubscription = new EventSubscription<object>(actionDelegateReference,
+                                                                                null);
+            });
+        }
+
+        [Test]
+        public void CanInitEventSubscription()
+        {
+            var actionDelegateReference = new MockDelegateReference((Action<object>)delegate { });
+            var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate { return true; });
+            var eventSubscription = new EventSubscription<object>(actionDelegateReference, filterDelegateReference);
+
+            var subscriptionToken = new SubscriptionToken(t => { });
+
+            eventSubscription.SubscriptionToken = subscriptionToken;
+
+            Assert.AreSame(actionDelegateReference.Target, eventSubscription.Action);
+            Assert.AreSame(filterDelegateReference.Target, eventSubscription.Filter);
+            Assert.AreSame(subscriptionToken, eventSubscription.SubscriptionToken);
+        }
+
+        [Test]
+        public void CanInitEventSubscriptionNonGeneric()
+        {
+            var actionDelegateReference = new MockDelegateReference((Action)delegate { });
+            var eventSubscription = new EventSubscription(actionDelegateReference);
+
+            var subscriptionToken = new SubscriptionToken(t => { });
+
+            eventSubscription.SubscriptionToken = subscriptionToken;
+
+            Assert.AreSame(actionDelegateReference.Target, eventSubscription.Action);
+            Assert.AreSame(subscriptionToken, eventSubscription.SubscriptionToken);
+        }
+
+        [Test]
+        public void GetPublishActionReturnsDelegateThatExecutesTheFilterAndThenTheAction()
+        {
+            var executedDelegates = new List<string>();
+            var actionDelegateReference =
+                new MockDelegateReference((Action<object>)delegate { executedDelegates.Add("Action"); });
+
+            var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate
+            {
+                executedDelegates.Add(
+                    "Filter");
+                return true;
+            });
+
+            var eventSubscription = new EventSubscription<object>(actionDelegateReference, filterDelegateReference);
+
+
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.NotNull(publishAction);
+
+            publishAction.Invoke(null);
+
+            Assert.AreEqual(2, executedDelegates.Count);
+            Assert.AreEqual("Filter", executedDelegates[0]);
+            Assert.AreEqual("Action", executedDelegates[1]);
+        }
+
+        [Test]
+        public void GetPublishActionReturnsNullIfActionIsNull()
+        {
+            var actionDelegateReference = new MockDelegateReference((Action<object>)delegate { });
+            var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate { return true; });
+
+            var eventSubscription = new EventSubscription<object>(actionDelegateReference, filterDelegateReference);
+
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.NotNull(publishAction);
+
+            actionDelegateReference.Target = null;
+
+            publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.Null(publishAction);
+        }
+
+        [Test]
+        public void GetPublishActionReturnsNullIfActionIsNullNonGeneric()
+        {
+            var actionDelegateReference = new MockDelegateReference((Action)delegate { });
+
+            var eventSubscription = new EventSubscription(actionDelegateReference);
+
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.NotNull(publishAction);
+
+            actionDelegateReference.Target = null;
+
+            publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.Null(publishAction);
+        }
+
+        [Test]
+        public void GetPublishActionReturnsNullIfFilterIsNull()
+        {
+            var actionDelegateReference = new MockDelegateReference((Action<object>)delegate { });
+            var filterDelegateReference = new MockDelegateReference((Predicate<object>)delegate { return true; });
+
+            var eventSubscription = new EventSubscription<object>(actionDelegateReference, filterDelegateReference);
+
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.NotNull(publishAction);
+
+            filterDelegateReference.Target = null;
+
+            publishAction = eventSubscription.GetExecutionStrategy();
+
+            Assert.Null(publishAction);
+        }
+
+        [Test]
+        public void GetPublishActionDoesNotExecuteActionIfFilterReturnsFalse()
+        {
+            bool actionExecuted = false;
+            var actionDelegateReference = new MockDelegateReference()
+            {
+                Target = (Action<int>)delegate { actionExecuted = true; }
+            };
+            var filterDelegateReference = new MockDelegateReference((Predicate<int>)delegate
+            {
+                return false;
+            });
+
+            var eventSubscription = new EventSubscription<int>(actionDelegateReference, filterDelegateReference);
+
+
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            publishAction.Invoke(new object[] { null });
+
+            Assert.False(actionExecuted);
+        }
+
+        [Test]
+        public void StrategyPassesArgumentToDelegates()
+        {
+            string passedArgumentToAction = null;
+            string passedArgumentToFilter = null;
+
+            var actionDelegateReference = new MockDelegateReference((Action<string>)(obj => passedArgumentToAction = obj));
+            var filterDelegateReference = new MockDelegateReference((Predicate<string>)(obj =>
+            {
+                passedArgumentToFilter = obj;
+                return true;
+            }));
+
+            var eventSubscription = new EventSubscription<string>(actionDelegateReference, filterDelegateReference);
+            var publishAction = eventSubscription.GetExecutionStrategy();
+
+            publishAction.Invoke(new[] { "TestString" });
+
+            Assert.AreEqual("TestString", passedArgumentToAction);
+            Assert.AreEqual("TestString", passedArgumentToFilter);
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net.Tests/Support/Util/Events/TestPubSubEvent.cs b/src/Lucene.Net.Tests/Support/Util/Events/TestPubSubEvent.cs
new file mode 100644
index 000000000..710545ecc
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Util/Events/TestPubSubEvent.cs
@@ -0,0 +1,712 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Assert = Lucene.Net.TestFramework.Assert;
+
+namespace Lucene.Net.Util.Events
+{
+    [TestFixture]
+    public class TestPubSubEvent
+    {
+        [Test]
+        public void EnsureSubscriptionListIsEmptyAfterPublishingAMessage()
+        {
+            var pubSubEvent = new TestablePubSubEvent<string>();
+            SubscribeExternalActionWithoutReference(pubSubEvent);
+            GC.Collect();
+            pubSubEvent.Publish("testPayload");
+            Assert.True(pubSubEvent.BaseSubscriptions.Count == 0, "Subscriptionlist is not empty");
+        }
+
+        [Test]
+        public void EnsureSubscriptionListIsNotEmptyWithoutPublishOrSubscribe()
+        {
+            var pubSubEvent = new TestablePubSubEvent<string>();
+            SubscribeExternalActionWithoutReference(pubSubEvent);
+            GC.Collect();
+            Assert.True(pubSubEvent.BaseSubscriptions.Count == 1, "Subscriptionlist is empty");
+        }
+
+        [Test]
+        public void EnsureSubscriptionListIsEmptyAfterSubscribeAgainAMessage()
+        {
+            var pubSubEvent = new TestablePubSubEvent<string>();
+            SubscribeExternalActionWithoutReference(pubSubEvent);
+            GC.Collect();
+            SubscribeExternalActionWithoutReference(pubSubEvent);
+            pubSubEvent.Prune();
+            Assert.True(pubSubEvent.BaseSubscriptions.Count == 1, "Subscriptionlist is empty");
+        }
+
+        private static void SubscribeExternalActionWithoutReference(TestablePubSubEvent<string> pubSubEvent)
+        {
+            pubSubEvent.Subscribe(new ExternalAction().ExecuteAction);
+        }
+
+
+        [Test]
+        public void CanSubscribeAndRaiseEvent()
+        {
+            TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+            bool published = false;
+            pubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, true, delegate { return true; });
+            pubSubEvent.Publish(null);
+
+            Assert.True(published);
+        }
+
+        [Test]
+        public void CanSubscribeAndRaiseEventNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+            bool published = false;
+            pubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, true);
+            pubSubEvent.Publish();
+
+            Assert.True(published);
+        }
+
+        [Test]
+        public void CanSubscribeAndRaiseCustomEvent()
+        {
+            var customEvent = new TestablePubSubEvent<Payload>();
+            Payload payload = new Payload();
+            var action = new ActionHelper();
+            customEvent.Subscribe(action.Action);
+
+            customEvent.Publish(payload);
+
+            Assert.AreSame(action.ActionArg<Payload>(), payload);
+        }
+
+        [Test]
+        public void CanHaveMultipleSubscribersAndRaiseCustomEvent()
+        {
+            var customEvent = new TestablePubSubEvent<Payload>();
+            Payload payload = new Payload();
+            var action1 = new ActionHelper();
+            var action2 = new ActionHelper();
+            customEvent.Subscribe(action1.Action);
+            customEvent.Subscribe(action2.Action);
+
+            customEvent.Publish(payload);
+
+            Assert.AreSame(action1.ActionArg<Payload>(), payload);
+            Assert.AreSame(action2.ActionArg<Payload>(), payload);
+        }
+
+        [Test]
+        public void CanHaveMultipleSubscribersAndRaiseEvent()
+        {
+            var customEvent = new TestablePubSubEvent();
+            var action1 = new ActionHelper();
+            var action2 = new ActionHelper();
+            customEvent.Subscribe(action1.Action);
+            customEvent.Subscribe(action2.Action);
+
+            customEvent.Publish();
+
+            Assert.True(action1.ActionCalled);
+            Assert.True(action2.ActionCalled);
+        }
+
+        [Test]
+        public void SubscribeTakesExecuteDelegateThreadOptionAndFilter()
+        {
+            TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+            var action = new ActionHelper();
+            pubSubEvent.Subscribe(action.Action);
+
+            pubSubEvent.Publish("test");
+
+            Assert.AreEqual("test", action.ActionArg<string>());
+
+        }
+
+        [Test]
+        public void FilterEnablesActionTarget()
+        {
+            TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+            var goodFilter = new MockFilter { FilterReturnValue = true };
+            var actionGoodFilter = new ActionHelper();
+            var badFilter = new MockFilter { FilterReturnValue = false };
+            var actionBadFilter = new ActionHelper();
+            pubSubEvent.Subscribe(actionGoodFilter.Action, ThreadOption.PublisherThread, true, goodFilter.FilterString);
+            pubSubEvent.Subscribe(actionBadFilter.Action, ThreadOption.PublisherThread, true, badFilter.FilterString);
+
+            pubSubEvent.Publish("test");
+
+            Assert.True(actionGoodFilter.ActionCalled);
+            Assert.False(actionBadFilter.ActionCalled);
+
+        }
+
+        [Test]
+        public void FilterEnablesActionTarget_Weak()
+        {
+            TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+            var goodFilter = new MockFilter { FilterReturnValue = true };
+            var actionGoodFilter = new ActionHelper();
+            var badFilter = new MockFilter { FilterReturnValue = false };
+            var actionBadFilter = new ActionHelper();
+            pubSubEvent.Subscribe(actionGoodFilter.Action, goodFilter.FilterString);
+            pubSubEvent.Subscribe(actionBadFilter.Action, badFilter.FilterString);
+
+            pubSubEvent.Publish("test");
+
+            Assert.True(actionGoodFilter.ActionCalled);
+            Assert.False(actionBadFilter.ActionCalled);
+
+        }
+
+        [Test]
+        public void SubscribeDefaultsThreadOptionAndNoFilter()
+        {
+            TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+            SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+            SynchronizationContext calledSyncContext = null;
+            var myAction = new ActionHelper()
+            {
+                ActionToExecute =
+                    () => calledSyncContext = SynchronizationContext.Current
+            };
+            pubSubEvent.Subscribe(myAction.Action);
+
+            pubSubEvent.Publish("test");
+
+            Assert.AreEqual(SynchronizationContext.Current, calledSyncContext);
+        }
+
+        [Test]
+        public void SubscribeDefaultsThreadOptionAndNoFilterNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+            SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
+            SynchronizationContext calledSyncContext = null;
+            var myAction = new ActionHelper()
+            {
+                ActionToExecute =
+                    () => calledSyncContext = SynchronizationContext.Current
+            };
+            pubSubEvent.Subscribe(myAction.Action);
+
+            pubSubEvent.Publish();
+
+            Assert.AreEqual(SynchronizationContext.Current, calledSyncContext);
+        }
+
+        [Test]
+        public void ShouldUnsubscribeFromPublisherThread()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+
+            var actionEvent = new ActionHelper();
+            PubSubEvent.Subscribe(
+                actionEvent.Action,
+                ThreadOption.PublisherThread);
+
+            Assert.True(PubSubEvent.Contains(actionEvent.Action));
+            PubSubEvent.Unsubscribe(actionEvent.Action);
+            Assert.False(PubSubEvent.Contains(actionEvent.Action));
+        }
+
+        [Test]
+        public void ShouldUnsubscribeFromPublisherThreadNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+
+            var actionEvent = new ActionHelper();
+            pubSubEvent.Subscribe(
+                actionEvent.Action,
+                ThreadOption.PublisherThread);
+
+            Assert.True(pubSubEvent.Contains(actionEvent.Action));
+            pubSubEvent.Unsubscribe(actionEvent.Action);
+            Assert.False(pubSubEvent.Contains(actionEvent.Action));
+        }
+
+        [Test]
+        public void UnsubscribeShouldNotFailWithNonSubscriber()
+        {
+            TestablePubSubEvent<string> pubSubEvent = new TestablePubSubEvent<string>();
+
+            Action<string> subscriber = delegate { };
+            pubSubEvent.Unsubscribe(subscriber);
+        }
+
+        [Test]
+        public void UnsubscribeShouldNotFailWithNonSubscriberNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+
+            Action subscriber = delegate { };
+            pubSubEvent.Unsubscribe(subscriber);
+        }
+
+        [Test]
+        public void ShouldUnsubscribeFromBackgroundThread()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+
+            var actionEvent = new ActionHelper();
+            PubSubEvent.Subscribe(
+                actionEvent.Action,
+                ThreadOption.BackgroundThread);
+
+            Assert.True(PubSubEvent.Contains(actionEvent.Action));
+            PubSubEvent.Unsubscribe(actionEvent.Action);
+            Assert.False(PubSubEvent.Contains(actionEvent.Action));
+        }
+
+        [Test]
+        public void ShouldUnsubscribeFromBackgroundThreadNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+
+            var actionEvent = new ActionHelper();
+            pubSubEvent.Subscribe(
+                actionEvent.Action,
+                ThreadOption.BackgroundThread);
+
+            Assert.True(pubSubEvent.Contains(actionEvent.Action));
+            pubSubEvent.Unsubscribe(actionEvent.Action);
+            Assert.False(pubSubEvent.Contains(actionEvent.Action));
+        }
+
+        [Test]
+        public void ShouldUnsubscribeFromUIThread()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+            PubSubEvent.SynchronizationContext = new SynchronizationContext();
+
+            var actionEvent = new ActionHelper();
+            PubSubEvent.Subscribe(
+                actionEvent.Action,
+                ThreadOption.UIThread);
+
+            Assert.True(PubSubEvent.Contains(actionEvent.Action));
+            PubSubEvent.Unsubscribe(actionEvent.Action);
+            Assert.False(PubSubEvent.Contains(actionEvent.Action));
+        }
+
+        [Test]
+        public void ShouldUnsubscribeFromUIThreadNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+            pubSubEvent.SynchronizationContext = new SynchronizationContext();
+
+            var actionEvent = new ActionHelper();
+            pubSubEvent.Subscribe(
+                actionEvent.Action,
+                ThreadOption.UIThread);
+
+            Assert.True(pubSubEvent.Contains(actionEvent.Action));
+            pubSubEvent.Unsubscribe(actionEvent.Action);
+            Assert.False(pubSubEvent.Contains(actionEvent.Action));
+        }
+
+        [Test]
+        public void ShouldUnsubscribeASingleDelegate()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+
+            int callCount = 0;
+
+            var actionEvent = new ActionHelper() { ActionToExecute = () => callCount++ };
+            PubSubEvent.Subscribe(actionEvent.Action);
+            PubSubEvent.Subscribe(actionEvent.Action);
+
+            PubSubEvent.Publish(null);
+            Assert.AreEqual<int>(2, callCount);
+
+            callCount = 0;
+            PubSubEvent.Unsubscribe(actionEvent.Action);
+            PubSubEvent.Publish(null);
+            Assert.AreEqual<int>(1, callCount);
+        }
+
+        [Test]
+        public void ShouldUnsubscribeASingleDelegateNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+
+            int callCount = 0;
+
+            var actionEvent = new ActionHelper() { ActionToExecute = () => callCount++ };
+            pubSubEvent.Subscribe(actionEvent.Action);
+            pubSubEvent.Subscribe(actionEvent.Action);
+
+            pubSubEvent.Publish();
+            Assert.AreEqual<int>(2, callCount);
+
+            callCount = 0;
+            pubSubEvent.Unsubscribe(actionEvent.Action);
+            pubSubEvent.Publish();
+            Assert.AreEqual<int>(1, callCount);
+        }
+
+        [Test]
+        public async Task ShouldNotExecuteOnGarbageCollectedDelegateReferenceWhenNotKeepAlive()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+
+            ExternalAction externalAction = new ExternalAction();
+            PubSubEvent.Subscribe(externalAction.ExecuteAction);
+
+            PubSubEvent.Publish("testPayload");
+            Assert.AreEqual("testPayload", externalAction.PassedValue);
+
+            WeakReference actionEventReference = new WeakReference(externalAction);
+            externalAction = null;
+            await Task.Delay(100);
+            GC.Collect();
+            Assert.False(actionEventReference.IsAlive);
+
+            PubSubEvent.Publish("testPayload");
+        }
+
+        [Test]
+        public async Task ShouldNotExecuteOnGarbageCollectedDelegateReferenceWhenNotKeepAliveNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+
+            var externalAction = new ExternalAction();
+            pubSubEvent.Subscribe(externalAction.ExecuteAction);
+
+            pubSubEvent.Publish();
+            Assert.True(externalAction.Executed);
+
+            var actionEventReference = new WeakReference(externalAction);
+            externalAction = null;
+            await Task.Delay(100);
+            GC.Collect();
+            Assert.False(actionEventReference.IsAlive);
+
+            pubSubEvent.Publish();
+        }
+
+        [Test]
+        public async Task ShouldNotExecuteOnGarbageCollectedFilterReferenceWhenNotKeepAlive()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+
+            bool wasCalled = false;
+            var actionEvent = new ActionHelper() { ActionToExecute = () => wasCalled = true };
+
+            ExternalFilter filter = new ExternalFilter();
+            PubSubEvent.Subscribe(actionEvent.Action, ThreadOption.PublisherThread, false, filter.AlwaysTrueFilter);
+
+            PubSubEvent.Publish("testPayload");
+            Assert.True(wasCalled);
+
+            wasCalled = false;
+            WeakReference filterReference = new WeakReference(filter);
+            filter = null;
+            await Task.Delay(100);
+            GC.Collect();
+            Assert.False(filterReference.IsAlive);
+
+            PubSubEvent.Publish("testPayload");
+            Assert.False(wasCalled);
+        }
+
+        [Test]
+        public void CanAddSubscriptionWhileEventIsFiring()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+
+            var emptyAction = new ActionHelper();
+            var subscriptionAction = new ActionHelper
+            {
+                ActionToExecute = (() =>
+                                                  PubSubEvent.Subscribe(
+                                                      emptyAction.Action))
+            };
+
+            PubSubEvent.Subscribe(subscriptionAction.Action);
+
+            Assert.False(PubSubEvent.Contains(emptyAction.Action));
+
+            PubSubEvent.Publish(null);
+
+            Assert.True((PubSubEvent.Contains(emptyAction.Action)));
+        }
+
+        [Test]
+        public void CanAddSubscriptionWhileEventIsFiringNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+
+            var emptyAction = new ActionHelper();
+            var subscriptionAction = new ActionHelper
+            {
+                ActionToExecute = (() =>
+                                          pubSubEvent.Subscribe(
+                                          emptyAction.Action))
+            };
+
+            pubSubEvent.Subscribe(subscriptionAction.Action);
+
+            Assert.False(pubSubEvent.Contains(emptyAction.Action));
+
+            pubSubEvent.Publish();
+
+            Assert.True((pubSubEvent.Contains(emptyAction.Action)));
+        }
+
+        [Test]
+        public void InlineDelegateDeclarationsDoesNotGetCollectedIncorrectlyWithWeakReferences()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+            bool published = false;
+            PubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, false, delegate { return true; });
+            GC.Collect();
+            PubSubEvent.Publish(null);
+
+            Assert.True(published);
+        }
+
+        [Test]
+        public void InlineDelegateDeclarationsDoesNotGetCollectedIncorrectlyWithWeakReferencesNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+            bool published = false;
+            pubSubEvent.Subscribe(delegate { published = true; }, ThreadOption.PublisherThread, false);
+            GC.Collect();
+            pubSubEvent.Publish();
+
+            Assert.True(published);
+        }
+
+        [Test]
+        public void ShouldNotGarbageCollectDelegateReferenceWhenUsingKeepAlive()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+
+            var externalAction = new ExternalAction();
+            PubSubEvent.Subscribe(externalAction.ExecuteAction, ThreadOption.PublisherThread, true);
+
+            WeakReference actionEventReference = new WeakReference(externalAction);
+            externalAction = null;
+            GC.Collect();
+            GC.Collect();
+            Assert.True(actionEventReference.IsAlive);
+
+            PubSubEvent.Publish("testPayload");
+
+            Assert.AreEqual("testPayload", ((ExternalAction)actionEventReference.Target).PassedValue);
+        }
+
+        [Test]
+        public void ShouldNotGarbageCollectDelegateReferenceWhenUsingKeepAliveNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+
+            var externalAction = new ExternalAction();
+            pubSubEvent.Subscribe(externalAction.ExecuteAction, ThreadOption.PublisherThread, true);
+
+            WeakReference actionEventReference = new WeakReference(externalAction);
+            externalAction = null;
+            GC.Collect();
+            GC.Collect();
+            Assert.True(actionEventReference.IsAlive);
+
+            pubSubEvent.Publish();
+
+            Assert.True(((ExternalAction)actionEventReference.Target).Executed);
+        }
+
+        [Test]
+        public void RegisterReturnsTokenThatCanBeUsedToUnsubscribe()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+            var emptyAction = new ActionHelper();
+
+            var token = PubSubEvent.Subscribe(emptyAction.Action);
+            PubSubEvent.Unsubscribe(token);
+
+            Assert.False(PubSubEvent.Contains(emptyAction.Action));
+        }
+
+        [Test]
+        public void RegisterReturnsTokenThatCanBeUsedToUnsubscribeNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+            var emptyAction = new ActionHelper();
+
+            var token = pubSubEvent.Subscribe(emptyAction.Action);
+            pubSubEvent.Unsubscribe(token);
+
+            Assert.False(pubSubEvent.Contains(emptyAction.Action));
+        }
+
+        [Test]
+        public void ContainsShouldSearchByToken()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+            var emptyAction = new ActionHelper();
+            var token = PubSubEvent.Subscribe(emptyAction.Action);
+
+            Assert.True(PubSubEvent.Contains(token));
+
+            PubSubEvent.Unsubscribe(emptyAction.Action);
+            Assert.False(PubSubEvent.Contains(token));
+        }
+
+        [Test]
+        public void ContainsShouldSearchByTokenNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+            var emptyAction = new ActionHelper();
+            var token = pubSubEvent.Subscribe(emptyAction.Action);
+
+            Assert.True(pubSubEvent.Contains(token));
+
+            pubSubEvent.Unsubscribe(emptyAction.Action);
+            Assert.False(pubSubEvent.Contains(token));
+        }
+
+        [Test]
+        public void SubscribeDefaultsToPublisherThread()
+        {
+            var PubSubEvent = new TestablePubSubEvent<string>();
+            Action<string> action = delegate { };
+            var token = PubSubEvent.Subscribe(action, true);
+
+            Assert.AreEqual(1, PubSubEvent.BaseSubscriptions.Count);
+            Assert.AreEqual(typeof(EventSubscription<string>), PubSubEvent.BaseSubscriptions.ElementAt(0).GetType());
+        }
+
+        [Test]
+        public void SubscribeDefaultsToPublisherThreadNonGeneric()
+        {
+            var pubSubEvent = new TestablePubSubEvent();
+            Action action = delegate { };
+            var token = pubSubEvent.Subscribe(action, true);
+
+            Assert.AreEqual(1, pubSubEvent.BaseSubscriptions.Count);
+            Assert.AreEqual(typeof(EventSubscription), pubSubEvent.BaseSubscriptions.ElementAt(0).GetType());
+        }
+
+        public class ExternalFilter
+        {
+            public bool AlwaysTrueFilter(string value)
+            {
+                return true;
+            }
+        }
+
+        public class ExternalAction
+        {
+            public string PassedValue;
+            public bool Executed = false;
+
+            public void ExecuteAction(string value)
+            {
+                PassedValue = value;
+                Executed = true;
+            }
+
+            public void ExecuteAction()
+            {
+                Executed = true;
+            }
+        }
+
+        class TestablePubSubEvent<TPayload> : PubSubEvent<TPayload>
+        {
+            public ICollection<IEventSubscription> BaseSubscriptions
+            {
+                get { return base.Subscriptions; }
+            }
+        }
+
+        class TestablePubSubEvent : PubSubEvent
+        {
+            public ICollection<IEventSubscription> BaseSubscriptions
+            {
+                get { return base.Subscriptions; }
+            }
+        }
+
+        public class Payload { }
+    }
+
+    public class ActionHelper
+    {
+        public bool ActionCalled;
+        public Action ActionToExecute = null;
+        private object actionArg;
+
+        public T ActionArg<T>()
+        {
+            return (T)actionArg;
+        }
+
+        public void Action(TestPubSubEvent.Payload arg)
+        {
+            Action((object)arg);
+        }
+
+        public void Action(string arg)
+        {
+            Action((object)arg);
+        }
+
+        public void Action(object arg)
+        {
+            actionArg = arg;
+            ActionCalled = true;
+            if (ActionToExecute != null)
+            {
+                ActionToExecute.Invoke();
+            }
+        }
+
+        public void Action()
+        {
+            ActionCalled = true;
+            if (ActionToExecute != null)
+            {
+                ActionToExecute.Invoke();
+            }
+        }
+    }
+
+    public class MockFilter
+    {
+        public bool FilterReturnValue;
+
+        public bool FilterString(string arg)
+        {
+            return FilterReturnValue;
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Index/IndexReader.cs b/src/Lucene.Net/Index/IndexReader.cs
index d1e355e0f..f8fa30527 100644
--- a/src/Lucene.Net/Index/IndexReader.cs
+++ b/src/Lucene.Net/Index/IndexReader.cs
@@ -4,7 +4,7 @@ using Lucene.Net.Support;
 using Lucene.Net.Support.Threading;
 using Lucene.Net.Util;
 #if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
 #endif
 using System;
 using System.Collections;
@@ -157,7 +157,7 @@ namespace Lucene.Net.Index
                 if (!parentReaders.TryGetValue(key: reader, out _))
                 {
                     parentReaders.Add(key: reader, value: null);
-                    reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent<Events.GetParentReadersEvent>());
+                    reader.SubscribeToGetParentReadersEvent(eventAggregator.GetEvent<WeakEvents.GetParentReadersEvent>());
                 }
 #endif
             }
@@ -211,8 +211,8 @@ namespace Lucene.Net.Index
                 {
                     IndexReader target = kvp.Key;
 #else
-                var e = new Events.GetParentReadersEventArgs();
-                eventAggregator.GetEvent<Events.GetParentReadersEvent>().Publish(e);
+                var e = new WeakEvents.GetParentReadersEventArgs();
+                eventAggregator.GetEvent<WeakEvents.GetParentReadersEvent>().Publish(e);
                 foreach (var target in e.ParentReaders)
                 {
 #endif
@@ -636,10 +636,10 @@ namespace Lucene.Net.Index
         // 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>();
+        private readonly ISet<WeakEvents.GetParentReadersEvent> getParentReadersEvents = new JCG.HashSet<WeakEvents.GetParentReadersEvent>();
         [ExcludeFromRamUsageEstimation]
-        private readonly ISet<Events.GetCacheKeysEvent> getCacheKeysEvents = new JCG.HashSet<Events.GetCacheKeysEvent>();
-        internal void SubscribeToGetParentReadersEvent(Events.GetParentReadersEvent getParentReadersEvent)
+        private readonly ISet<WeakEvents.GetCacheKeysEvent> getCacheKeysEvents = new JCG.HashSet<WeakEvents.GetCacheKeysEvent>();
+        internal void SubscribeToGetParentReadersEvent(WeakEvents.GetParentReadersEvent getParentReadersEvent)
         {
             if (getParentReadersEvent is null)
                 throw new ArgumentNullException(nameof(getParentReadersEvent));
@@ -647,7 +647,7 @@ namespace Lucene.Net.Index
                 getParentReadersEvent.Subscribe(OnGetParentReaders);
         }
 
-        internal void SubscribeToGetCacheKeysEvent(Events.GetCacheKeysEvent getCacheKeysEvent)
+        internal void SubscribeToGetCacheKeysEvent(WeakEvents.GetCacheKeysEvent getCacheKeysEvent)
         {
             if (getCacheKeysEvent is null)
                 throw new ArgumentNullException(nameof(getCacheKeysEvent));
@@ -662,12 +662,12 @@ namespace Lucene.Net.Index
         }
 
         // 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)
+        private void OnGetParentReaders(WeakEvents.GetParentReadersEventArgs e)
         {
             e.ParentReaders.Add(this);
         }
 
-        private void OnGetCacheKeys(Events.GetCacheKeysEventArgs e)
+        private void OnGetCacheKeys(WeakEvents.GetCacheKeysEventArgs e)
         {
             e.CacheKeys.Add(this.CoreCacheKey);
         }
diff --git a/src/Lucene.Net/Lucene.Net.csproj b/src/Lucene.Net/Lucene.Net.csproj
index 317590439..a1889b96b 100644
--- a/src/Lucene.Net/Lucene.Net.csproj
+++ b/src/Lucene.Net/Lucene.Net.csproj
@@ -59,12 +59,10 @@
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
-    <PackageReference Include="Prism.Core" Version="$(PrismCorePackageVersion)" />
     <PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
   </ItemGroup>
 
   <ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
-    <PackageReference Include="Prism.Core" Version="$(PrismCorePackageVersion)" />
     <PackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
   </ItemGroup>
 
diff --git a/src/Lucene.Net/Search/CachingWrapperFilter.cs b/src/Lucene.Net/Search/CachingWrapperFilter.cs
index b65a47e05..ece5aab60 100644
--- a/src/Lucene.Net/Search/CachingWrapperFilter.cs
+++ b/src/Lucene.Net/Search/CachingWrapperFilter.cs
@@ -3,7 +3,7 @@ using Lucene.Net.Runtime.CompilerServices;
 using Lucene.Net.Support.Threading;
 using Lucene.Net.Util;
 #if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
 #endif
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
@@ -136,7 +136,7 @@ namespace Lucene.Net.Search
                     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>());
+                    reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>());
                 }
                 finally
                 {
@@ -200,8 +200,8 @@ namespace Lucene.Net.Search
                 // 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);
+                var e = new WeakEvents.GetCacheKeysEventArgs();
+                eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>().Publish(e);
                 foreach (var key in e.CacheKeys)
                     if (cache.TryGetValue(key, out DocIdSet value))
                         docIdSets.Add(value);
diff --git a/src/Lucene.Net/Search/FieldCacheImpl.cs b/src/Lucene.Net/Search/FieldCacheImpl.cs
index 07e490a3d..5f40e4203 100644
--- a/src/Lucene.Net/Search/FieldCacheImpl.cs
+++ b/src/Lucene.Net/Search/FieldCacheImpl.cs
@@ -7,7 +7,7 @@ using Lucene.Net.Support.IO;
 using Lucene.Net.Support.Threading;
 using Lucene.Net.Util;
 #if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
-using Prism.Events;
+using Lucene.Net.Util.Events;
 #endif
 using System;
 using System.Collections.Generic;
@@ -188,8 +188,8 @@ namespace Lucene.Net.Search
 #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);
+                var e = new WeakEvents.GetCacheKeysEventArgs();
+                eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>().Publish(e);
                 foreach (object readerKey in e.CacheKeys)
                 {
                     if (cache.readerCache.TryGetValue(readerKey, out IDictionary<TKey, object> innerCache))
@@ -270,7 +270,7 @@ namespace Lucene.Net.Search
 #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>());
+            reader.SubscribeToGetCacheKeysEvent(eventAggregator.GetEvent<WeakEvents.GetCacheKeysEvent>());
 #endif
         }
 
diff --git a/src/Lucene.Net/Support/Util/Events/BackgroundEventSubscription.cs b/src/Lucene.Net/Support/Util/Events/BackgroundEventSubscription.cs
new file mode 100644
index 000000000..ced968e08
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/BackgroundEventSubscription.cs
@@ -0,0 +1,84 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Threading.Tasks;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Extends <see cref="EventSubscription"/> to invoke the <see cref="EventSubscription.Action"/> delegate in a background thread.
+    /// </summary>
+    internal class BackgroundEventSubscription : EventSubscription
+    {
+        /// <summary>
+        /// Creates a new instance of <see cref="BackgroundEventSubscription"/>.
+        /// </summary>
+        /// <param name="actionReference">A reference to a delegate of type <see cref="System.Action"/>.</param>
+        /// <exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+        /// <exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action"/>.</exception>
+        public BackgroundEventSubscription(IDelegateReference actionReference)
+            : base(actionReference)
+        {
+        }
+
+        /// <summary>
+        /// Invokes the specified <see cref="System.Action"/> in an asynchronous thread by using a <see cref="Task"/>.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
+        public override void InvokeAction(Action action)
+        {
+            Task.Run(action);
+        }
+    }
+    /// <summary>
+    /// Extends <see cref="EventSubscription{TPayload}"/> to invoke the <see cref="EventSubscription{TPayload}.Action"/> delegate in a background thread.
+    /// </summary>
+    /// <typeparam name="TPayload">The type to use for the generic <see cref="System.Action{TPayload}"/> and <see cref="Predicate{TPayload}"/> types.</typeparam>
+    internal class BackgroundEventSubscription<TPayload> : EventSubscription<TPayload>
+    {
+        /// <summary>
+        /// Creates a new instance of <see cref="BackgroundEventSubscription{TPayload}"/>.
+        /// </summary>
+        /// <param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+        /// <param name="filterReference">A reference to a delegate of type <see cref="Predicate{TPayload}"/>.</param>
+        /// <exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+        /// <exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>,
+        /// or the target of <paramref name="filterReference"/> is not of type <see cref="Predicate{TPayload}"/>.</exception>
+        public BackgroundEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference)
+            : base(actionReference, filterReference)
+        {
+        }
+
+        /// <summary>
+        /// Invokes the specified <see cref="System.Action{TPayload}"/> in an asynchronous thread by using a <see cref="System.Threading.ThreadPool"/>.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
+        /// <param name="argument">The payload to pass <paramref name="action"/> while invoking it.</param>
+        public override void InvokeAction(Action<TPayload> action, TPayload argument)
+        {
+            //ThreadPool.QueueUserWorkItem( (o) => action(argument) );
+            Task.Run(() => action(argument));
+        }
+    }
+}
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/DelegateReference.cs b/src/Lucene.Net/Support/Util/Events/DelegateReference.cs
new file mode 100644
index 000000000..5ab8bfd41
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/DelegateReference.cs
@@ -0,0 +1,117 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Reflection;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Represents a reference to a <see cref="Delegate"/> that may contain a
+    /// <see cref="WeakReference"/> to the target. This class is used
+    /// internally.
+    /// </summary>
+    internal class DelegateReference : IDelegateReference
+    {
+        private readonly Delegate _delegate;
+        private readonly WeakReference _weakReference;
+        private readonly MethodInfo _method;
+        private readonly Type _delegateType;
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="DelegateReference"/>.
+        /// </summary>
+        /// <param name="delegate">The original <see cref="Delegate"/> to create a reference for.</param>
+        /// <param name="keepReferenceAlive">If <see langword="false" /> the class will create a weak reference to the delegate, allowing it to be garbage collected. Otherwise it will keep a strong reference to the target.</param>
+        /// <exception cref="ArgumentNullException">If the passed <paramref name="delegate"/> is not assignable to <see cref="Delegate"/>.</exception>
+        public DelegateReference(Delegate @delegate, bool keepReferenceAlive)
+        {
+            if (@delegate == null)
+                throw new ArgumentNullException("delegate");
+
+            if (keepReferenceAlive)
+            {
+                this._delegate = @delegate;
+            }
+            else
+            {
+                _weakReference = new WeakReference(@delegate.Target);
+                _method = @delegate.GetMethodInfo();
+                _delegateType = @delegate.GetType();
+            }
+        }
+
+        /// <summary>
+        /// Gets the <see cref="Delegate" /> (the target) referenced by the current <see cref="DelegateReference"/> object.
+        /// </summary>
+        /// <value><see langword="null"/> if the object referenced by the current <see cref="DelegateReference"/> object has been garbage collected; otherwise, a reference to the <see cref="Delegate"/> referenced by the current <see cref="DelegateReference"/> object.</value>
+        public Delegate Target
+        {
+            get
+            {
+                if (_delegate != null)
+                {
+                    return _delegate;
+                }
+                else
+                {
+                    return TryGetDelegate();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Checks if the <see cref="Delegate" /> (the target) referenced by the current <see cref="DelegateReference"/> object are equal to another <see cref="Delegate" />.
+        /// This is equivalent with comparing <see cref="Target"/> with <paramref name="delegate"/>, only more efficient.
+        /// </summary>
+        /// <param name="delegate">The other delegate to compare with.</param>
+        /// <returns>True if the target referenced by the current object are equal to <paramref name="delegate"/>.</returns>
+        public bool TargetEquals(Delegate @delegate)
+        {
+            if (_delegate != null)
+            {
+                return _delegate == @delegate;
+            }
+            if (@delegate == null)
+            {
+                return !_method.IsStatic && !_weakReference.IsAlive;
+            }
+            return _weakReference.Target == @delegate.Target && Equals(_method, @delegate.GetMethodInfo());
+        }
+
+        private Delegate TryGetDelegate()
+        {
+            if (_method.IsStatic)
+            {
+                return _method.CreateDelegate(_delegateType, null);
+            }
+            object target = _weakReference.Target;
+            if (target != null)
+            {
+                return _method.CreateDelegate(_delegateType, target);
+            }
+            return null;
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/DispatcherEventSubscription.cs b/src/Lucene.Net/Support/Util/Events/DispatcherEventSubscription.cs
new file mode 100644
index 000000000..b56ee8847
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/DispatcherEventSubscription.cs
@@ -0,0 +1,95 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+    ///<summary>
+    /// Extends <see cref="EventSubscription"/> to invoke the <see cref="EventSubscription.Action"/> delegate
+    /// in a specific <see cref="SynchronizationContext"/>.
+    ///</summary>
+    internal class DispatcherEventSubscription : EventSubscription
+    {
+        private readonly SynchronizationContext syncContext;
+
+        ///<summary>
+        /// Creates a new instance of <see cref="BackgroundEventSubscription"/>.
+        ///</summary>
+        ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+        ///<param name="context">The synchronization context to use for UI thread dispatching.</param>
+        ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+        ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>.</exception>
+        public DispatcherEventSubscription(IDelegateReference actionReference, SynchronizationContext context)
+            : base(actionReference)
+        {
+            syncContext = context;
+        }
+
+        /// <summary>
+        /// Invokes the specified <see cref="System.Action{TPayload}"/> asynchronously in the specified <see cref="SynchronizationContext"/>.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
+        public override void InvokeAction(Action action)
+        {
+            syncContext.Post((o) => action(), null);
+        }
+    }
+
+    ///<summary>
+    /// Extends <see cref="EventSubscription{TPayload}"/> to invoke the <see cref="EventSubscription{TPayload}.Action"/> delegate
+    /// in a specific <see cref="SynchronizationContext"/>.
+    ///</summary>
+    /// <typeparam name="TPayload">The type to use for the generic <see cref="System.Action{TPayload}"/> and <see cref="Predicate{TPayload}"/> types.</typeparam>
+    internal class DispatcherEventSubscription<TPayload> : EventSubscription<TPayload>
+    {
+        private readonly SynchronizationContext syncContext;
+
+        ///<summary>
+        /// Creates a new instance of <see cref="BackgroundEventSubscription{TPayload}"/>.
+        ///</summary>
+        ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+        ///<param name="filterReference">A reference to a delegate of type <see cref="Predicate{TPayload}"/>.</param>
+        ///<param name="context">The synchronization context to use for UI thread dispatching.</param>
+        ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+        ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>,
+        ///or the target of <paramref name="filterReference"/> is not of type <see cref="Predicate{TPayload}"/>.</exception>
+        public DispatcherEventSubscription(IDelegateReference actionReference, IDelegateReference filterReference, SynchronizationContext context)
+            : base(actionReference, filterReference)
+        {
+            syncContext = context;
+        }
+
+        /// <summary>
+        /// Invokes the specified <see cref="System.Action{TPayload}"/> asynchronously in the specified <see cref="SynchronizationContext"/>.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
+        /// <param name="argument">The payload to pass <paramref name="action"/> while invoking it.</param>
+        public override void InvokeAction(Action<TPayload> action, TPayload argument)
+        {
+            syncContext.Post((o) => action((TPayload)o), argument);
+        }
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/EventAggregator.cs b/src/Lucene.Net/Support/Util/Events/EventAggregator.cs
new file mode 100644
index 000000000..0c753b16c
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/EventAggregator.cs
@@ -0,0 +1,68 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Implements <see cref="IEventAggregator"/>.
+    /// </summary>
+    internal class EventAggregator : IEventAggregator
+    {
+        private readonly Dictionary<Type, EventBase> events = new Dictionary<Type, EventBase>();
+        // Captures the sync context for the UI thread when constructed on the UI thread 
+        // in a platform agnostic way so it can be used for UI thread dispatching
+        private readonly SynchronizationContext syncContext = SynchronizationContext.Current;
+
+        /// <summary>
+        /// Gets the single instance of the event managed by this EventAggregator. Multiple calls to this method with the same <typeparamref name="TEventType"/> returns the same event instance.
+        /// </summary>
+        /// <typeparam name="TEventType">The type of event to get. This must inherit from <see cref="EventBase"/>.</typeparam>
+        /// <returns>A singleton instance of an event object of type <typeparamref name="TEventType"/>.</returns>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
+        public TEventType GetEvent<TEventType>() where TEventType : EventBase, new()
+        {
+            lock (events)
+            {
+                EventBase existingEvent = null;
+
+                if (!events.TryGetValue(typeof(TEventType), out existingEvent))
+                {
+                    TEventType newEvent = new TEventType();
+                    newEvent.SynchronizationContext = syncContext;
+                    events[typeof(TEventType)] = newEvent;
+
+                    return newEvent;
+                }
+                else
+                {
+                    return (TEventType)existingEvent;
+                }
+            }
+        }
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/EventBase.cs b/src/Lucene.Net/Support/Util/Events/EventBase.cs
new file mode 100644
index 000000000..7d087d505
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/EventBase.cs
@@ -0,0 +1,163 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+    ///<summary>
+    /// Defines a base class to publish and subscribe to events.
+    ///</summary>
+    internal abstract class EventBase
+    {
+        private readonly List<IEventSubscription> _subscriptions = new List<IEventSubscription>();
+
+        /// <summary>
+        /// Allows the SynchronizationContext to be set by the EventAggregator for UI Thread Dispatching
+        /// </summary>
+        public SynchronizationContext SynchronizationContext { get; set; }
+
+        /// <summary>
+        /// Gets the list of current subscriptions.
+        /// </summary>
+        /// <value>The current subscribers.</value>
+        protected ICollection<IEventSubscription> Subscriptions
+        {
+            get { return _subscriptions; }
+        }
+
+        /// <summary>
+        /// Adds the specified <see cref="IEventSubscription"/> to the subscribers' collection.
+        /// </summary>
+        /// <param name="eventSubscription">The subscriber.</param>
+        /// <returns>The <see cref="SubscriptionToken"/> that uniquely identifies every subscriber.</returns>
+        /// <remarks>
+        /// Adds the subscription to the internal list and assigns it a new <see cref="SubscriptionToken"/>.
+        /// </remarks>
+        protected virtual SubscriptionToken InternalSubscribe(IEventSubscription eventSubscription)
+        {
+            if (eventSubscription == null) throw new ArgumentNullException(nameof(eventSubscription));
+
+            eventSubscription.SubscriptionToken = new SubscriptionToken(Unsubscribe);
+
+            lock (Subscriptions)
+            {
+                Subscriptions.Add(eventSubscription);
+            }
+            return eventSubscription.SubscriptionToken;
+        }
+
+        /// <summary>
+        /// Calls all the execution strategies exposed by the list of <see cref="IEventSubscription"/>.
+        /// </summary>
+        /// <param name="arguments">The arguments that will be passed to the listeners.</param>
+        /// <remarks>Before executing the strategies, this class will prune all the subscribers from the
+        /// list that return a <see langword="null" /> <see cref="Action{T}"/> when calling the
+        /// <see cref="IEventSubscription.GetExecutionStrategy"/> method.</remarks>
+        protected virtual void InternalPublish(params object[] arguments)
+        {
+            List<Action<object[]>> executionStrategies = PruneAndReturnStrategies();
+            foreach (var executionStrategy in executionStrategies)
+            {
+                executionStrategy(arguments);
+            }
+        }
+
+        /// <summary>
+        /// Removes the subscriber matching the <see cref="SubscriptionToken"/>.
+        /// </summary>
+        /// <param name="token">The <see cref="SubscriptionToken"/> returned by <see cref="EventBase"/> while subscribing to the event.</param>
+        public virtual void Unsubscribe(SubscriptionToken token)
+        {
+            lock (Subscriptions)
+            {
+                IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
+                if (subscription != null)
+                {
+                    Subscriptions.Remove(subscription);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns <see langword="true"/> if there is a subscriber matching <see cref="SubscriptionToken"/>.
+        /// </summary>
+        /// <param name="token">The <see cref="SubscriptionToken"/> returned by <see cref="EventBase"/> while subscribing to the event.</param>
+        /// <returns><see langword="true"/> if there is a <see cref="SubscriptionToken"/> that matches; otherwise <see langword="false"/>.</returns>
+        public virtual bool Contains(SubscriptionToken token)
+        {
+            lock (Subscriptions)
+            {
+                IEventSubscription subscription = Subscriptions.FirstOrDefault(evt => evt.SubscriptionToken == token);
+                return subscription != null;
+            }
+        }
+
+        private List<Action<object[]>> PruneAndReturnStrategies()
+        {
+            List<Action<object[]>> returnList = new List<Action<object[]>>();
+
+            lock (Subscriptions)
+            {
+                for (var i = Subscriptions.Count - 1; i >= 0; i--)
+                {
+                    Action<object[]> listItem =
+                        _subscriptions[i].GetExecutionStrategy();
+
+                    if (listItem == null)
+                    {
+                        // Prune from main list. Log?
+                        _subscriptions.RemoveAt(i);
+                    }
+                    else
+                    {
+                        returnList.Add(listItem);
+                    }
+                }
+            }
+
+            return returnList;
+        }
+
+        /// <summary>
+        /// Forces the PubSubEvent to remove any subscriptions that no longer have an execution strategy.
+        /// </summary>
+        public void Prune()
+        {
+            lock (Subscriptions)
+            {
+                for (var i = Subscriptions.Count - 1; i >= 0; i--)
+                {
+                    if (_subscriptions[i].GetExecutionStrategy() == null)
+                    {
+                        _subscriptions.RemoveAt(i);
+                    }
+                }
+            }
+        }
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/EventSubscription.cs b/src/Lucene.Net/Support/Util/Events/EventSubscription.cs
new file mode 100644
index 000000000..d5ee36290
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/EventSubscription.cs
@@ -0,0 +1,214 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Globalization;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Provides a way to retrieve a <see cref="Delegate"/> to execute an action depending
+    /// on the value of a second filter predicate that returns true if the action should execute.
+    /// </summary>
+    internal class EventSubscription : IEventSubscription
+    {
+        private readonly IDelegateReference _actionReference;
+
+        ///<summary>
+        /// Creates a new instance of <see cref="EventSubscription"/>.
+        ///</summary>
+        ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action"/>.</param>
+        ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+        ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action"/>.</exception>
+        public EventSubscription(IDelegateReference actionReference)
+        {
+            if (actionReference == null)
+                throw new ArgumentNullException(nameof(actionReference));
+            if (!(actionReference.Target is Action))
+                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Action).FullName), nameof(actionReference));
+
+            _actionReference = actionReference;
+        }
+
+        /// <summary>
+        /// Gets the target <see cref="System.Action"/> that is referenced by the <see cref="IDelegateReference"/>.
+        /// </summary>
+        /// <value>An <see cref="System.Action"/> or <see langword="null" /> if the referenced target is not alive.</value>
+        public Action Action
+        {
+            get { return (Action)_actionReference.Target; }
+        }
+
+        /// <summary>
+        /// Gets or sets a <see cref="SubscriptionToken"/> that identifies this <see cref="IEventSubscription"/>.
+        /// </summary>
+        /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
+        public SubscriptionToken SubscriptionToken { get; set; }
+
+        /// <summary>
+        /// Gets the execution strategy to publish this event.
+        /// </summary>
+        /// <returns>An <see cref="System.Action"/> with the execution strategy, or <see langword="null" /> if the <see cref="IEventSubscription"/> is no longer valid.</returns>
+        /// <remarks>
+        /// If <see cref="Action"/>is no longer valid because it was
+        /// garbage collected, this method will return <see langword="null" />.
+        /// Otherwise it will return a delegate that evaluates the <see cref="EventSubscription{TPayload}.Filter"/> and if it
+        /// returns <see langword="true" /> will then call <see cref="InvokeAction"/>. The returned
+        /// delegate holds a hard reference to the <see cref="Action"/> target
+        /// <see cref="Delegate">delegates</see>. As long as the returned delegate is not garbage collected,
+        /// the <see cref="Action"/> references delegates won't get collected either.
+        /// </remarks>
+        public virtual Action<object[]> GetExecutionStrategy()
+        {
+            Action action = this.Action;
+            if (action != null)
+            {
+                return arguments =>
+                {
+                    InvokeAction(action);
+                };
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Invokes the specified <see cref="System.Action{TPayload}"/> synchronously when not overridden.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
+        /// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException"/> is thrown if <paramref name="action"/> is null.</exception>
+        public virtual void InvokeAction(Action action)
+        {
+            if (action == null) throw new ArgumentNullException(nameof(action));
+
+            action();
+        }
+    }
+
+    /// <summary>
+    /// Provides a way to retrieve a <see cref="Delegate"/> to execute an action depending
+    /// on the value of a second filter predicate that returns true if the action should execute.
+    /// </summary>
+    /// <typeparam name="TPayload">The type to use for the generic <see cref="System.Action{TPayload}"/> and <see cref="Predicate{TPayload}"/> types.</typeparam>
+    internal class EventSubscription<TPayload> : IEventSubscription
+    {
+        private readonly IDelegateReference _actionReference;
+        private readonly IDelegateReference _filterReference;
+
+        ///<summary>
+        /// Creates a new instance of <see cref="EventSubscription{TPayload}"/>.
+        ///</summary>
+        ///<param name="actionReference">A reference to a delegate of type <see cref="System.Action{TPayload}"/>.</param>
+        ///<param name="filterReference">A reference to a delegate of type <see cref="Predicate{TPayload}"/>.</param>
+        ///<exception cref="ArgumentNullException">When <paramref name="actionReference"/> or <see paramref="filterReference"/> are <see langword="null" />.</exception>
+        ///<exception cref="ArgumentException">When the target of <paramref name="actionReference"/> is not of type <see cref="System.Action{TPayload}"/>,
+        ///or the target of <paramref name="filterReference"/> is not of type <see cref="Predicate{TPayload}"/>.</exception>
+        public EventSubscription(IDelegateReference actionReference, IDelegateReference filterReference)
+        {
+            if (actionReference == null)
+                throw new ArgumentNullException(nameof(actionReference));
+            if (!(actionReference.Target is Action<TPayload>))
+                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Action<TPayload>).FullName), nameof(actionReference));
+
+            if (filterReference == null)
+                throw new ArgumentNullException(nameof(filterReference));
+            if (!(filterReference.Target is Predicate<TPayload>))
+                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidDelegateRerefenceTypeException, typeof(Predicate<TPayload>).FullName), nameof(filterReference));
+
+            _actionReference = actionReference;
+            _filterReference = filterReference;
+        }
+
+        /// <summary>
+        /// Gets the target <see cref="System.Action{T}"/> that is referenced by the <see cref="IDelegateReference"/>.
+        /// </summary>
+        /// <value>An <see cref="System.Action{T}"/> or <see langword="null" /> if the referenced target is not alive.</value>
+        public Action<TPayload> Action
+        {
+            get { return (Action<TPayload>)_actionReference.Target; }
+        }
+
+        /// <summary>
+        /// Gets the target <see cref="Predicate{T}"/> that is referenced by the <see cref="IDelegateReference"/>.
+        /// </summary>
+        /// <value>An <see cref="Predicate{T}"/> or <see langword="null" /> if the referenced target is not alive.</value>
+        public Predicate<TPayload> Filter
+        {
+            get { return (Predicate<TPayload>)_filterReference.Target; }
+        }
+
+        /// <summary>
+        /// Gets or sets a <see cref="SubscriptionToken"/> that identifies this <see cref="IEventSubscription"/>.
+        /// </summary>
+        /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
+        public SubscriptionToken SubscriptionToken { get; set; }
+
+        /// <summary>
+        /// Gets the execution strategy to publish this event.
+        /// </summary>
+        /// <returns>An <see cref="System.Action{T}"/> with the execution strategy, or <see langword="null" /> if the <see cref="IEventSubscription"/> is no longer valid.</returns>
+        /// <remarks>
+        /// If <see cref="Action"/> or <see cref="Filter"/> are no longer valid because they were
+        /// garbage collected, this method will return <see langword="null" />.
+        /// Otherwise it will return a delegate that evaluates the <see cref="Filter"/> and if it
+        /// returns <see langword="true" /> will then call <see cref="InvokeAction"/>. The returned
+        /// delegate holds hard references to the <see cref="Action"/> and <see cref="Filter"/> target
+        /// <see cref="Delegate">delegates</see>. As long as the returned delegate is not garbage collected,
+        /// the <see cref="Action"/> and <see cref="Filter"/> references delegates won't get collected either.
+        /// </remarks>
+        public virtual Action<object[]> GetExecutionStrategy()
+        {
+            Action<TPayload> action = this.Action;
+            Predicate<TPayload> filter = this.Filter;
+            if (action != null && filter != null)
+            {
+                return arguments =>
+                {
+                    TPayload argument = default(TPayload);
+                    if (arguments != null && arguments.Length > 0 && arguments[0] != null)
+                    {
+                        argument = (TPayload)arguments[0];
+                    }
+                    if (filter(argument))
+                    {
+                        InvokeAction(action, argument);
+                    }
+                };
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Invokes the specified <see cref="System.Action{TPayload}"/> synchronously when not overridden.
+        /// </summary>
+        /// <param name="action">The action to execute.</param>
+        /// <param name="argument">The payload to pass <paramref name="action"/> while invoking it.</param>
+        /// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException"/> is thrown if <paramref name="action"/> is null.</exception>
+        public virtual void InvokeAction(Action<TPayload> action, TPayload argument)
+        {
+            if (action == null) throw new ArgumentNullException(nameof(action));
+
+            action(argument);
+        }
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/IDelegateReference.cs b/src/Lucene.Net/Support/Util/Events/IDelegateReference.cs
new file mode 100644
index 000000000..003cf52d3
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/IDelegateReference.cs
@@ -0,0 +1,40 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Represents a reference to a <see cref="Delegate"/>.
+    /// </summary>
+    internal interface IDelegateReference
+    {
+        /// <summary>
+        /// Gets the referenced <see cref="Delegate" /> object.
+        /// </summary>
+        /// <value>A <see cref="Delegate"/> instance if the target is valid; otherwise <see langword="null"/>.</value>
+        Delegate Target { get; }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/IEventAggregator.cs b/src/Lucene.Net/Support/Util/Events/IEventAggregator.cs
new file mode 100644
index 000000000..585e69b15
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/IEventAggregator.cs
@@ -0,0 +1,40 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Defines an interface to get instances of an event type.
+    /// </summary>
+    internal interface IEventAggregator
+    {
+        /// <summary>
+        /// Gets an instance of an event type.
+        /// </summary>
+        /// <typeparam name="TEventType">The type of event to get.</typeparam>
+        /// <returns>An instance of an event object of type <typeparamref name="TEventType"/>.</returns>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
+        TEventType GetEvent<TEventType>() where TEventType : EventBase, new();
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/IEventSubscription.cs b/src/Lucene.Net/Support/Util/Events/IEventSubscription.cs
new file mode 100644
index 000000000..c34fed138
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/IEventSubscription.cs
@@ -0,0 +1,47 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+    ///<summary>
+    /// Defines a contract for an event subscription to be used by <see cref="EventBase"/>.
+    ///</summary>
+    internal interface IEventSubscription
+    {
+        /// <summary>
+        /// Gets or sets a <see cref="SubscriptionToken"/> that identifies this <see cref="IEventSubscription"/>.
+        /// </summary>
+        /// <value>A token that identifies this <see cref="IEventSubscription"/>.</value>
+        SubscriptionToken SubscriptionToken { get; set; }
+
+        /// <summary>
+        /// Gets the execution strategy to publish this event.
+        /// </summary>
+        /// <returns>An <see cref="Action{T}"/> with the execution strategy, or <see langword="null" /> if the <see cref="IEventSubscription"/> is no longer valid.</returns>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
+        Action<object[]> GetExecutionStrategy();
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/PubSubEvent.cs b/src/Lucene.Net/Support/Util/Events/PubSubEvent.cs
new file mode 100644
index 000000000..d90f41234
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/PubSubEvent.cs
@@ -0,0 +1,329 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Defines a class that manages publication and subscription to events.
+    /// </summary>
+    internal class PubSubEvent : EventBase
+    {
+        /// <summary>
+        /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+        /// <see cref="PubSubEvent"/> will maintain a <see cref="WeakReference"/> to the target of the supplied <paramref name="action"/> delegate.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is published.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public SubscriptionToken Subscribe(Action action)
+        {
+            return Subscribe(action, ThreadOption.PublisherThread);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event.
+        /// PubSubEvent will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is raised.</param>
+        /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public SubscriptionToken Subscribe(Action action, ThreadOption threadOption)
+        {
+            return Subscribe(action, threadOption, false);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is published.</param>
+        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+        /// <para/>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public SubscriptionToken Subscribe(Action action, bool keepSubscriberReferenceAlive)
+        {
+            return Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is published.</param>
+        /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+        /// <para/>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public virtual SubscriptionToken Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
+        {
+            IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);
+
+            EventSubscription subscription;
+            switch (threadOption)
+            {
+                case ThreadOption.PublisherThread:
+                    subscription = new EventSubscription(actionReference);
+                    break;
+                case ThreadOption.BackgroundThread:
+                    subscription = new BackgroundEventSubscription(actionReference);
+                    break;
+                case ThreadOption.UIThread:
+                    if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
+                    subscription = new DispatcherEventSubscription(actionReference, SynchronizationContext);
+                    break;
+                default:
+                    subscription = new EventSubscription(actionReference);
+                    break;
+            }
+
+            return InternalSubscribe(subscription);
+        }
+
+        /// <summary>
+        /// Publishes the <see cref="PubSubEvent"/>.
+        /// </summary>
+        public virtual void Publish()
+        {
+            InternalPublish();
+        }
+
+        /// <summary>
+        /// Removes the first subscriber matching <see cref="Action"/> from the subscribers' list.
+        /// </summary>
+        /// <param name="subscriber">The <see cref="Action"/> used when subscribing to the event.</param>
+        public virtual void Unsubscribe(Action subscriber)
+        {
+            lock (Subscriptions)
+            {
+                IEventSubscription eventSubscription = Subscriptions.Cast<EventSubscription>().FirstOrDefault(evt => evt.Action == subscriber);
+                if (eventSubscription != null)
+                {
+                    Subscriptions.Remove(eventSubscription);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns <see langword="true"/> if there is a subscriber matching <see cref="Action"/>.
+        /// </summary>
+        /// <param name="subscriber">The <see cref="Action"/> used when subscribing to the event.</param>
+        /// <returns><see langword="true"/> if there is an <see cref="Action"/> that matches; otherwise <see langword="false"/>.</returns>
+        public virtual bool Contains(Action subscriber)
+        {
+            IEventSubscription eventSubscription;
+            lock (Subscriptions)
+            {
+                eventSubscription = Subscriptions.Cast<EventSubscription>().FirstOrDefault(evt => evt.Action == subscriber);
+            }
+            return eventSubscription != null;
+        }
+    }
+
+    /// <summary>
+    /// Defines a class that manages publication and subscription to events.
+    /// </summary>
+    /// <typeparam name="TPayload">The type of message that will be passed to the subscribers.</typeparam>
+    internal class PubSubEvent<TPayload> : EventBase
+    {
+        /// <summary>
+        /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+        /// <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the target of the supplied <paramref name="action"/> delegate.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is published.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public SubscriptionToken Subscribe(Action<TPayload> action)
+        {
+            return Subscribe(action, ThreadOption.PublisherThread);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is raised.</param>
+        /// <param name="filter">Filter to evaluate if the subscriber should receive the event.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        public virtual SubscriptionToken Subscribe(Action<TPayload> action, Predicate<TPayload> filter)
+        {
+            return Subscribe(action, ThreadOption.PublisherThread, false, filter);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event.
+        /// PubSubEvent will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is raised.</param>
+        /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption)
+        {
+            return Subscribe(action, threadOption, false);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event that will be published on the <see cref="ThreadOption.PublisherThread"/>.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is published.</param>
+        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent{TPayload}"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+        /// <para/>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public SubscriptionToken Subscribe(Action<TPayload> action, bool keepSubscriberReferenceAlive)
+        {
+            return Subscribe(action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is published.</param>
+        /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent{TPayload}"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+        /// <para/>
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
+        {
+            return Subscribe(action, threadOption, keepSubscriberReferenceAlive, null);
+        }
+
+        /// <summary>
+        /// Subscribes a delegate to an event.
+        /// </summary>
+        /// <param name="action">The delegate that gets executed when the event is published.</param>
+        /// <param name="threadOption">Specifies on which thread to receive the delegate callback.</param>
+        /// <param name="keepSubscriberReferenceAlive">When <see langword="true"/>, the <see cref="PubSubEvent{TPayload}"/> keeps a reference to the subscriber so it does not get garbage collected.</param>
+        /// <param name="filter">Filter to evaluate if the subscriber should receive the event.</param>
+        /// <returns>A <see cref="SubscriptionToken"/> that uniquely identifies the added subscription.</returns>
+        /// <remarks>
+        /// If <paramref name="keepSubscriberReferenceAlive"/> is set to <see langword="false" />, <see cref="PubSubEvent{TPayload}"/> will maintain a <see cref="WeakReference"/> to the Target of the supplied <paramref name="action"/> delegate.
+        /// If not using a WeakReference (<paramref name="keepSubscriberReferenceAlive"/> is <see langword="true" />), the user must explicitly call Unsubscribe for the event when disposing the subscriber in order to avoid memory leaks or unexpected behavior.
+        ///
+        /// The PubSubEvent collection is thread-safe.
+        /// </remarks>
+        public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter)
+        {
+            IDelegateReference actionReference = new DelegateReference(action, keepSubscriberReferenceAlive);
+            IDelegateReference filterReference;
+            if (filter != null)
+            {
+                filterReference = new DelegateReference(filter, keepSubscriberReferenceAlive);
+            }
+            else
+            {
+                filterReference = new DelegateReference(new Predicate<TPayload>(delegate { return true; }), true);
+            }
+            EventSubscription<TPayload> subscription;
+            switch (threadOption)
+            {
+                case ThreadOption.PublisherThread:
+                    subscription = new EventSubscription<TPayload>(actionReference, filterReference);
+                    break;
+                case ThreadOption.BackgroundThread:
+                    subscription = new BackgroundEventSubscription<TPayload>(actionReference, filterReference);
+                    break;
+                case ThreadOption.UIThread:
+                    if (SynchronizationContext == null) throw new InvalidOperationException(Resources.EventAggregatorNotConstructedOnUIThread);
+                    subscription = new DispatcherEventSubscription<TPayload>(actionReference, filterReference, SynchronizationContext);
+                    break;
+                default:
+                    subscription = new EventSubscription<TPayload>(actionReference, filterReference);
+                    break;
+            }
+
+            return InternalSubscribe(subscription);
+        }
+
+        /// <summary>
+        /// Publishes the <see cref="PubSubEvent{TPayload}"/>.
+        /// </summary>
+        /// <param name="payload">Message to pass to the subscribers.</param>
+        public virtual void Publish(TPayload payload)
+        {
+            InternalPublish(payload);
+        }
+
+        /// <summary>
+        /// Removes the first subscriber matching <see cref="Action{TPayload}"/> from the subscribers' list.
+        /// </summary>
+        /// <param name="subscriber">The <see cref="Action{TPayload}"/> used when subscribing to the event.</param>
+        public virtual void Unsubscribe(Action<TPayload> subscriber)
+        {
+            lock (Subscriptions)
+            {
+                IEventSubscription eventSubscription = Subscriptions.Cast<EventSubscription<TPayload>>().FirstOrDefault(evt => evt.Action == subscriber);
+                if (eventSubscription != null)
+                {
+                    Subscriptions.Remove(eventSubscription);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns <see langword="true"/> if there is a subscriber matching <see cref="Action{TPayload}"/>.
+        /// </summary>
+        /// <param name="subscriber">The <see cref="Action{TPayload}"/> used when subscribing to the event.</param>
+        /// <returns><see langword="true"/> if there is an <see cref="Action{TPayload}"/> that matches; otherwise <see langword="false"/>.</returns>
+        public virtual bool Contains(Action<TPayload> subscriber)
+        {
+            IEventSubscription eventSubscription;
+            lock (Subscriptions)
+            {
+                eventSubscription = Subscriptions.Cast<EventSubscription<TPayload>>().FirstOrDefault(evt => evt.Action == subscriber);
+            }
+            return eventSubscription != null;
+        }
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/Resources.cs b/src/Lucene.Net/Support/Util/Events/Resources.cs
new file mode 100644
index 000000000..04e9d28ec
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/Resources.cs
@@ -0,0 +1,32 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+namespace Lucene.Net.Util.Events
+{
+    internal static class Resources
+    {
+        public static string EventAggregatorNotConstructedOnUIThread = "To use the UIThread option for subscribing, the EventAggregator must be constructed on the UI thread.";
+        public static string InvalidDelegateRerefenceTypeException = "Invalid Delegate Reference Type Exception";
+    }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Lucene.Net/Support/Util/Events/SubscriptionToken.cs b/src/Lucene.Net/Support/Util/Events/SubscriptionToken.cs
new file mode 100644
index 000000000..e422eb3bb
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/SubscriptionToken.cs
@@ -0,0 +1,106 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Subscription token returned from <see cref="EventBase"/> on subscribe.
+    /// </summary>
+    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Should never have a need for a finalizer, hence no need for Dispose(bool)")]
+    internal class SubscriptionToken
+    {
+        private readonly Guid _token;
+        private Action<SubscriptionToken> _unsubscribeAction;
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="SubscriptionToken"/>.
+        /// </summary>
+        public SubscriptionToken(Action<SubscriptionToken> unsubscribeAction)
+        {
+            _unsubscribeAction = unsubscribeAction;
+            _token = Guid.NewGuid();
+        }
+
+        ///<summary>
+        ///Indicates whether the current object is equal to another object of the same type.
+        ///</summary>
+        ///<returns>
+        ///<see langword="true"/> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <see langword="false"/>.
+        ///</returns>
+        ///<param name="other">An object to compare with this object.</param>
+        public bool Equals(SubscriptionToken other)
+        {
+            if (other == null) return false;
+            return Equals(_token, other._token);
+        }
+
+        ///<summary>
+        ///Determines whether the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />.
+        ///</summary>
+        ///<returns>
+        ///true if the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />; otherwise, false.
+        ///</returns>
+        ///<param name="obj">The <see cref="T:System.Object" /> to compare with the current <see cref="T:System.Object" />. </param>
+        ///<exception cref="T:System.NullReferenceException">The <paramref name="obj" /> parameter is null.</exception><filterpriority>2</filterpriority>
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(this, obj)) return true;
+            return Equals(obj as SubscriptionToken);
+        }
+
+        /// <summary>
+        /// Serves as a hash function for a particular type. 
+        /// </summary>
+        /// <returns>
+        /// A hash code for the current <see cref="T:System.Object" />.
+        /// </returns>
+        /// <filterpriority>2</filterpriority>
+        public override int GetHashCode()
+        {
+            return _token.GetHashCode();
+        }
+
+        /// <summary>
+        /// Disposes the SubscriptionToken, removing the subscription from the corresponding <see cref="EventBase"/>.
+        /// </summary>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Should never have need for a finalizer, hence no need for Dispose(bool).")]
+        public virtual void Dispose()
+        {
+            // While the SubscriptionToken class implements IDisposable, in the case of weak subscriptions 
+            // (i.e. keepSubscriberReferenceAlive set to false in the Subscribe method) it's not necessary to unsubscribe,
+            // as no resources should be kept alive by the event subscription. 
+            // In such cases, if a warning is issued, it could be suppressed.
+
+            if (this._unsubscribeAction != null)
+            {
+                this._unsubscribeAction(this);
+                this._unsubscribeAction = null;
+            }
+
+            GC.SuppressFinalize(this);
+        }
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/ThreadOption.cs b/src/Lucene.Net/Support/Util/Events/ThreadOption.cs
new file mode 100644
index 000000000..3043c2110
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/ThreadOption.cs
@@ -0,0 +1,47 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Specifies on which thread a <see cref="PubSubEvent{TPayload}"/> subscriber will be called.
+    /// </summary>
+    internal enum ThreadOption
+    {
+        /// <summary>
+        /// The call is done on the same thread on which the <see cref="PubSubEvent{TPayload}"/> was published.
+        /// </summary>
+        PublisherThread,
+
+        /// <summary>
+        /// The call is done on the UI thread.
+        /// </summary>
+        UIThread,
+
+        /// <summary>
+        /// The call is done asynchronously on a background thread.
+        /// </summary>
+        BackgroundThread
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events/WeakDelegatesManager.cs b/src/Lucene.Net/Support/Util/Events/WeakDelegatesManager.cs
new file mode 100644
index 000000000..5f171fe48
--- /dev/null
+++ b/src/Lucene.Net/Support/Util/Events/WeakDelegatesManager.cs
@@ -0,0 +1,71 @@
+// Source: https://github.com/PrismLibrary/Prism/blob/7f0b1680bbe754da790274f80851265f808d9bbf
+
+#region Copyright .NET Foundation, Licensed under the MIT License (MIT)
+// The MIT License (MIT)
+//
+// Copyright(c).NET Foundation
+//
+// All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+// is furnished to do so, subject to the following conditions: 
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#endregion
+
+#if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net.Util.Events
+{
+    /// <summary>
+    /// Manage delegates using weak references to prevent keeping target instances longer than expected.
+    /// </summary>
+    internal class WeakDelegatesManager
+    {
+        private readonly List<DelegateReference> _listeners = new List<DelegateReference>();
+
+        /// <summary>
+        /// Adds a weak reference to the specified <see cref="Delegate"/> listener.
+        /// </summary>
+        /// <param name="listener">The original <see cref="Delegate"/> to add.</param>
+        public void AddListener(Delegate listener)
+        {
+            _listeners.Add(new DelegateReference(listener, false));
+        }
+
+        /// <summary>
+        /// Removes the weak reference to the specified <see cref="Delegate"/> listener.
+        /// </summary>
+        /// <param name="listener">The original <see cref="Delegate"/> to remove.</param>
+        public void RemoveListener(Delegate listener)
+        {
+            //Remove the listener, and prune collected listeners
+            _listeners.RemoveAll(reference => reference.TargetEquals(null) || reference.TargetEquals(listener));
+        }
+
+        /// <summary>
+        /// Invoke the delegates for all targets still being alive.
+        /// </summary>
+        /// <param name="args">An array of objects that are the arguments to pass to the delegates. -or- null, if the method represented by the delegate does not require arguments. </param>
+        public void Raise(params object[] args)
+        {
+            _listeners.RemoveAll(listener => listener.TargetEquals(null));
+
+            foreach (Delegate handler in _listeners.Select(listener => listener.Target).Where(listener => listener != null).ToList())
+            {
+                handler.DynamicInvoke(args);
+            }
+        }
+    }
+}
+
+#endif
diff --git a/src/Lucene.Net/Support/Util/Events.cs b/src/Lucene.Net/Support/Util/WeakEvents.cs
similarity index 96%
rename from src/Lucene.Net/Support/Util/Events.cs
rename to src/Lucene.Net/Support/Util/WeakEvents.cs
index 3d1203cdf..365c84726 100644
--- a/src/Lucene.Net/Support/Util/Events.cs
+++ b/src/Lucene.Net/Support/Util/WeakEvents.cs
@@ -1,6 +1,6 @@
 #if !FEATURE_CONDITIONALWEAKTABLE_ENUMERATOR
 using Lucene.Net.Index;
-using Prism.Events;
+using Lucene.Net.Util.Events;
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
 
@@ -27,7 +27,7 @@ namespace Lucene.Net.Util
     /// 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
+    internal static class WeakEvents
     {
         #region GetParentReaders