Nullability and modernization updates, part 3 (#126)

* Nullability and modernization updates, part 3

* PR comments and fix remoting NDC test

* Additional PR comment
diff --git a/src/log4net.Tests/Appender/RemotingAppenderTest.cs b/src/log4net.Tests/Appender/RemotingAppenderTest.cs
index 5119874..511b221 100644
--- a/src/log4net.Tests/Appender/RemotingAppenderTest.cs
+++ b/src/log4net.Tests/Appender/RemotingAppenderTest.cs
@@ -22,6 +22,8 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
 using System.Reflection;
 using System.Runtime.Remoting;
 using System.Runtime.Remoting.Channels;
@@ -47,7 +49,7 @@
   [TestFixture]
   public class RemotingAppenderTest
   {
-    private IChannel m_remotingChannel = null;
+    private IChannel? m_remotingChannel;
 
     /// <summary>
     /// Test that the Message property is correctly remoted
@@ -55,21 +57,20 @@
     [Test]
     public void TestRemotedMessage()
     {
-      // Setup the remoting appender
+      // Set up the remoting appender
       ConfigureRootAppender(FixFlags.Partial);
 
       RemoteLoggingSinkImpl.Instance.Reset();
 
-      Logger root;
-      root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
+      Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
 
-      string testMessage = string.Format("test message [ {0} ]", (new Random()).Next());
+      string testMessage = $"test message [ {(new Random()).Next()} ]";
 
       // Log a message that will be remoted
       root.Log(Level.Debug, testMessage, null);
 
       // Wait for the remoted object to be delivered
-      Thread.Sleep(2000);
+      WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
 
       LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
       Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
@@ -83,19 +84,18 @@
     [Test]
     public void TestPartialFix()
     {
-      // Setup the remoting appender
+      // Set up the remoting appender
       ConfigureRootAppender(FixFlags.Partial);
 
       RemoteLoggingSinkImpl.Instance.Reset();
 
-      Logger root;
-      root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
+      Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
 
       // Log a message that will be remoted
       root.Log(Level.Debug, "test message", null);
 
       // Wait for the remoted object to be delivered
-      Thread.Sleep(2000);
+      WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
 
       LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
       Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
@@ -109,20 +109,17 @@
     [Test]
     public void TestFullFix()
     {
-      // Setup the remoting appender
+      // Set up the remoting appender
       ConfigureRootAppender(FixFlags.All);
 
       RemoteLoggingSinkImpl.Instance.Reset();
 
-      Logger root;
-      root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
+      Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
 
       // Log a message that will be remoted
       root.Log(Level.Debug, "test message", null);
 
       // Wait for the remoted object to be delivered
-      Thread.Sleep(5000);
-
       WaitFor("Remote instance should have received a remoting event", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
       LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
       Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
@@ -130,7 +127,7 @@
       Assert.IsNotNull(events[0].LocationInfo, "Expect LocationInfo to not be null because doing a full fix");
     }
 
-    private void WaitFor(
+    private static void WaitFor(
       string failMessage,
       Func<bool> condition,
       int maxWaitMilliseconds = 5000)
@@ -139,7 +136,9 @@
       do
       {
         if (condition())
+        {
           return;
+        }
         Thread.Sleep(100);
       } while ((DateTime.Now - start).TotalMilliseconds < maxWaitMilliseconds);
       throw new TimeoutException($"Condition not achieved within {maxWaitMilliseconds}ms: {failMessage}");
@@ -151,15 +150,14 @@
     [Test]
     public void TestRemotedMessageNdcPushPop()
     {
-      // Setup the remoting appender
+      // Set up the remoting appender
       ConfigureRootAppender(FixFlags.Partial);
 
       RemoteLoggingSinkImpl.Instance.Reset();
 
-      Logger root;
-      root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
+      Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
 
-      string testMessage = string.Format("test message [ {0} ]", (new Random()).Next());
+      string testMessage = $"test message [ {(new Random()).Next()} ]";
 
       using (NDC.Push("value"))
       {
@@ -169,7 +167,7 @@
       root.Log(Level.Debug, testMessage, null);
 
       // Wait for the remoted object to be delivered
-      Thread.Sleep(2000);
+      WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length > 0);
 
       LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
       Assert.AreEqual(1, events.Length, "Expect to receive 1 remoted event");
@@ -180,47 +178,43 @@
     [Test]
     public void TestNestedNdc()
     {
-      // This test can suffer from timing and ordering issues as the RemotingAppender does dispatch events asynchronously
-
-      // Setup the remoting appender
+      // Set up the remoting appender
       ConfigureRootAppender(FixFlags.Partial);
 
       RemoteLoggingSinkImpl.Instance.Reset();
 
-      TestService t;
-      t = new TestService();
+      var t = new TestService();
       t.Test();
 
-      // Wait for the remoted objects to be delivered
-      Thread.Sleep(3000);
+      WaitFor("Remote instance should have received all remoting events", () => RemoteLoggingSinkImpl.Instance.Events.Length == 5);
 
       LoggingEvent[] events = RemoteLoggingSinkImpl.Instance.Events;
-      Assert.AreEqual(5, events.Length, "Expect to receive 5 remoted event");
 
-      Assert.AreEqual("begin test", events[0].RenderedMessage, "Verify event 1 RenderedMessage");
-      Assert.AreEqual("feature", events[1].RenderedMessage, "Verify event 2 RenderedMessage");
-      Assert.AreEqual("return", events[2].RenderedMessage, "Verify event 3 RenderedMessage");
-      Assert.AreEqual("return", events[3].RenderedMessage, "Verify event 4 RenderedMessage");
-      Assert.AreEqual("end test", events[4].RenderedMessage, "Verify event 5 RenderedMessage");
+      // RemotingAppender dispatches events asynchronously, messages could be in any order.
+      LoggingEvent beingTest = events.First(e => e.RenderedMessage == "begin test");
+      Assert.IsNull(beingTest.Properties["NDC"], "Verify 'being test' event Properties");
 
-      Assert.IsNull(events[0].Properties["NDC"], "Verify event 1 Properties");
-      Assert.AreEqual("test1", events[1].Properties["NDC"], "Verify event 2 Properties");
-      Assert.AreEqual("test1 test2", events[2].Properties["NDC"], "Verify event 3 Properties");
-      Assert.AreEqual("test1", events[3].Properties["NDC"], "Verify event 4 Properties");
-      Assert.IsNull(events[4].Properties["NDC"], "Verify event 5 Properties");
+      LoggingEvent feature = events.First(e => e.RenderedMessage == "feature");
+      Assert.AreEqual("test1", feature.Properties["NDC"], "Verify 'feature' event Properties");
+
+      LoggingEvent return1 = events.First(e => e.RenderedMessage == "return" && Equals(e.Properties["NDC"], "test1 test2"));
+
+      LoggingEvent return2 = events.First(e => e.RenderedMessage == "return" && Equals(e.Properties["NDC"], "test1"));
+
+      LoggingEvent endTest = events.First(e => e.RenderedMessage == "end test");
+      Assert.IsNull(endTest.Properties["NDC"], "Verify 'end test' event Properties");
     }
 
-
     private void RegisterRemotingServerChannel()
     {
-      if (m_remotingChannel == null)
+      if (m_remotingChannel is null)
       {
         BinaryClientFormatterSinkProvider clientSinkProvider = new BinaryClientFormatterSinkProvider();
 
         BinaryServerFormatterSinkProvider serverSinkProvider = new BinaryServerFormatterSinkProvider();
         serverSinkProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
 
-        Hashtable channelProperties = new Hashtable();
+        var channelProperties = new Hashtable();
         channelProperties["port"] = 8085;
 
         m_remotingChannel = new TcpChannel(channelProperties, clientSinkProvider, serverSinkProvider);
@@ -277,7 +271,7 @@
     [OneTimeTearDown]
     public void UnregisterRemotingServerChannel()
     {
-      if (m_remotingChannel != null)
+      if (m_remotingChannel is not null)
       {
         ((TcpChannel)m_remotingChannel).StopListening(null);
         try
@@ -296,20 +290,21 @@
     /// </summary>
     private static void ConfigureRootAppender(FixFlags fixFlags)
     {
-      Logger root;
-      root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
+      Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
       root.Level = Level.Debug;
       root.AddAppender(CreateAppender(fixFlags));
-      root.Repository.Configured = true;
+      root.Repository!.Configured = true;
     }
 
     private static RemotingAppender CreateAppender(FixFlags fixFlags)
     {
-      RemotingAppender appender = new RemotingAppender();
-      appender.Sink = "tcp://localhost:8085/LoggingSink";
-      appender.Lossy = false;
-      appender.BufferSize = 1;
-      appender.Fix = fixFlags;
+      var appender = new RemotingAppender
+      {
+        Sink = "tcp://localhost:8085/LoggingSink",
+        Lossy = false,
+        BufferSize = 1,
+        Fix = fixFlags
+      };
 
       appender.ActivateOptions();
 
@@ -318,33 +313,25 @@
 
     public class RemoteLoggingSinkImpl : MarshalByRefObject, RemotingAppender.IRemoteLoggingSink
     {
-      public static readonly RemoteLoggingSinkImpl Instance = new RemoteLoggingSinkImpl();
+      public static readonly RemoteLoggingSinkImpl Instance = new();
 
-      private ArrayList m_events = new ArrayList();
+      private readonly List<LoggingEvent> m_events = new();
 
-      #region Public Instance Constructors
       private RemoteLoggingSinkImpl()
       {
       }
-      #endregion Public Instance Constructors
 
-      #region Implementation of IRemoteLoggingSink
       /// <summary>
-      /// Logs the events to to an internal buffer
+      /// Logs the events to an internal buffer.
       /// </summary>
       /// <param name="events">The events to log.</param>
       /// <remarks>
-      /// Logs the events to to an internal buffer. The logged events can 
+      /// The logged events can 
       /// be retrieved via the <see cref="Events"/> property. To clear
       /// the buffer call the <see cref="Reset"/> method.
       /// </remarks>
-      public void LogEvents(LoggingEvent[] events)
-      {
-        m_events.AddRange(events);
-      }
-      #endregion Implementation of IRemoteLoggingSink
+      public void LogEvents(LoggingEvent[] events) => m_events.AddRange(events);
 
-      #region Override implementation of MarshalByRefObject
       /// <summary>
       /// Obtains a lifetime service object to control the lifetime 
       /// policy for this instance.
@@ -353,21 +340,14 @@
       /// <c>null</c> to indicate that this instance should live
       /// forever.
       /// </returns>
-      public override object InitializeLifetimeService()
+      public override object? InitializeLifetimeService()
       {
         return null;
       }
-      #endregion Override implementation of MarshalByRefObject
 
-      public void Reset()
-      {
-        m_events.Clear();
-      }
+      public void Reset() => m_events.Clear();
 
-      public LoggingEvent[] Events
-      {
-        get { return (LoggingEvent[])m_events.ToArray(typeof(LoggingEvent)); }
-      }
+      public LoggingEvent[] Events => m_events.ToArray();
     }
   }
 }
@@ -378,14 +358,14 @@
 {
   public class TestService
   {
-    private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+    private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod()!.DeclaringType!);
 
     public void Test()
     {
       log.Info("begin test");
       Thread.Sleep(100);
 
-      Feature f = new Feature();
+      var f = new Feature();
       f.Test();
       log.Info("end test");
       Thread.Sleep(100);
@@ -399,7 +379,7 @@
 {
   public class Feature
   {
-    private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+    private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod()!.DeclaringType!);
 
     public void Test()
     {
@@ -408,7 +388,7 @@
         log.Info("feature");
         Thread.Sleep(100);
 
-        Dal d = new Dal();
+        var d = new Dal();
         d.Test();
         log.Info("return");
         Thread.Sleep(100);
@@ -423,7 +403,7 @@
 {
   public class Dal
   {
-    private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+    private static ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod()!.DeclaringType!);
 
     public void Test()
     {
diff --git a/src/log4net.Tests/Appender/StringAppender.cs b/src/log4net.Tests/Appender/StringAppender.cs
index 83602f8..90e0c6e 100644
--- a/src/log4net.Tests/Appender/StringAppender.cs
+++ b/src/log4net.Tests/Appender/StringAppender.cs
@@ -30,7 +30,7 @@
   /// <author>Nicko Cadell</author>
   public class StringAppender : AppenderSkeleton
   {
-    private StringBuilder m_buf = new StringBuilder();
+    private StringBuilder m_buf = new();
 
     /// <summary>
     /// Initializes a new instance of the <see cref="StringAppender" /> class.
@@ -68,9 +68,6 @@
     /// This appender requires a <see cref="Layout"/> to be set.
     /// </summary>
     /// <value><c>true</c></value>
-    protected override bool RequiresLayout
-    {
-      get { return true; }
-    }
+    protected override bool RequiresLayout => true;
   }
 }
\ No newline at end of file
diff --git a/src/log4net.Tests/Context/LogicalThreadContextTest.cs b/src/log4net.Tests/Context/LogicalThreadContextTest.cs
index 8d61a39..b4861e0 100644
--- a/src/log4net.Tests/Context/LogicalThreadContextTest.cs
+++ b/src/log4net.Tests/Context/LogicalThreadContextTest.cs
@@ -29,6 +29,8 @@
 
 using NUnit.Framework;
 
+#nullable enable
+
 namespace log4net.Tests.Context
 {
   /// <summary>
@@ -253,7 +255,7 @@
       stringAppender.Reset();
 
       string testValueForCurrentContext = "Outer";
-      string[] strings = null;
+      string[]? strings;
       using (LogicalThreadContext.Stacks[Utils.PROPERTY_KEY].Push(testValueForCurrentContext))
       {
         log1.Info("TestMessage");
@@ -304,10 +306,12 @@
       log.Info("TestMessage");
     }
 
-    static async Task<string> SomeWorkStack(string stackName)
+    private static async Task<string> SomeWorkStack(string stackName)
     {
-      StringAppender stringAppender = new StringAppender();
-      stringAppender.Layout = new PatternLayout("%property{" + Utils.PROPERTY_KEY + "}");
+      var stringAppender = new StringAppender
+      {
+        Layout = new PatternLayout("%property{" + Utils.PROPERTY_KEY + "}")
+      };
 
       ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
       BasicConfigurator.Configure(rep, stringAppender);
diff --git a/src/log4net.Tests/Context/ThreadContextTest.cs b/src/log4net.Tests/Context/ThreadContextTest.cs
index f08a63c..a01ca03 100644
--- a/src/log4net.Tests/Context/ThreadContextTest.cs
+++ b/src/log4net.Tests/Context/ThreadContextTest.cs
@@ -21,6 +21,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Threading;
 using log4net.Config;
@@ -35,9 +36,6 @@
   /// <summary>
   /// Used for internal unit testing the <see cref="ThreadContext"/> class.
   /// </summary>
-  /// <remarks>
-  /// Used for internal unit testing the <see cref="ThreadContext"/> class.
-  /// </remarks>
   [TestFixture]
   public class ThreadContextTest
   {
@@ -197,36 +195,36 @@
       stringAppender.Reset();
     }
 
-    private static string TestBackgroundThreadContextPropertyRepository;
-
     [Test]
     public void TestBackgroundThreadContextProperty()
     {
       StringAppender stringAppender = new StringAppender();
       stringAppender.Layout = new PatternLayout("%property{DateTimeTodayToString}");
 
-      ILoggerRepository rep = LogManager.CreateRepository(TestBackgroundThreadContextPropertyRepository =
-          "TestBackgroundThreadContextPropertyRepository" + Guid.NewGuid().ToString());
+      string testBackgroundThreadContextPropertyRepository =
+        "TestBackgroundThreadContextPropertyRepository" + Guid.NewGuid();
+      ILoggerRepository rep = LogManager.CreateRepository(testBackgroundThreadContextPropertyRepository);
       BasicConfigurator.Configure(rep, stringAppender);
 
-      Thread thread = new Thread(new ThreadStart(ExecuteBackgroundThread));
-      thread.Start();
+      Thread thread = new Thread(ExecuteBackgroundThread);
+      thread.Start(testBackgroundThreadContextPropertyRepository);
 
       Thread.CurrentThread.Join(2000);
     }
 
-    private static void ExecuteBackgroundThread()
+    private static void ExecuteBackgroundThread(object context)
     {
-      ILog log = LogManager.GetLogger(TestBackgroundThreadContextPropertyRepository, "ExecuteBackGroundThread");
-      ThreadContext.Properties["DateTimeTodayToString"] = DateTime.Today.ToString();
+      string testBackgroundThreadContextPropertyRepository = (string)context;
+      ILog log = LogManager.GetLogger(testBackgroundThreadContextPropertyRepository, "ExecuteBackGroundThread");
+      ThreadContext.Properties["DateTimeTodayToString"] = DateTime.Today.ToString(CultureInfo.InvariantCulture);
 
       log.Info("TestMessage");
 
       Repository.Hierarchy.Hierarchy hierarchyLoggingRepository =
           (Repository.Hierarchy.Hierarchy)log.Logger.Repository;
-      StringAppender stringAppender = (StringAppender)hierarchyLoggingRepository.Root.Appenders[0];
+      StringAppender stringAppender = (StringAppender)hierarchyLoggingRepository!.Root.Appenders[0];
 
-      Assert.AreEqual(DateTime.Today.ToString(), stringAppender.GetString());
+      Assert.AreEqual(DateTime.Today.ToString(CultureInfo.InvariantCulture), stringAppender.GetString());
     }
 
     [Test]
@@ -237,7 +235,7 @@
       var flags = new List<FlagContainer>();
 
       // Act
-      for (var i = 0; i < 256; i++)
+      for (var i = 0; i < Math.Max(64, 4 * Environment.ProcessorCount); i++)
       {
         var t = new Thread(SpinAndCheck);
         var flag = new FlagContainer();
@@ -261,7 +259,7 @@
 
     private void SpinAndCheck(object obj)
     {
-      var container = obj as FlagContainer;
+      var container = (FlagContainer)obj;
       var threadid = Thread.CurrentThread.ManagedThreadId;
       for (var i = 0; i < 100000; i++)
       {
diff --git a/src/log4net.Tests/Core/DefaultRepositorySelectorTest.cs b/src/log4net.Tests/Core/DefaultRepositorySelectorTest.cs
index 024e03d..a052592 100644
--- a/src/log4net.Tests/Core/DefaultRepositorySelectorTest.cs
+++ b/src/log4net.Tests/Core/DefaultRepositorySelectorTest.cs
@@ -21,7 +21,6 @@
 using System.Collections;
 using System.Collections.Generic;
 using log4net.Appender;
-using log4net.Config;
 using log4net.Core;
 using log4net.ObjectRenderer;
 using log4net.Plugin;
@@ -183,7 +182,5 @@
     }
   }
 
-  internal class MockLoggerRepository2 : MockLoggerRepository
-  {
-  }
+  internal class MockLoggerRepository2 : MockLoggerRepository;
 }
diff --git a/src/log4net.Tests/Hierarchy/LoggerTest.cs b/src/log4net.Tests/Hierarchy/LoggerTest.cs
index b03357b..6dbd233 100644
--- a/src/log4net.Tests/Hierarchy/LoggerTest.cs
+++ b/src/log4net.Tests/Hierarchy/LoggerTest.cs
@@ -71,6 +71,7 @@
       {
         Assert.IsTrue(enumAppenders.MoveNext());
         var aHat = (CountingAppender?)enumAppenders.Current;
+        Assert.IsNotNull(aHat);
         Assert.AreEqual(a1, aHat);
       }
       finally
@@ -101,6 +102,7 @@
       Assert.AreEqual(a1, aHat);
 
       aHat = (CountingAppender?)log.GetAppender(a2.Name);
+      Assert.IsNotNull(aHat);
       Assert.AreEqual(a2, aHat);
 
       // By name.
@@ -113,6 +115,7 @@
       {
         Assert.IsTrue(enumAppenders.MoveNext());
         aHat = (CountingAppender?)enumAppenders.Current;
+        Assert.IsNotNull(aHat);
         Assert.AreSame(a2, aHat);
         Assert.IsFalse(enumAppenders.MoveNext());
       }
@@ -125,6 +128,7 @@
       }
 
       aHat = (CountingAppender?)log.GetAppender(a2.Name);
+      Assert.IsNotNull(aHat);
       Assert.AreSame(a2, aHat);
 
       // By appender.
@@ -154,7 +158,7 @@
     {
       Logger a = (Logger)LogManager.GetLogger("a").Logger;
       Logger ab = (Logger)LogManager.GetLogger("a.b").Logger;
-      CountingAppender ca = new CountingAppender();
+      var ca = new CountingAppender();
 
       a.AddAppender(ca);
       Assert.IsNotNull(a.Repository);
@@ -182,8 +186,8 @@
       Logger abc = (Logger)LogManager.GetLogger("a.b.c").Logger;
       Logger x = (Logger)LogManager.GetLogger("x").Logger;
 
-      CountingAppender ca1 = new CountingAppender();
-      CountingAppender ca2 = new CountingAppender();
+      var ca1 = new CountingAppender();
+      var ca2 = new CountingAppender();
 
       a.AddAppender(ca1);
       abc.AddAppender(ca2);
@@ -217,9 +221,9 @@
       Logger ab = (Logger)LogManager.GetLogger("a.b").Logger;
       Logger abc = (Logger)LogManager.GetLogger("a.b.c").Logger;
 
-      CountingAppender caRoot = new CountingAppender();
-      CountingAppender caA = new CountingAppender();
-      CountingAppender caABC = new CountingAppender();
+      var caRoot = new CountingAppender();
+      var caA = new CountingAppender();
+      var caABC = new CountingAppender();
 
       root.AddAppender(caRoot);
       a.AddAppender(caA);
@@ -255,7 +259,7 @@
     [Test]
     public void TestDisable1()
     {
-      CountingAppender caRoot = new CountingAppender();
+      var caRoot = new CountingAppender();
       Logger root = ((Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root;
       root.AddAppender(caRoot);
 
@@ -308,8 +312,11 @@
     public void TestExists()
     {
       ILog a = LogManager.GetLogger("a");
+      Assert.IsNotNull(a);
       ILog a_b = LogManager.GetLogger("a.b");
+      Assert.IsNotNull(a_b);
       ILog a_b_c = LogManager.GetLogger("a.b.c");
+      Assert.IsNotNull(a_b_c);
 
       ILog? t = LogManager.Exists("xx");
       Assert.IsNull(t);
@@ -327,7 +334,7 @@
     [Test]
     public void TestHierarchy1()
     {
-      Repository.Hierarchy.Hierarchy h = new Repository.Hierarchy.Hierarchy();
+      var h = new Repository.Hierarchy.Hierarchy();
       h.Root.Level = Level.Error;
 
       Logger a0 = (Logger)h.GetLogger("a");
@@ -339,4 +346,4 @@
       Assert.AreSame(a0, a1);
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net.Tests/Util/LogLogTest.cs b/src/log4net.Tests/Util/LogLogTest.cs
index d9fcf4f..4aed3a2 100644
--- a/src/log4net.Tests/Util/LogLogTest.cs
+++ b/src/log4net.Tests/Util/LogLogTest.cs
@@ -19,7 +19,7 @@
  *
 */
 
-using System.Collections;
+using System.Collections.Generic;
 using System.Diagnostics;
 using log4net.Util;
 using NUnit.Framework;
@@ -71,7 +71,7 @@
     [Test]
     public void LogReceivedAdapter()
     {
-      ArrayList messages = new ArrayList();
+      var messages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(messages))
       {
@@ -86,26 +86,21 @@
 
   public class TraceListenerCounter : TraceListener
   {
-    private int count = 0;
-
-    public override void Write(string message)
+    public override void Write(string? message)
     {
-      count++;
+      Count++;
     }
 
-    public override void WriteLine(string message)
+    public override void WriteLine(string? message)
     {
       Write(message);
     }
 
     public void Reset()
     {
-      count = 0;
+      Count = 0;
     }
 
-    public int Count
-    {
-      get { return count; }
-    }
+    public int Count { get; private set; }
   }
 }
diff --git a/src/log4net.Tests/Util/PropertiesDictionaryTest.cs b/src/log4net.Tests/Util/PropertiesDictionaryTest.cs
index 51cf6bf..861b7ac 100644
--- a/src/log4net.Tests/Util/PropertiesDictionaryTest.cs
+++ b/src/log4net.Tests/Util/PropertiesDictionaryTest.cs
@@ -19,6 +19,7 @@
 
 #if NET462_OR_GREATER
 
+using System.Collections.Generic;
 using System.IO;
 using System.Runtime.Serialization.Formatters.Binary;
 
@@ -49,6 +50,8 @@
 
       Assert.AreEqual(10, pd.Count, "Dictionary should have 10 items");
 
+      Assert.IsNull(pd["notThere"], "Getter should act as IDictionary not IDictionary<TKey, TValue>");
+
       // Serialize the properties into a memory stream
       BinaryFormatter formatter = new BinaryFormatter();
       MemoryStream memory = new MemoryStream();
diff --git a/src/log4net/Config/BasicConfigurator.cs b/src/log4net/Config/BasicConfigurator.cs
index be31939..7af9f90 100644
--- a/src/log4net/Config/BasicConfigurator.cs
+++ b/src/log4net/Config/BasicConfigurator.cs
@@ -19,6 +19,7 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Reflection;
 
 using log4net.Appender;
@@ -49,10 +50,8 @@
   /// </remarks>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  public sealed class BasicConfigurator
+  public static class BasicConfigurator
   {
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the BasicConfigurator class.
     /// </summary>
@@ -62,26 +61,6 @@
     /// </remarks>
     private static readonly Type declaringType = typeof(BasicConfigurator);
 
-    #endregion Private Static Fields
-
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="BasicConfigurator" /> class. 
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Uses a private access modifier to prevent instantiation of this class.
-    /// </para>
-    /// </remarks>
-    private BasicConfigurator()
-    {
-    }
-
-    #endregion Private Instance Constructors
-
-    #region Public Static Methods
-
     /// <summary>
     /// Initializes the log4net system with a default configuration.
     /// </summary>
@@ -96,7 +75,7 @@
     /// </remarks>
     public static ICollection Configure()
     {
-      return BasicConfigurator.Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()));
+      return Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()));
     }
 
     /// <summary>
@@ -110,7 +89,7 @@
     /// </remarks>
     public static ICollection Configure(params IAppender[] appenders)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
 
@@ -125,20 +104,6 @@
     }
 
     /// <summary>
-    /// Initializes the log4net system using the specified appender.
-    /// </summary>
-    /// <param name="appender">The appender to use to log all logging events.</param>
-    /// <remarks>
-    /// <para>
-    /// Initializes the log4net system using the specified appender.
-    /// </para>
-    /// </remarks>
-    public static ICollection Configure(IAppender appender)
-    {
-      return Configure(new IAppender[] { appender });
-    }
-
-    /// <summary>
     /// Initializes the <see cref="ILoggerRepository"/> with a default configuration.
     /// </summary>
     /// <param name="repository">The repository to configure.</param>
@@ -153,18 +118,16 @@
     /// </remarks>
     public static ICollection Configure(ILoggerRepository repository)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
         // Create the layout
-        PatternLayout layout = new PatternLayout();
-        layout.ConversionPattern = PatternLayout.DetailConversionPattern;
+        var layout = new PatternLayout { ConversionPattern = PatternLayout.DetailConversionPattern };
         layout.ActivateOptions();
 
         // Create the appender
-        ConsoleAppender appender = new ConsoleAppender();
-        appender.Layout = layout;
+        var appender = new ConsoleAppender { Layout = layout };
         appender.ActivateOptions();
 
         InternalConfigure(repository, appender);
@@ -176,21 +139,6 @@
     }
 
     /// <summary>
-    /// Initializes the <see cref="ILoggerRepository"/> using the specified appender.
-    /// </summary>
-    /// <param name="repository">The repository to configure.</param>
-    /// <param name="appender">The appender to use to log all logging events.</param>
-    /// <remarks>
-    /// <para>
-    /// Initializes the <see cref="ILoggerRepository"/> using the specified appender.
-    /// </para>
-    /// </remarks>
-    public static ICollection Configure(ILoggerRepository repository, IAppender appender)
-    {
-      return Configure(repository, new IAppender[] { appender });
-    }
-
-    /// <summary>
     /// Initializes the <see cref="ILoggerRepository"/> using the specified appenders.
     /// </summary>
     /// <param name="repository">The repository to configure.</param>
@@ -202,7 +150,7 @@
     /// </remarks>
     public static ICollection Configure(ILoggerRepository repository, params IAppender[] appenders)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -216,17 +164,14 @@
 
     private static void InternalConfigure(ILoggerRepository repository, params IAppender[] appenders)
     {
-      IBasicRepositoryConfigurator configurableRepository = repository as IBasicRepositoryConfigurator;
-      if (configurableRepository != null)
+      if (repository is IBasicRepositoryConfigurator configurableRepository)
       {
         configurableRepository.Configure(appenders);
       }
       else
       {
-        LogLog.Warn(declaringType, "BasicConfigurator: Repository [" + repository + "] does not support the BasicConfigurator");
+        LogLog.Warn(declaringType, $"BasicConfigurator: Repository [{repository}] does not support the BasicConfigurator");
       }
     }
-
-    #endregion Public Static Methods
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Config/XmlConfigurator.cs b/src/log4net/Config/XmlConfigurator.cs
index 48f22c3..29ecbab 100644
--- a/src/log4net/Config/XmlConfigurator.cs
+++ b/src/log4net/Config/XmlConfigurator.cs
@@ -20,6 +20,7 @@
 using System;
 using System.Xml;
 using System.Collections;
+using System.Collections.Concurrent;
 using System.IO;
 using System.Reflection;
 using System.Threading;
@@ -27,34 +28,17 @@
 
 using log4net.Util;
 using log4net.Repository;
+using System.Collections.Generic;
 
 namespace log4net.Config
 {
   /// <summary>
-  /// Use this class to initialize the log4net environment using an Xml tree.
+  /// Configures a <see cref="ILoggerRepository"/> using an XML tree.
   /// </summary>
-  /// <remarks>
-  /// <para>
-  /// Configures a <see cref="ILoggerRepository"/> using an Xml tree.
-  /// </para>
-  /// </remarks>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  public sealed class XmlConfigurator
+  public static class XmlConfigurator
   {
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Private constructor
-    /// </summary>
-    private XmlConfigurator()
-    {
-    }
-
-    #endregion Protected Instance Constructors
-
-    #region Configure static methods
-
     /// <summary>
     /// Automatically configures the <see cref="ILoggerRepository"/> using settings
     /// stored in the application's configuration file.
@@ -77,7 +61,7 @@
     /// <param name="repository">The repository to configure.</param>
     public static ICollection Configure(ILoggerRepository repository)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -91,11 +75,11 @@
 
     private static void InternalConfigure(ILoggerRepository repository)
     {
-      LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using .config file section");
+      LogLog.Debug(declaringType, $"configuring repository [{repository.Name}] using .config file section");
 
       try
       {
-        LogLog.Debug(declaringType, "Application config file is [" + SystemInfo.ConfigurationFileLocation + "]");
+        LogLog.Debug(declaringType, $"Application config file is [{SystemInfo.ConfigurationFileLocation}]");
       }
       catch
       {
@@ -105,8 +89,7 @@
 
       try
       {
-        XmlElement configElement = System.Configuration.ConfigurationManager.GetSection("log4net") as XmlElement;
-        if (configElement == null)
+        if (System.Configuration.ConfigurationManager.GetSection("log4net") is not XmlElement configElement)
         {
           // Failed to load the xml config using configuration settings handler
           LogLog.Error(declaringType, "Failed to find configuration section 'log4net' in the application's .config file. Check your .config file for the <log4net> and <configSections> elements. The configuration section should look like: <section name=\"log4net\" type=\"log4net.Config.Log4NetConfigurationSectionHandler,log4net\" />");
@@ -119,7 +102,7 @@
       }
       catch (System.Configuration.ConfigurationException confEx)
       {
-        if (confEx.BareMessage.IndexOf("Unrecognized element") >= 0)
+        if (confEx.BareMessage.IndexOf("Unrecognized element", StringComparison.Ordinal) >= 0)
         {
           // Looks like the XML file is not valid
           LogLog.Error(declaringType, "Failed to parse config file. Check your .config file is well formed XML.", confEx);
@@ -170,7 +153,7 @@
     /// <param name="element">The element to parse.</param>
     public static ICollection Configure(XmlElement element)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
 
@@ -195,7 +178,7 @@
     /// the log4net configuration data.
     /// </para>
     /// <para>
-    /// The log4net configuration file can possible be specified in the application's
+    /// The log4net configuration file can possibly be specified in the application's
     /// configuration file (either <c>MyAppName.exe.config</c> for a
     /// normal application on <c>Web.config</c> for an ASP.NET application).
     /// </para>
@@ -236,7 +219,7 @@
     /// </remarks>
     public static ICollection Configure(FileInfo configFile)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -262,7 +245,7 @@
     /// </remarks>
     public static ICollection Configure(Uri configUri)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
       using (new LogLog.LogReceivedAdapter(configurationMessages))
@@ -291,7 +274,7 @@
     /// </remarks>
     public static ICollection Configure(Stream configStream)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
       using (new LogLog.LogReceivedAdapter(configurationMessages))
@@ -316,7 +299,7 @@
     /// <param name="element">The element to parse.</param>
     public static ICollection Configure(ILoggerRepository repository, XmlElement element)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -343,7 +326,7 @@
     /// the configuration data.
     /// </para>
     /// <para>
-    /// The log4net configuration file can possible be specified in the application's
+    /// The log4net configuration file can possibly be specified in the application's
     /// configuration file (either <c>MyAppName.exe.config</c> for a
     /// normal application on <c>Web.config</c> for an ASP.NET application).
     /// </para>
@@ -384,7 +367,7 @@
     /// </remarks>
     public static ICollection Configure(ILoggerRepository repository, FileInfo configFile)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -396,11 +379,11 @@
       return configurationMessages;
     }
 
-    private static void InternalConfigure(ILoggerRepository repository, FileInfo configFile)
+    private static void InternalConfigure(ILoggerRepository repository, FileInfo? configFile)
     {
-      LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using file [" + configFile + "]");
+      LogLog.Debug(declaringType, $"configuring repository [{repository.Name}] using file [{configFile}]");
 
-      if (configFile == null)
+      if (configFile is null)
       {
         LogLog.Error(declaringType, "Configure called with null 'configFile' parameter");
       }
@@ -411,7 +394,7 @@
         if (File.Exists(configFile.FullName))
         {
           // Open the file for reading
-          FileStream fs = null;
+          FileStream? fs = null;
 
           // Try hard to open the file
           for (int retry = 5; --retry >= 0;)
@@ -425,16 +408,16 @@
             {
               if (retry == 0)
               {
-                LogLog.Error(declaringType, "Failed to open XML config file [" + configFile.Name + "]", ex);
+                LogLog.Error(declaringType, $"Failed to open XML config file [{configFile.Name}]", ex);
 
                 // The stream cannot be valid
                 fs = null;
               }
-              System.Threading.Thread.Sleep(250);
+              Thread.Sleep(250);
             }
           }
 
-          if (fs != null)
+          if (fs is not null)
           {
             try
             {
@@ -473,7 +456,7 @@
     /// </remarks>
     public static ICollection Configure(ILoggerRepository repository, Uri configUri)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -485,11 +468,11 @@
       return configurationMessages;
     }
 
-    private static void InternalConfigure(ILoggerRepository repository, Uri configUri)
+    private static void InternalConfigure(ILoggerRepository repository, Uri? configUri)
     {
-      LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using URI [" + configUri + "]");
+      LogLog.Debug(declaringType, $"configuring repository [{repository.Name}] using URI [{configUri}]");
 
-      if (configUri == null)
+      if (configUri is null)
       {
         LogLog.Error(declaringType, "Configure called with null 'configUri' parameter");
       }
@@ -503,7 +486,7 @@
         else
         {
           // NETCF dose not support WebClient
-          WebRequest configRequest = null;
+          WebRequest? configRequest = null;
 
           try
           {
@@ -511,10 +494,10 @@
           }
           catch (Exception ex)
           {
-            LogLog.Error(declaringType, "Failed to create WebRequest for URI [" + configUri + "]", ex);
+            LogLog.Error(declaringType, $"Failed to create WebRequest for URI [{configUri}]", ex);
           }
 
-          if (configRequest != null)
+          if (configRequest is not null)
           {
             // authentication may be required, set client to use default credentials
             try
@@ -527,8 +510,8 @@
             }
             try
             {
-              using WebResponse response = configRequest.GetResponse();
-              if (response != null)
+              using WebResponse? response = configRequest.GetResponse();
+              if (response is not null)
               {
                 using var configStream = response.GetResponseStream();
                 InternalConfigure(repository, configStream);
@@ -536,7 +519,7 @@
             }
             catch (Exception ex)
             {
-              LogLog.Error(declaringType, "Failed to request config from URI [" + configUri + "]", ex);
+              LogLog.Error(declaringType, $"Failed to request config from URI [{configUri}]", ex);
             }
           }
         }
@@ -561,7 +544,7 @@
     /// </remarks>
     public static ICollection Configure(ILoggerRepository repository, Stream configStream)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -573,22 +556,22 @@
       return configurationMessages;
     }
 
-    private static void InternalConfigure(ILoggerRepository repository, Stream configStream)
+    private static void InternalConfigure(ILoggerRepository repository, Stream? configStream)
     {
-      LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using stream");
+      LogLog.Debug(declaringType, $"configuring repository [{repository.Name}] using stream");
 
-      if (configStream == null)
+      if (configStream is null)
       {
         LogLog.Error(declaringType, "Configure called with null 'configStream' parameter");
       }
       else
       {
         // Load the config file into a document
-        XmlDocument doc = new XmlDocument { XmlResolver = null };
+        XmlDocument? doc = new XmlDocument { XmlResolver = null };
         try
         {
           // Allow the DTD to specify entity includes
-          XmlReaderSettings settings = new XmlReaderSettings();
+          var settings = new XmlReaderSettings();
           // .NET 4.0 warning CS0618: 'System.Xml.XmlReaderSettings.ProhibitDtd'
           // is obsolete: 'Use XmlReaderSettings.DtdProcessing property instead.'
           settings.DtdProcessing = DtdProcessing.Ignore;
@@ -607,7 +590,7 @@
           doc = null;
         }
 
-        if (doc != null)
+        if (doc is not null)
         {
           LogLog.Debug(declaringType, "loading XML configuration");
 
@@ -619,7 +602,7 @@
           }
           else if (configNodeList.Count > 1)
           {
-            LogLog.Error(declaringType, "XML configuration contains [" + configNodeList.Count + "] <log4net> elements. Only one is allowed. Configuration Aborted.");
+            LogLog.Error(declaringType, $"XML configuration contains [{configNodeList.Count}] <log4net> elements. Only one is allowed. Configuration Aborted.");
           }
           else
           {
@@ -629,10 +612,6 @@
       }
     }
 
-    #endregion Configure static methods
-
-    #region ConfigureAndWatch static methods
-
     /// <summary>
     /// Configures log4net using the file specified, monitors the file for changes 
     /// and reloads the configuration if a change is detected.
@@ -656,7 +635,7 @@
     /// <seealso cref="M:Configure(FileInfo)"/>
     public static ICollection ConfigureAndWatch(FileInfo configFile)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
 
@@ -695,7 +674,7 @@
     /// <seealso cref="M:Configure(FileInfo)"/>
     public static ICollection ConfigureAndWatch(ILoggerRepository repository, FileInfo configFile)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -707,9 +686,9 @@
       return configurationMessages;
     }
 
-    private static void InternalConfigureAndWatch(ILoggerRepository repository, FileInfo configFile)
+    private static void InternalConfigureAndWatch(ILoggerRepository repository, FileInfo? configFile)
     {
-      LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using file [" + configFile + "] watching for file updates");
+      LogLog.Debug(declaringType, $"configuring repository [{repository.Name}] using file [{configFile}] watching for file updates");
 
       if (configFile == null)
       {
@@ -722,35 +701,26 @@
 
         try
         {
-          lock (m_repositoryName2ConfigAndWatchHandler)
-          {
-            // support multiple repositories each having their own watcher
-            ConfigureAndWatchHandler handler =
-              (ConfigureAndWatchHandler)m_repositoryName2ConfigAndWatchHandler[configFile.FullName];
-
-            if (handler != null)
+          // Support multiple repositories each having their own watcher.
+          // Create and start a watch handler that will reload the
+          // configuration whenever the config file is modified.
+          m_repositoryName2ConfigAndWatchHandler.AddOrUpdate(
+            configFile.FullName,
+            _ => new ConfigureAndWatchHandler(repository, configFile),
+            (_, handler) =>
             {
-              m_repositoryName2ConfigAndWatchHandler.Remove(configFile.FullName);
+              // Replace the old handler.
               handler.Dispose();
-            }
-
-            // Create and start a watch handler that will reload the
-            // configuration whenever the config file is modified.
-            handler = new ConfigureAndWatchHandler(repository, configFile);
-            m_repositoryName2ConfigAndWatchHandler[configFile.FullName] = handler;
-          }
+              return new ConfigureAndWatchHandler(repository, configFile);
+            });
         }
         catch (Exception ex)
         {
-          LogLog.Error(declaringType, "Failed to initialize configuration file watcher for file [" + configFile.FullName + "]", ex);
+          LogLog.Error(declaringType, $"Failed to initialize configuration file watcher for file [{configFile.FullName}]", ex);
         }
       }
     }
 
-    #endregion ConfigureAndWatch static methods
-
-    #region ConfigureAndWatchHandler
-
     /// <summary>
     /// Class used to watch config files.
     /// </summary>
@@ -772,17 +742,17 @@
       /// <summary>
       /// Holds the FileInfo used to configure the XmlConfigurator
       /// </summary>
-      private FileInfo m_configFile;
+      private readonly FileInfo m_configFile;
 
       /// <summary>
       /// Holds the repository being configured.
       /// </summary>
-      private ILoggerRepository m_repository;
+      private readonly ILoggerRepository m_repository;
 
       /// <summary>
       /// The timer used to compress the notification events.
       /// </summary>
-      private Timer m_timer;
+      private readonly Timer m_timer;
 
       /// <summary>
       /// The default amount of time to wait after receiving notification
@@ -794,7 +764,7 @@
       /// Watches file for changes. This object should be disposed when no longer
       /// needed to free system handles on the watched resources.
       /// </summary>
-      private FileSystemWatcher m_watcher;
+      private readonly FileSystemWatcher m_watcher;
 
       /// <summary>
       /// Initializes a new instance of the <see cref="ConfigureAndWatchHandler" /> class to
@@ -814,25 +784,24 @@
         m_configFile = configFile;
 
         // Create a new FileSystemWatcher and set its properties.
-        m_watcher = new FileSystemWatcher();
-
-        m_watcher.Path = m_configFile.DirectoryName;
-        m_watcher.Filter = m_configFile.Name;
-
-        // Set the notification filters
-        m_watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName;
+        m_watcher = new FileSystemWatcher()
+        {
+          Path = m_configFile.DirectoryName,
+          Filter = m_configFile.Name,
+          NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName,
+        };
 
         // Add event handlers. OnChanged will do for all event handlers that fire a FileSystemEventArgs
-        m_watcher.Changed += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged);
-        m_watcher.Created += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged);
-        m_watcher.Deleted += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged);
-        m_watcher.Renamed += new RenamedEventHandler(ConfigureAndWatchHandler_OnRenamed);
+        m_watcher.Changed += ConfigureAndWatchHandler_OnChanged;
+        m_watcher.Created += ConfigureAndWatchHandler_OnChanged;
+        m_watcher.Deleted += ConfigureAndWatchHandler_OnChanged;
+        m_watcher.Renamed += ConfigureAndWatchHandler_OnRenamed;
 
         // Begin watching.
         m_watcher.EnableRaisingEvents = true;
 
         // Create the timer that will be used to deliver events. Set as disabled
-        m_timer = new Timer(new TimerCallback(OnWatchedFileChange), null, Timeout.Infinite, Timeout.Infinite);
+        m_timer = new Timer(OnWatchedFileChange, state: null, Timeout.Infinite, Timeout.Infinite);
       }
 
       /// <summary>
@@ -847,7 +816,7 @@
       /// </remarks>
       private void ConfigureAndWatchHandler_OnChanged(object source, FileSystemEventArgs e)
       {
-        LogLog.Debug(declaringType, "ConfigureAndWatchHandler: " + e.ChangeType + " [" + m_configFile.FullName + "]");
+        LogLog.Debug(declaringType, $"ConfigureAndWatchHandler: {e.ChangeType} [{m_configFile.FullName}]");
 
         // Deliver the event in TimeoutMillis time
         // timer will fire only once
@@ -866,7 +835,7 @@
       /// </remarks>
       private void ConfigureAndWatchHandler_OnRenamed(object source, RenamedEventArgs e)
       {
-        LogLog.Debug(declaringType, "ConfigureAndWatchHandler: " + e.ChangeType + " [" + m_configFile.FullName + "]");
+        LogLog.Debug(declaringType, $"ConfigureAndWatchHandler: {e.ChangeType} [{m_configFile.FullName}]");
 
         // Deliver the event in TimeoutMillis time
         // timer will fire only once
@@ -894,10 +863,6 @@
       }
     }
 
-    #endregion ConfigureAndWatchHandler
-
-    #region Private Static Methods
-
     /// <summary>
     /// Configures the specified repository using a <c>log4net</c> element.
     /// </summary>
@@ -913,13 +878,13 @@
     /// to load the configuration from an <see cref="XmlElement"/>.
     /// </para>
     /// </remarks>
-    private static void InternalConfigureFromXml(ILoggerRepository repository, XmlElement element)
+    private static void InternalConfigureFromXml(ILoggerRepository? repository, XmlElement? element)
     {
-      if (element == null)
+      if (element is null)
       {
         LogLog.Error(declaringType, "ConfigureFromXml called with null 'element' parameter");
       }
-      else if (repository == null)
+      else if (repository is null)
       {
         LogLog.Error(declaringType, "ConfigureFromXml called with null 'repository' parameter");
       }
@@ -927,8 +892,7 @@
       {
         LogLog.Debug(declaringType, "Configuring Repository [" + repository.Name + "]");
 
-        IXmlRepositoryConfigurator configurableRepository = repository as IXmlRepositoryConfigurator;
-        if (configurableRepository == null)
+        if (repository is not IXmlRepositoryConfigurator configurableRepository)
         {
           LogLog.Warn(declaringType, "Repository [" + repository + "] does not support the XmlConfigurator");
         }
@@ -946,16 +910,12 @@
       }
     }
 
-    #endregion Private Static Methods
-
-    #region Private Static Fields
-
     /// <summary>
     /// Maps repository names to ConfigAndWatchHandler instances to allow a particular
     /// ConfigAndWatchHandler to dispose of its FileSystemWatcher when a repository is 
     /// reconfigured.
     /// </summary>
-    private static readonly Hashtable m_repositoryName2ConfigAndWatchHandler = new Hashtable();
+    private static readonly ConcurrentDictionary<string, ConfigureAndWatchHandler> m_repositoryName2ConfigAndWatchHandler = new(StringComparer.Ordinal);
 
     /// <summary>
     /// The fully qualified type of the XmlConfigurator class.
@@ -965,7 +925,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(XmlConfigurator);
-
-    #endregion Private Static Fields
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Config/XmlConfiguratorAttribute.cs b/src/log4net/Config/XmlConfiguratorAttribute.cs
index b6d6c24..3579cda 100644
--- a/src/log4net/Config/XmlConfiguratorAttribute.cs
+++ b/src/log4net/Config/XmlConfiguratorAttribute.cs
@@ -18,7 +18,7 @@
 #endregion
 
 using System;
-using System.Collections;
+using System.Collections.Generic;
 using System.Reflection;
 using System.IO;
 
@@ -26,6 +26,8 @@
 using log4net.Repository;
 using log4net.Repository.Hierarchy;
 
+#nullable enable
+
 namespace log4net.Config
 {
   /// <summary>
@@ -95,8 +97,6 @@
     {
     }
 
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets or sets the filename of the configuration file.
     /// </summary>
@@ -113,18 +113,11 @@
     /// The <see cref="ConfigFile"/> takes priority over the <see cref="ConfigFileExtension"/>.
     /// </para>
     /// </remarks>
-    public string ConfigFile
-    {
-      get { return m_configFile; }
-      set { m_configFile = value; }
-    }
+    public string? ConfigFile { get; set; }
 
     /// <summary>
     /// Gets or sets the extension of the configuration file.
     /// </summary>
-    /// <value>
-    /// The extension of the configuration file.
-    /// </value>
     /// <remarks>
     /// <para>
     /// If specified this is the extension for the configuration file.
@@ -141,11 +134,7 @@
     /// The <see cref="ConfigFile"/> takes priority over the <see cref="ConfigFileExtension"/>.
     /// </para>
     /// </remarks>
-    public string ConfigFileExtension
-    {
-      get { return m_configFileExtension; }
-      set { m_configFileExtension = value; }
-    }
+    public string? ConfigFileExtension { get; set; }
 
     /// <summary>
     /// Gets or sets a value indicating whether to watch the configuration file.
@@ -169,15 +158,7 @@
     /// Watching configuration is not supported on the SSCLI.
     /// </note>
     /// </remarks>
-    public bool Watch
-    {
-      get { return m_configureAndWatch; }
-      set { m_configureAndWatch = value; }
-    }
-
-    #endregion Public Instance Properties
-
-    #region Override ConfiguratorAttribute
+    public bool Watch { get; set; }
 
     /// <summary>
     /// Configures the <see cref="ILoggerRepository"/> for the specified assembly.
@@ -195,11 +176,11 @@
     /// <exception cref="ArgumentOutOfRangeException">The <paramref name="targetRepository" /> does not extend <see cref="Hierarchy"/>.</exception>
     public override void Configure(Assembly sourceAssembly, ILoggerRepository targetRepository)
     {
-      IList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
-        string applicationBaseDirectory = null;
+        string? applicationBaseDirectory = null;
         try
         {
           applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
@@ -210,21 +191,19 @@
           // and the application does not have PathDiscovery permission
         }
 
-        if (applicationBaseDirectory == null || (new Uri(applicationBaseDirectory)).IsFile)
+        if (applicationBaseDirectory is null || (new Uri(applicationBaseDirectory)).IsFile)
         {
           ConfigureFromFile(sourceAssembly, targetRepository);
         }
         else
         {
-          ConfigureFromUri(sourceAssembly, targetRepository);
+          ConfigureFromUri(targetRepository);
         }
       }
 
       targetRepository.ConfigurationMessages = configurationMessages;
     }
 
-    #endregion
-
     /// <summary>
     /// Attempt to load configuration from the local file system
     /// </summary>
@@ -233,12 +212,12 @@
     private void ConfigureFromFile(Assembly sourceAssembly, ILoggerRepository targetRepository)
     {
       // Work out the full path to the config file
-      string fullPath2ConfigFile = null;
+      string? fullPath2ConfigFile = null;
 
       // Select the config file
-      if (m_configFile == null || m_configFile.Length == 0)
+      if (string.IsNullOrEmpty(ConfigFile))
       {
-        if (m_configFileExtension == null || m_configFileExtension.Length == 0)
+        if (string.IsNullOrEmpty(ConfigFileExtension))
         {
           // Use the default .config file for the AppDomain
           try
@@ -253,12 +232,12 @@
         else
         {
           // Force the extension to start with a '.'
-          if (m_configFileExtension[0] != '.')
+          if (ConfigFileExtension![0] != '.')
           {
-            m_configFileExtension = "." + m_configFileExtension;
+            ConfigFileExtension = '.' + ConfigFileExtension;
           }
 
-          string applicationBaseDirectory = null;
+          string? applicationBaseDirectory = null;
           try
           {
             applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
@@ -268,36 +247,36 @@
             LogLog.Error(declaringType, "Exception getting ApplicationBaseDirectory. Must be able to resolve ApplicationBaseDirectory and AssemblyFileName when ConfigFileExtension property is set.", ex);
           }
 
-          if (applicationBaseDirectory != null)
+          if (applicationBaseDirectory is not null)
           {
-            fullPath2ConfigFile = Path.Combine(applicationBaseDirectory, SystemInfo.AssemblyFileName(sourceAssembly) + m_configFileExtension);
+            fullPath2ConfigFile = Path.Combine(applicationBaseDirectory, SystemInfo.AssemblyFileName(sourceAssembly) + ConfigFileExtension);
           }
         }
       }
       else
       {
-        string applicationBaseDirectory = null;
+        string? applicationBaseDirectory = null;
         try
         {
           applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
         }
         catch (Exception ex)
         {
-          LogLog.Warn(declaringType, "Exception getting ApplicationBaseDirectory. ConfigFile property path [" + m_configFile + "] will be treated as an absolute path.", ex);
+          LogLog.Warn(declaringType, $"Exception getting ApplicationBaseDirectory. ConfigFile property path [{ConfigFile}] will be treated as an absolute path.", ex);
         }
 
-        if (applicationBaseDirectory != null)
+        if (applicationBaseDirectory is not null)
         {
           // Just the base dir + the config file
-          fullPath2ConfigFile = Path.Combine(applicationBaseDirectory, m_configFile);
+          fullPath2ConfigFile = Path.Combine(applicationBaseDirectory, ConfigFile!);
         }
         else
         {
-          fullPath2ConfigFile = m_configFile;
+          fullPath2ConfigFile = ConfigFile;
         }
       }
 
-      if (fullPath2ConfigFile != null)
+      if (fullPath2ConfigFile is not null)
       {
         ConfigureFromFile(targetRepository, new FileInfo(fullPath2ConfigFile));
       }
@@ -311,7 +290,7 @@
     private void ConfigureFromFile(ILoggerRepository targetRepository, FileInfo configFile)
     {
       // Do we configure just once or do we configure and then watch?
-      if (m_configureAndWatch)
+      if (Watch)
       {
         XmlConfigurator.ConfigureAndWatch(targetRepository, configFile);
       }
@@ -324,19 +303,18 @@
     /// <summary>
     /// Attempt to load configuration from a URI
     /// </summary>
-    /// <param name="sourceAssembly">The assembly that this attribute was defined on.</param>
     /// <param name="targetRepository">The repository to configure.</param>
-    private void ConfigureFromUri(Assembly sourceAssembly, ILoggerRepository targetRepository)
+    private void ConfigureFromUri(ILoggerRepository targetRepository)
     {
       // Work out the full path to the config file
-      Uri fullPath2ConfigFile = null;
+      Uri? fullPath2ConfigFile = null;
 
       // Select the config file
-      if (m_configFile == null || m_configFile.Length == 0)
+      if (string.IsNullOrEmpty(ConfigFile))
       {
-        if (m_configFileExtension == null || m_configFileExtension.Length == 0)
+        if (string.IsNullOrEmpty(ConfigFileExtension))
         {
-          string systemConfigFilePath = null;
+          string? systemConfigFilePath = null;
           try
           {
             systemConfigFilePath = SystemInfo.ConfigurationFileLocation;
@@ -346,23 +324,21 @@
             LogLog.Error(declaringType, "XmlConfiguratorAttribute: Exception getting ConfigurationFileLocation. Must be able to resolve ConfigurationFileLocation when ConfigFile and ConfigFileExtension properties are not set.", ex);
           }
 
-          if (systemConfigFilePath != null)
+          if (systemConfigFilePath is not null)
           {
-            Uri systemConfigFileUri = new Uri(systemConfigFilePath);
-
             // Use the default .config file for the AppDomain
-            fullPath2ConfigFile = systemConfigFileUri;
+            fullPath2ConfigFile = new Uri(systemConfigFilePath);
           }
         }
         else
         {
           // Force the extension to start with a '.'
-          if (m_configFileExtension[0] != '.')
+          if (ConfigFileExtension![0] != '.')
           {
-            m_configFileExtension = "." + m_configFileExtension;
+            ConfigFileExtension = '.' + ConfigFileExtension;
           }
 
-          string systemConfigFilePath = null;
+          string? systemConfigFilePath = null;
           try
           {
             systemConfigFilePath = SystemInfo.ConfigurationFileLocation;
@@ -372,18 +348,18 @@
             LogLog.Error(declaringType, "XmlConfiguratorAttribute: Exception getting ConfigurationFileLocation. Must be able to resolve ConfigurationFileLocation when the ConfigFile property are not set.", ex);
           }
 
-          if (systemConfigFilePath != null)
+          if (systemConfigFilePath is not null)
           {
-            UriBuilder builder = new UriBuilder(new Uri(systemConfigFilePath));
+            var builder = new UriBuilder(new Uri(systemConfigFilePath));
 
             // Remove the current extension from the systemConfigFileUri path
             string path = builder.Path;
-            int startOfExtension = path.LastIndexOf(".");
+            int startOfExtension = path.LastIndexOf('.');
             if (startOfExtension >= 0)
             {
               path = path.Substring(0, startOfExtension);
             }
-            path += m_configFileExtension;
+            path += ConfigFileExtension;
 
             builder.Path = path;
             fullPath2ConfigFile = builder.Uri;
@@ -392,38 +368,38 @@
       }
       else
       {
-        string applicationBaseDirectory = null;
+        string? applicationBaseDirectory = null;
         try
         {
           applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
         }
         catch (Exception ex)
         {
-          LogLog.Warn(declaringType, "Exception getting ApplicationBaseDirectory. ConfigFile property path [" + m_configFile + "] will be treated as an absolute URI.", ex);
+          LogLog.Warn(declaringType, $"Exception getting ApplicationBaseDirectory. ConfigFile property path [{ConfigFile}] will be treated as an absolute URI.", ex);
         }
 
-        if (applicationBaseDirectory != null)
+        if (applicationBaseDirectory is not null)
         {
           // Just the base dir + the config file
-          fullPath2ConfigFile = new Uri(new Uri(applicationBaseDirectory), m_configFile);
+          fullPath2ConfigFile = new Uri(new Uri(applicationBaseDirectory), ConfigFile!);
         }
         else
         {
-          fullPath2ConfigFile = new Uri(m_configFile);
+          fullPath2ConfigFile = new Uri(ConfigFile!);
         }
       }
 
-      if (fullPath2ConfigFile != null)
+      if (fullPath2ConfigFile is not null)
       {
         if (fullPath2ConfigFile.IsFile)
         {
-          // The m_configFile could be an absolute local path, therefore we have to be
+          // The ConfigFile could be an absolute local path, therefore we have to be
           // prepared to switch back to using FileInfos here
           ConfigureFromFile(targetRepository, new FileInfo(fullPath2ConfigFile.LocalPath));
         }
         else
         {
-          if (m_configureAndWatch)
+          if (Watch)
           {
             LogLog.Warn(declaringType, "XmlConfiguratorAttribute: Unable to watch config file loaded from a URI");
           }
@@ -432,16 +408,6 @@
       }
     }
 
-    #region Private Instance Fields
-
-    private string m_configFile = null;
-    private string m_configFileExtension = null;
-    private bool m_configureAndWatch = false;
-
-    #endregion Private Instance Fields
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the XmlConfiguratorAttribute class.
     /// </summary>
@@ -450,7 +416,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(XmlConfiguratorAttribute);
-
-    #endregion Private Static Fields
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Core/IFixingRequired.cs b/src/log4net/Core/IFixingRequired.cs
index 58e84cf..2251135 100644
--- a/src/log4net/Core/IFixingRequired.cs
+++ b/src/log4net/Core/IFixingRequired.cs
@@ -17,8 +17,6 @@
 //
 #endregion
 
-using System;
-
 namespace log4net.Core
 {
   /// <summary>
@@ -53,6 +51,6 @@
     /// and logged from any thread with identical results.
     /// </para>
     /// </remarks>
-    object GetFixedObject();
+    object? GetFixedObject();
   }
 }
diff --git a/src/log4net/Core/ILoggerWrapper.cs b/src/log4net/Core/ILoggerWrapper.cs
index fefbd20..3edde25 100644
--- a/src/log4net/Core/ILoggerWrapper.cs
+++ b/src/log4net/Core/ILoggerWrapper.cs
@@ -17,12 +17,6 @@
 //
 #endregion
 
-using System;
-
-using log4net;
-using log4net.Core;
-using log4net.Repository;
-
 namespace log4net.Core
 {
   /// <summary>
@@ -40,18 +34,14 @@
   public interface ILoggerWrapper
   {
     /// <summary>
-    /// Get the implementation behind this wrapper object.
+    /// Gets the <see cref="ILogger"/> object that implements this object.
     /// </summary>
     /// <value>
-    /// The <see cref="ILogger"/> object that in implementing this object.
     /// </value>
     /// <remarks>
     /// <para>
-    /// The <see cref="ILogger"/> object that in implementing this
-    /// object. The <c>Logger</c> object may not 
-    /// be the same object as this object because of logger decorators.
-    /// This gets the actual underlying objects that is used to process
-    /// the log events.
+    /// The <c>Logger</c> object may not  be the same object as this object because of logger decorators.
+    /// This gets the actual underlying objects that is used to process the log events.
     /// </para>
     /// </remarks>
     ILogger Logger { get; }
diff --git a/src/log4net/Core/IOptionHandler.cs b/src/log4net/Core/IOptionHandler.cs
index 6d73ee1..aadf71c 100644
--- a/src/log4net/Core/IOptionHandler.cs
+++ b/src/log4net/Core/IOptionHandler.cs
@@ -17,8 +17,6 @@
 //
 #endregion
 
-using System;
-
 namespace log4net.Core
 {
   /// <summary>
diff --git a/src/log4net/Core/LocationInfo.cs b/src/log4net/Core/LocationInfo.cs
index 5e8295e..a05adca 100644
--- a/src/log4net/Core/LocationInfo.cs
+++ b/src/log4net/Core/LocationInfo.cs
@@ -59,8 +59,6 @@
   [Serializable]
   public class LocationInfo
   {
-    #region Public Instance Constructors
-
     /// <summary>
     /// Constructor
     /// </summary>
@@ -72,16 +70,16 @@
     /// class based on the current thread.
     /// </para>
     /// </remarks>
-    public LocationInfo(Type callerStackBoundaryDeclaringType) 
+    public LocationInfo(Type? callerStackBoundaryDeclaringType) 
     {
       // Initialize all fields
-      m_className = NA;
-      m_fileName = NA;
-      m_lineNumber = NA;
-      m_methodName = NA;
-      m_fullInfo = NA;
+      ClassName = NA;
+      FileName = NA;
+      LineNumber = NA;
+      MethodName = NA;
+      FullInfo = NA;
 
-      if (callerStackBoundaryDeclaringType != null)
+      if (callerStackBoundaryDeclaringType is not null)
       {
         try
         {
@@ -114,35 +112,35 @@
           {
             // take into account the frames we skip above
             int adjustedFrameCount = st.FrameCount - frameIndex;
-                        ArrayList stackFramesList = new ArrayList(adjustedFrameCount);
-            m_stackFrames = new StackFrameItem[adjustedFrameCount];
+            ArrayList stackFramesList = new ArrayList(adjustedFrameCount);
+            StackFrames = new StackFrameItem[adjustedFrameCount];
             for (int i=frameIndex; i < st.FrameCount; i++) 
             {
               stackFramesList.Add(new StackFrameItem(st.GetFrame(i)));
             }
                         
-            stackFramesList.CopyTo(m_stackFrames, 0);
+            stackFramesList.CopyTo(StackFrames, 0);
             
             // now frameIndex is the first 'user' caller frame
             StackFrame locationFrame = st.GetFrame(frameIndex);
 
-            if (locationFrame != null)
+            if (locationFrame is not null)
             {
               System.Reflection.MethodBase method = locationFrame.GetMethod();
 
-              if (method != null)
+              if (method is not null)
               {
-                m_methodName =  method.Name;
-                if (method.DeclaringType != null)
+                MethodName =  method.Name;
+                if (method.DeclaringType is not null)
                 {
-                  m_className = method.DeclaringType.FullName;
+                  ClassName = method.DeclaringType.FullName;
                 }
               }
-              m_fileName = locationFrame.GetFileName();
-              m_lineNumber = locationFrame.GetFileLineNumber().ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
+              FileName = locationFrame.GetFileName();
+              LineNumber = locationFrame.GetFileLineNumber().ToString(System.Globalization.NumberFormatInfo.InvariantInfo);
 
               // Combine all location info
-              m_fullInfo =  m_className + '.' + m_methodName + '(' + m_fileName + ':' + m_lineNumber + ')';
+              FullInfo =  ClassName + '.' + MethodName + '(' + FileName + ':' + LineNumber + ')';
             }
           }
         }
@@ -170,84 +168,33 @@
     /// </remarks>
     public LocationInfo(string className, string methodName, string fileName, string lineNumber)
     {
-      m_className = className;
-      m_fileName = fileName;
-      m_lineNumber = lineNumber;
-      m_methodName = methodName;
-      m_fullInfo = m_className + '.' + m_methodName + '(' + m_fileName + 
-        ':' + m_lineNumber + ')';
+      ClassName = className;
+      FileName = fileName;
+      LineNumber = lineNumber;
+      MethodName = methodName;
+      FullInfo = $"{ClassName}.{MethodName}({FileName}:{LineNumber})";
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets the fully qualified class name of the caller making the logging 
     /// request.
     /// </summary>
-    /// <value>
-    /// The fully qualified class name of the caller making the logging 
-    /// request.
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Gets the fully qualified class name of the caller making the logging 
-    /// request.
-    /// </para>
-    /// </remarks>
-    public string ClassName
-    {
-      get { return m_className; }
-    }
+    public string? ClassName { get; }
 
     /// <summary>
     /// Gets the file name of the caller.
     /// </summary>
-    /// <value>
-    /// The file name of the caller.
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Gets the file name of the caller.
-    /// </para>
-    /// </remarks>
-    public string FileName
-    {
-      get { return m_fileName; }
-    }
+    public string? FileName { get; }
 
     /// <summary>
     /// Gets the line number of the caller.
     /// </summary>
-    /// <value>
-    /// The line number of the caller.
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Gets the line number of the caller.
-    /// </para>
-    /// </remarks>
-    public string LineNumber
-    {
-      get { return m_lineNumber; }
-    }
+    public string LineNumber { get; }
 
     /// <summary>
     /// Gets the method name of the caller.
     /// </summary>
-    /// <value>
-    /// The method name of the caller.
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Gets the method name of the caller.
-    /// </para>
-    /// </remarks>
-    public string MethodName
-    {
-      get { return m_methodName; }
-    }
+    public string MethodName { get; }
 
     /// <summary>
     /// Gets all available caller information
@@ -262,42 +209,21 @@
     /// <c>fully.qualified.classname.of.caller.methodName(Filename:line)</c>
     /// </para>
     /// </remarks>
-    public string FullInfo
-    {
-      get { return m_fullInfo; }
-    }
-    
+    public string FullInfo { get; }
+
     /// <summary>
     /// Gets the stack frames from the stack trace of the caller making the log request
     /// </summary>
-    public StackFrameItem[] StackFrames
-    {
-      get { return m_stackFrames; }
-    }
+    public StackFrameItem[]? StackFrames { get; }
 
-    #endregion Public Instance Properties
-
-    #region Private Instance Fields
-
-    private readonly string m_className;
-    private readonly string m_fileName;
-    private readonly string m_lineNumber;
-    private readonly string m_methodName;
-    private readonly string m_fullInfo;
-    private readonly StackFrameItem[] m_stackFrames;
-
-    #endregion Private Instance Fields
-
-    #region Private Static Fields
-
-      /// <summary>
-      /// The fully qualified type of the LocationInfo class.
-      /// </summary>
-      /// <remarks>
-      /// Used by the internal logger to record the Type of the
-      /// log message.
-      /// </remarks>
-      private static readonly Type declaringType = typeof(LocationInfo);
+    /// <summary>
+    /// The fully qualified type of the LocationInfo class.
+    /// </summary>
+    /// <remarks>
+    /// Used by the internal logger to record the Type of the
+    /// log message.
+    /// </remarks>
+    private static readonly Type declaringType = typeof(LocationInfo);
 
     /// <summary>
     /// When location information is not available the constant
@@ -305,7 +231,5 @@
     /// constant is <b>?</b>.
     /// </summary>
     private const string NA = "?";
-
-    #endregion Private Static Fields
   }
 }
diff --git a/src/log4net/Core/LoggerManager.cs b/src/log4net/Core/LoggerManager.cs
index e09b422..0a9a691 100644
--- a/src/log4net/Core/LoggerManager.cs
+++ b/src/log4net/Core/LoggerManager.cs
@@ -18,12 +18,13 @@
 #endregion
 
 using System;
-using System.Runtime.InteropServices;
 using System.Reflection;
-
+using System.Text;
 using log4net.Util;
 using log4net.Repository;
 
+#nullable enable
+
 namespace log4net.Core
 {
   /// <summary>
@@ -34,7 +35,7 @@
   /// Static manager that controls the creation of repositories
   /// </para>
   /// <para>
-  /// This class is used by the wrapper managers (e.g. <see cref="log4net.LogManager"/>)
+  /// This class is used by the wrapper managers (e.g. <see cref="LogManager"/>)
   /// to provide access to the <see cref="ILogger"/> objects.
   /// </para>
   /// <para>
@@ -47,26 +48,8 @@
   /// </remarks>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  public sealed class LoggerManager
+  public static class LoggerManager
   {
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Private constructor to prevent instances. Only static methods should be used.
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Private constructor to prevent instances. Only static methods should be used.
-    /// </para>
-    /// </remarks>
-    private LoggerManager()
-    {
-    }
-
-    #endregion Private Instance Constructors
-
-    #region Static Constructor
-
     /// <summary>
     /// Hook the shutdown event
     /// </summary>
@@ -74,7 +57,7 @@
     /// <para>
     /// On the full .NET runtime, the static constructor hooks up the 
     /// <c>AppDomain.ProcessExit</c> and <c>AppDomain.DomainUnload</c>> events. 
-    /// These are used to shutdown the log4net system as the application exits.
+    /// These are used to shut down the log4net system as the application exits.
     /// </para>
     /// </remarks>
     static LoggerManager()
@@ -99,48 +82,45 @@
 
       // Set the default repository selector
       // Look for the RepositorySelector type specified in the AppSettings 'log4net.RepositorySelector'
-      string appRepositorySelectorTypeName = SystemInfo.GetAppSetting("log4net.RepositorySelector");
-      if (appRepositorySelectorTypeName != null && appRepositorySelectorTypeName.Length > 0)
+      string? appRepositorySelectorTypeName = SystemInfo.GetAppSetting("log4net.RepositorySelector");
+      if (!string.IsNullOrEmpty(appRepositorySelectorTypeName))
       {
         // Resolve the config string into a Type
-        Type appRepositorySelectorType = null;
+        Type? appRepositorySelectorType = null;
         try
         {
-          appRepositorySelectorType = SystemInfo.GetTypeFromString(appRepositorySelectorTypeName, false, true);
+          appRepositorySelectorType = SystemInfo.GetTypeFromString(appRepositorySelectorTypeName!, false, true);
         }
         catch (Exception ex)
         {
-          LogLog.Error(declaringType, "Exception while resolving RepositorySelector Type [" + appRepositorySelectorTypeName + "]", ex);
+          LogLog.Error(declaringType, $"Exception while resolving RepositorySelector Type [{appRepositorySelectorTypeName}]", ex);
         }
 
-        if (appRepositorySelectorType != null)
+        if (appRepositorySelectorType is not null)
         {
           // Create an instance of the RepositorySelectorType
-          object appRepositorySelectorObj = null;
+          object? appRepositorySelectorObj = null;
           try
           {
             appRepositorySelectorObj = Activator.CreateInstance(appRepositorySelectorType);
           }
           catch (Exception ex)
           {
-            LogLog.Error(declaringType, "Exception while creating RepositorySelector [" + appRepositorySelectorType.FullName + "]", ex);
+            LogLog.Error(declaringType, $"Exception while creating RepositorySelector [{appRepositorySelectorType.FullName}]", ex);
           }
 
-          if (appRepositorySelectorObj != null && appRepositorySelectorObj is IRepositorySelector)
+          if (appRepositorySelectorObj is IRepositorySelector sel)
           {
-            RepositorySelector = (IRepositorySelector)appRepositorySelectorObj;
+            RepositorySelector = sel;
           }
           else
           {
-            LogLog.Error(declaringType, "RepositorySelector Type [" + appRepositorySelectorType.FullName + "] is not an IRepositorySelector");
+            LogLog.Error(declaringType, $"RepositorySelector Type [{appRepositorySelectorType.FullName}] is not an IRepositorySelector");
           }
         }
       }
       // Create the DefaultRepositorySelector if not configured above 
-      if (RepositorySelector == null)
-      {
-        RepositorySelector = new DefaultRepositorySelector(typeof(log4net.Repository.Hierarchy.Hierarchy));
-      }
+      RepositorySelector ??= new DefaultRepositorySelector(typeof(Repository.Hierarchy.Hierarchy));
     }
 
     /// <summary>
@@ -158,16 +138,12 @@
     private static void RegisterAppDomainEvents()
     {
       // ProcessExit seems to be fired if we are part of the default domain
-      AppDomain.CurrentDomain.ProcessExit += new EventHandler(OnProcessExit);
+      AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
 
       // Otherwise DomainUnload is fired
-      AppDomain.CurrentDomain.DomainUnload += new EventHandler(OnDomainUnload);
+      AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
     }
 
-    #endregion Static Constructor
-
-    #region Public Static Methods
-
     /// <summary>
     /// Return the default <see cref="ILoggerRepository"/> instance.
     /// </summary>
@@ -188,7 +164,7 @@
     /// <summary>
     /// Returns the default <see cref="ILoggerRepository"/> instance.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <returns>The default <see cref="ILoggerRepository"/> instance.</returns>
     [Obsolete("Use GetRepository instead of GetLoggerRepository")]
     public static ILoggerRepository GetLoggerRepository(Assembly repositoryAssembly)
@@ -209,9 +185,9 @@
     /// </remarks>
     public static ILoggerRepository GetRepository(string repository)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
       return RepositorySelector.GetRepository(repository);
     }
@@ -219,7 +195,7 @@
     /// <summary>
     /// Returns the default <see cref="ILoggerRepository"/> instance.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <returns>The default <see cref="ILoggerRepository"/> instance.</returns>
     /// <remarks>
     /// <para>
@@ -228,9 +204,9 @@
     /// </remarks>
     public static ILoggerRepository GetRepository(Assembly repositoryAssembly)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
       return RepositorySelector.GetRepository(repositoryAssembly);
     }
@@ -251,15 +227,15 @@
     /// <c>null</c>.
     /// </para>
     /// </remarks>
-    public static ILogger Exists(string repository, string name)
+    public static ILogger? Exists(string repository, string name)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
-      if (name == null)
+      if (name is null)
       {
-        throw new ArgumentNullException("name");
+        throw new ArgumentNullException(nameof(name));
       }
       return RepositorySelector.GetRepository(repository).Exists(name);
     }
@@ -267,7 +243,7 @@
     /// <summary>
     /// Returns the named logger if it exists.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <param name="name">The fully qualified logger name to look for.</param>
     /// <returns>
     /// The logger found, or <c>null</c> if the named logger does not exist in the
@@ -280,15 +256,15 @@
     /// <c>null</c>.
     /// </para>
     /// </remarks>
-    public static ILogger Exists(Assembly repositoryAssembly, string name)
+    public static ILogger? Exists(Assembly repositoryAssembly, string name)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
-      if (name == null)
+      if (name is null)
       {
-        throw new ArgumentNullException("name");
+        throw new ArgumentNullException(nameof(name));
       }
       return RepositorySelector.GetRepository(repositoryAssembly).Exists(name);
     }
@@ -305,9 +281,9 @@
     /// </remarks>
     public static ILogger[] GetCurrentLoggers(string repository)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
       return RepositorySelector.GetRepository(repository).GetCurrentLoggers();
     }
@@ -315,7 +291,7 @@
     /// <summary>
     /// Returns all the currently defined loggers in the specified assembly's repository.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <returns>All the defined loggers.</returns>
     /// <remarks>
     /// <para>
@@ -324,9 +300,9 @@
     /// </remarks>
     public static ILogger[] GetCurrentLoggers(Assembly repositoryAssembly)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
       return RepositorySelector.GetRepository(repositoryAssembly).GetCurrentLoggers();
     }
@@ -352,13 +328,13 @@
     /// </remarks>
     public static ILogger GetLogger(string repository, string name)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
-      if (name == null)
+      if (name is null)
       {
-        throw new ArgumentNullException("name");
+        throw new ArgumentNullException(nameof(name));
       }
       return RepositorySelector.GetRepository(repository).GetLogger(name);
     }
@@ -366,7 +342,7 @@
     /// <summary>
     /// Retrieves or creates a named logger.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <param name="name">The name of the logger to retrieve.</param>
     /// <returns>The logger with the name specified.</returns>
     /// <remarks>
@@ -384,13 +360,13 @@
     /// </remarks>
     public static ILogger GetLogger(Assembly repositoryAssembly, string name)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
-      if (name == null)
+      if (name is null)
       {
-        throw new ArgumentNullException("name");
+        throw new ArgumentNullException(nameof(name));
       }
       return RepositorySelector.GetRepository(repositoryAssembly).GetLogger(name);
     }
@@ -408,13 +384,17 @@
     /// </remarks>
     public static ILogger GetLogger(string repository, Type type)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
-      if (type == null)
+      if (type is null)
       {
-        throw new ArgumentNullException("type");
+        throw new ArgumentNullException(nameof(type));
+      }
+      if (type.FullName is null)
+      {
+        throw new ArgumentException($"Type {type} does not have a full name", nameof(type));
       }
       return RepositorySelector.GetRepository(repository).GetLogger(type.FullName);
     }
@@ -422,7 +402,7 @@
     /// <summary>
     /// Shorthand for <see cref="M:LogManager.GetLogger(string)"/>.
     /// </summary>
-    /// <param name="repositoryAssembly">the assembly to use to lookup the repository</param>
+    /// <param name="repositoryAssembly">the assembly to use to look up the repository</param>
     /// <param name="type">The <paramref name="type"/> of which the fullname will be used as the name of the logger to retrieve.</param>
     /// <returns>The logger with the name specified.</returns>
     /// <remarks>
@@ -432,13 +412,17 @@
     /// </remarks>
     public static ILogger GetLogger(Assembly repositoryAssembly, Type type)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
-      if (type == null)
+      if (type is null)
       {
-        throw new ArgumentNullException("type");
+        throw new ArgumentNullException(nameof(type));
+      }
+      if (type.FullName is null)
+      {
+        throw new ArgumentException($"Type {type} does not have a full name", nameof(type));
       }
       return RepositorySelector.GetRepository(repositoryAssembly).GetLogger(type.FullName);
     }
@@ -458,7 +442,7 @@
     /// </para>
     /// <para>
     /// The <c>shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
@@ -474,7 +458,7 @@
     /// <summary>
     /// Shuts down the repository for the repository specified.
     /// </summary>
-    /// <param name="repository">The repository to shutdown.</param>
+    /// <param name="repository">The repository to shut down.</param>
     /// <remarks>
     /// <para>
     /// Calling this method will <b>safely</b> close and remove all
@@ -487,16 +471,16 @@
     /// </para>
     /// <para>
     /// The <c>shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
     /// </remarks>
     public static void ShutdownRepository(string repository)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
       RepositorySelector.GetRepository(repository).Shutdown();
     }
@@ -504,7 +488,7 @@
     /// <summary>
     /// Shuts down the repository for the repository specified.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <remarks>
     /// <para>
     /// Calling this method will <b>safely</b> close and remove all
@@ -518,16 +502,16 @@
     /// </para>
     /// <para>
     /// The <c>shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
     /// </remarks>
     public static void ShutdownRepository(Assembly repositoryAssembly)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
       RepositorySelector.GetRepository(repositoryAssembly).Shutdown();
     }
@@ -548,9 +532,9 @@
     /// </remarks>
     public static void ResetConfiguration(string repository)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
       RepositorySelector.GetRepository(repository).ResetConfiguration();
     }
@@ -558,7 +542,7 @@
     /// <summary>
     /// Resets all values contained in this repository instance to their defaults.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository to reset.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository to reset.</param>
     /// <remarks>
     /// <para>
     /// Resets all values contained in the repository instance to their
@@ -571,9 +555,9 @@
     /// </remarks>
     public static void ResetConfiguration(Assembly repositoryAssembly)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
       RepositorySelector.GetRepository(repositoryAssembly).ResetConfiguration();
     }
@@ -621,9 +605,9 @@
     /// <exception cref="LogException">The specified repository already exists.</exception>
     public static ILoggerRepository CreateRepository(string repository)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
       return RepositorySelector.CreateRepository(repository, null);
     }
@@ -669,13 +653,13 @@
     /// <exception cref="LogException">The specified repository already exists.</exception>
     public static ILoggerRepository CreateRepository(string repository, Type repositoryType)
     {
-      if (repository == null)
+      if (repository is null)
       {
-        throw new ArgumentNullException("repository");
+        throw new ArgumentNullException(nameof(repository));
       }
-      if (repositoryType == null)
+      if (repositoryType is null)
       {
-        throw new ArgumentNullException("repositoryType");
+        throw new ArgumentNullException(nameof(repositoryType));
       }
       return RepositorySelector.CreateRepository(repository, repositoryType);
     }
@@ -721,13 +705,13 @@
     /// </remarks>
     public static ILoggerRepository CreateRepository(Assembly repositoryAssembly, Type repositoryType)
     {
-      if (repositoryAssembly == null)
+      if (repositoryAssembly is null)
       {
-        throw new ArgumentNullException("repositoryAssembly");
+        throw new ArgumentNullException(nameof(repositoryAssembly));
       }
-      if (repositoryType == null)
+      if (repositoryType is null)
       {
-        throw new ArgumentNullException("repositoryType");
+        throw new ArgumentNullException(nameof(repositoryType));
       }
       return RepositorySelector.CreateRepository(repositoryAssembly, repositoryType);
     }
@@ -769,23 +753,19 @@
     /// </remarks>
     public static IRepositorySelector RepositorySelector { get; set; }
 
-    #endregion Public Static Methods
-
-    #region Private Static Methods
-
     /// <summary>
     /// Internal method to get pertinent version info.
     /// </summary>
     /// <returns>A string of version info.</returns>
     private static string GetVersionInfo()
     {
-      System.Text.StringBuilder sb = new System.Text.StringBuilder();
+      var sb = new StringBuilder();
 
       Assembly myAssembly = Assembly.GetExecutingAssembly();
       sb.Append("log4net assembly [").Append(myAssembly.FullName).Append("]. ");
       sb.Append("Loaded from [").Append(SystemInfo.AssemblyLocationInfo(myAssembly)).Append("]. ");
-      sb.Append("(.NET Runtime [").Append(Environment.Version.ToString()).Append("]");
-      sb.Append(" on ").Append(Environment.OSVersion.ToString());
+      sb.Append("(.NET Runtime [").Append(Environment.Version).Append("]");
+      sb.Append(" on ").Append(Environment.OSVersion);
       sb.Append(")");
       return sb.ToString();
     }
@@ -826,10 +806,6 @@
       Shutdown();
     }
 
-    #endregion Private Static Methods
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the LoggerManager class.
     /// </summary>
@@ -838,7 +814,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(LoggerManager);
-
-    #endregion Private Static Fields
   }
 }
\ No newline at end of file
diff --git a/src/log4net/Core/LoggingEvent.cs b/src/log4net/Core/LoggingEvent.cs
index 4498704..908d617 100644
--- a/src/log4net/Core/LoggingEvent.cs
+++ b/src/log4net/Core/LoggingEvent.cs
@@ -1,5 +1,4 @@
 #region Apache License
-
 //
 // Licensed to the Apache Software Foundation (ASF) under one or more 
 // contributor license agreements. See the NOTICE file distributed with
@@ -16,34 +15,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-
 #endregion
 
 using System;
-using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Runtime.Serialization;
 using System.Security;
 using System.Security.Principal;
+using System.Threading;
 using log4net.Util;
 using log4net.Repository;
-using System.ComponentModel;
 
 namespace log4net.Core
 {
   /// <summary>
   /// Portable data structure used by <see cref="LoggingEvent"/>
   /// </summary>
-  /// <remarks>
-  /// <para>
-  /// Portable data structure used by <see cref="LoggingEvent"/>
-  /// </para>
-  /// </remarks>
   /// <author>Nicko Cadell</author>
   public struct LoggingEventData
   {
-    #region Public Instance Fields
-
     /// <summary>
     /// The logger name.
     /// </summary>
@@ -52,7 +44,7 @@
     /// The logger name.
     /// </para>
     /// </remarks>
-    public string LoggerName;
+    public string? LoggerName;
 
     /// <summary>
     /// Level of logging event.
@@ -64,7 +56,7 @@
     /// cannot be declared final either.
     /// </para>
     /// </remarks>
-    public Level Level;
+    public Level? Level;
 
     /// <summary>
     /// The application supplied message.
@@ -74,7 +66,7 @@
     /// The application supplied message of logging event.
     /// </para>
     /// </remarks>
-    public string Message;
+    public string? Message;
 
     /// <summary>
     /// The name of thread
@@ -84,7 +76,7 @@
     /// The name of thread in which this logging event was generated
     /// </para>
     /// </remarks>
-    public string ThreadName;
+    public string? ThreadName;
 
     /// <summary>
     /// Gets or sets the local time the event was logged
@@ -112,9 +104,11 @@
       get
       {
         if (TimeStamp != default && _timeStampUtc == default)
+        {
           // TimeStamp field has been set explicitly but TimeStampUtc hasn't
           // => use TimeStamp
           return TimeStamp.ToUniversalTime();
+        }
 
         return _timeStampUtc;
       }
@@ -137,7 +131,7 @@
     /// Location information for the caller.
     /// </para>
     /// </remarks>
-    public LocationInfo LocationInfo;
+    public LocationInfo? LocationInfo;
 
     /// <summary>
     /// String representation of the user
@@ -148,7 +142,7 @@
     /// like DOMAIN\username
     /// </para>
     /// </remarks>
-    public string UserName;
+    public string? UserName;
 
     /// <summary>
     /// String representation of the identity.
@@ -158,7 +152,7 @@
     /// String representation of the current thread's principal identity.
     /// </para>
     /// </remarks>
-    public string Identity;
+    public string? Identity;
 
     /// <summary>
     /// The string representation of the exception
@@ -168,7 +162,7 @@
     /// The string representation of the exception
     /// </para>
     /// </remarks>
-    public string ExceptionString;
+    public string? ExceptionString;
 
     /// <summary>
     /// String representation of the AppDomain.
@@ -178,7 +172,7 @@
     /// String representation of the AppDomain.
     /// </para>
     /// </remarks>
-    public string Domain;
+    public string? Domain;
 
     /// <summary>
     /// Additional event specific properties
@@ -190,9 +184,7 @@
     /// have a string key and an object value.
     /// </para>
     /// </remarks>
-    public PropertiesDictionary Properties;
-
-    #endregion Public Instance Fields
+    public PropertiesDictionary? Properties;
   }
 
   /// <summary>
@@ -211,11 +203,11 @@
   /// Some of the values in instances of <see cref="LoggingEvent"/>
   /// are considered volatile, that is the values are correct at the
   /// time the event is delivered to appenders, but will not be consistent
-  /// at any time afterwards. If an event is to be stored and then processed
+  /// at any time afterward. If an event is to be stored and then processed
   /// at a later time these volatile values must be fixed by calling
   /// <see cref="M:FixVolatileData()"/>. There is a performance penalty
   /// for incurred by calling <see cref="M:FixVolatileData()"/> but it
-  /// is essential to maintaining data consistency.
+  /// is essential to maintain data consistency.
   /// </para>
   /// </remarks>
   /// <author>Nicko Cadell</author>
@@ -227,8 +219,6 @@
   {
     private static readonly Type declaringType = typeof(LoggingEvent);
 
-    #region Public Instance Constructors
-
     /// <summary>
     /// Initializes a new instance of the <see cref="LoggingEvent" /> class
     /// from the supplied parameters.
@@ -251,17 +241,18 @@
     /// to create a logging event.
     /// </para>
     /// </remarks>
-    public LoggingEvent(Type callerStackBoundaryDeclaringType,
-        log4net.Repository.ILoggerRepository repository,
+    public LoggingEvent(
+        Type callerStackBoundaryDeclaringType,
+        ILoggerRepository? repository,
         string loggerName,
-        Level level,
+        Level? level,
         object message,
-        Exception exception)
+        Exception? exception)
     {
       m_callerStackBoundaryDeclaringType = callerStackBoundaryDeclaringType;
       m_message = message;
-      m_repository = repository;
-      m_thrownException = exception;
+      Repository = repository;
+      ExceptionObject = exception;
 
       m_data.LoggerName = loggerName;
       m_data.Level = level;
@@ -295,13 +286,14 @@
     /// will be captured from the environment if requested or fixed.
     /// </para>
     /// </remarks>
-    public LoggingEvent(Type callerStackBoundaryDeclaringType,
-        log4net.Repository.ILoggerRepository repository,
+    public LoggingEvent(
+        Type? callerStackBoundaryDeclaringType,
+        ILoggerRepository? repository,
         LoggingEventData data,
         FixFlags fixedData)
     {
       m_callerStackBoundaryDeclaringType = callerStackBoundaryDeclaringType;
-      m_repository = repository;
+      Repository = repository;
 
       m_data = data;
       m_fixFlags = fixedData;
@@ -331,9 +323,11 @@
     /// parameter and no other data should be captured from the environment.
     /// </para>
     /// </remarks>
-    public LoggingEvent(Type callerStackBoundaryDeclaringType,
-        log4net.Repository.ILoggerRepository repository,
-        LoggingEventData data) : this(callerStackBoundaryDeclaringType, repository, data, FixFlags.All)
+    public LoggingEvent(
+        Type? callerStackBoundaryDeclaringType,
+        ILoggerRepository? repository,
+        LoggingEventData data)
+      : this(callerStackBoundaryDeclaringType, repository, data, FixFlags.All)
     {
     }
 
@@ -362,10 +356,6 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Protected Instance Constructors
-
     /// <summary>
     /// Serialization constructor
     /// </summary>
@@ -403,10 +393,6 @@
       m_fixFlags = FixFlags.All;
     }
 
-    #endregion Protected Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets the time when the current process started.
     /// </summary>
@@ -428,10 +414,7 @@
     /// without the process start time being reset.
     /// </para>
     /// </remarks>
-    public static DateTime StartTime
-    {
-      get { return SystemInfo.ProcessStartTimeUtc.ToLocalTime(); }
-    }
+    public static DateTime StartTime => SystemInfo.ProcessStartTimeUtc.ToLocalTime();
 
     /// <summary>
     /// Gets the UTC time when the current process started.
@@ -451,10 +434,7 @@
     /// without the process start time being reset.
     /// </para>
     /// </remarks>
-    public static DateTime StartTimeUtc
-    {
-      get { return SystemInfo.ProcessStartTimeUtc; }
-    }
+    public static DateTime StartTimeUtc => SystemInfo.ProcessStartTimeUtc;
 
     /// <summary>
     /// Gets the <see cref="Level" /> of the logging event.
@@ -467,10 +447,7 @@
     /// Gets the <see cref="Level" /> of the logging event.
     /// </para>
     /// </remarks>
-    public Level Level
-    {
-      get { return m_data.Level; }
-    }
+    public Level? Level => m_data.Level;
 
     /// <summary>
     /// Gets the time of the logging event.
@@ -483,10 +460,7 @@
     /// The TimeStamp is stored in UTC and converted to the local time zone for this computer.
     /// </para>
     /// </remarks>
-    public DateTime TimeStamp
-    {
-      get { return m_data.TimeStampUtc.ToLocalTime(); }
-    }
+    public DateTime TimeStamp => m_data.TimeStampUtc.ToLocalTime();
 
     /// <summary>
     /// Gets UTC the time of the logging event.
@@ -494,10 +468,7 @@
     /// <value>
     /// The UTC time of the logging event.
     /// </value>
-    public DateTime TimeStampUtc
-    {
-      get { return m_data.TimeStampUtc; }
-    }
+    public DateTime TimeStampUtc => m_data.TimeStampUtc;
 
     /// <summary>
     /// Gets the name of the logger that logged the event.
@@ -510,10 +481,7 @@
     /// Gets the name of the logger that logged the event.
     /// </para>
     /// </remarks>
-    public string LoggerName
-    {
-      get { return m_data.LoggerName; }
-    }
+    public string? LoggerName => m_data.LoggerName;
 
     /// <summary>
     /// Gets the location information for this logging event.
@@ -531,11 +499,11 @@
     /// Release builds.
     /// </para>
     /// </remarks>
-    public LocationInfo LocationInformation
+    public LocationInfo? LocationInformation
     {
       get
       {
-        if (m_data.LocationInfo == null && this.m_cacheUpdatable)
+        if (m_data.LocationInfo is null && m_cacheUpdatable)
         {
           m_data.LocationInfo = new LocationInfo(m_callerStackBoundaryDeclaringType);
         }
@@ -564,7 +532,7 @@
     /// null will be returned.
     /// </para>
     /// </remarks>
-    public object MessageObject
+    public object? MessageObject
     {
       get { return m_message; }
       protected set { m_message = value; }
@@ -590,10 +558,7 @@
     /// null will be returned.
     /// </para>
     /// </remarks>
-    public Exception ExceptionObject
-    {
-      get { return m_thrownException; }
-    }
+    public Exception? ExceptionObject { get; }
 
     /// <summary>
     /// The <see cref="ILoggerRepository"/> that this event was created in.
@@ -603,20 +568,17 @@
     /// The <see cref="ILoggerRepository"/> that this event was created in.
     /// </para>
     /// </remarks>
-    public ILoggerRepository Repository
-    {
-      get { return m_repository; }
-    }
+    public ILoggerRepository? Repository { get; private set; }
 
     /// <summary>
     /// Ensure that the repository is set.
     /// </summary>
     /// <param name="repository">the value for the repository</param>
-    internal void EnsureRepository(ILoggerRepository repository)
+    internal void EnsureRepository(ILoggerRepository? repository)
     {
-      if (repository != null)
+      if (repository is not null)
       {
-        m_repository = repository;
+        Repository = repository;
       }
     }
 
@@ -631,23 +593,23 @@
     /// The collected information is cached for future use.
     /// </para>
     /// </remarks>
-    public virtual string RenderedMessage
+    public virtual string? RenderedMessage
     {
       get
       {
-        if (m_data.Message == null && this.m_cacheUpdatable)
+        if (m_data.Message is null && m_cacheUpdatable)
         {
           if (m_message == null)
           {
-            m_data.Message = "";
+            m_data.Message = string.Empty;
           }
-          else if (m_message is string)
+          else if (m_message is string s)
           {
-            m_data.Message = (m_message as string);
+            m_data.Message = s;
           }
-          else if (m_repository != null)
+          else if (Repository is not null)
           {
-            m_data.Message = m_repository.RendererMap.FindAndRender(m_message);
+            m_data.Message = Repository.RendererMap.FindAndRender(m_message);
           }
           else
           {
@@ -675,21 +637,21 @@
     /// </remarks>
     public virtual void WriteRenderedMessage(TextWriter writer)
     {
-      if (m_data.Message != null)
+      if (m_data.Message is not null)
       {
         writer.Write(m_data.Message);
       }
       else
       {
-        if (m_message != null)
+        if (m_message is not null)
         {
-          if (m_message is string)
+          if (m_message is string s)
           {
-            writer.Write(m_message as string);
+            writer.Write(s);
           }
-          else if (m_repository != null)
+          else if (Repository is not null)
           {
-            m_repository.RendererMap.FindAndRender(m_message, writer);
+            Repository.RendererMap.FindAndRender(m_message, writer);
           }
           else
           {
@@ -712,16 +674,16 @@
     /// The collected information is cached for future use.
     /// </para>
     /// </remarks>
-    public string ThreadName
+    public string? ThreadName
     {
       get
       {
-        if (m_data.ThreadName == null && this.m_cacheUpdatable)
+        if (m_data.ThreadName == null && m_cacheUpdatable)
         {
           // '.NET ThreadPool Worker' appears as a default thread name in the .NET 6-7 thread pool.
           // '.NET TP Worker' is the default thread name in the .NET 8+ thread pool.
           // Prefer the numeric thread ID instead.
-          string threadName = System.Threading.Thread.CurrentThread.Name;
+          string threadName = Thread.CurrentThread.Name;
           if (!string.IsNullOrEmpty(threadName) && threadName != ".NET TP Worker" && threadName != ".NET ThreadPool Worker")
           {
             m_data.ThreadName = threadName;
@@ -733,9 +695,7 @@
             // current thread. (Why don't Threads know their own ID?)
             try
             {
-              m_data.ThreadName =
-                  SystemInfo.CurrentThreadId.ToString(System.Globalization.NumberFormatInfo
-                      .InvariantInfo);
+              m_data.ThreadName = SystemInfo.CurrentThreadId.ToString(NumberFormatInfo.InvariantInfo);
             }
             catch (SecurityException)
             {
@@ -745,8 +705,7 @@
                   "Security exception while trying to get current thread ID. Error Ignored. Empty thread name.");
 
               // As a last resort use the hash code of the Thread object
-              m_data.ThreadName = System.Threading.Thread.CurrentThread.GetHashCode()
-                  .ToString(System.Globalization.CultureInfo.InvariantCulture);
+              m_data.ThreadName = Thread.CurrentThread.GetHashCode().ToString(CultureInfo.InvariantCulture);
             }
           }
         }
@@ -805,7 +764,7 @@
     public string UserName =>
         m_data.UserName ??= TryGetCurrentUserName() ?? SystemInfo.NotAvailableText;
 
-    private string TryGetCurrentUserName()
+    private string? TryGetCurrentUserName()
     {
       if (_platformDoesNotSupportWindowsIdentity)
       {
@@ -840,11 +799,11 @@
       }
     }
 
-    private string _cachedWindowsIdentityUserName;
+    private string? _cachedWindowsIdentityUserName;
     private static string TryReadWindowsIdentityUserName()
     {
       using var identity = WindowsIdentity.GetCurrent();
-      return identity?.Name ?? "";
+      return identity?.Name ?? string.Empty;
     }
 
     private static bool _platformDoesNotSupportWindowsIdentity;
@@ -852,52 +811,47 @@
     /// <summary>
     /// Gets the identity of the current thread principal.
     /// </summary>
-    /// <value>
-    /// The string name of the identity of the current thread principal.
-    /// </value>
     /// <remarks>
     /// <para>
     /// Calls <c>System.Threading.Thread.CurrentPrincipal.Identity.Name</c> to get
     /// the name of the current thread principal.
     /// </para>
     /// </remarks>
-    public string Identity
+    public string? Identity
     {
       get
       {
-        if (m_data.Identity == null && this.m_cacheUpdatable)
+        if (m_data.Identity is null && m_cacheUpdatable)
         {
           try
           {
-            if (System.Threading.Thread.CurrentPrincipal != null &&
-                System.Threading.Thread.CurrentPrincipal.Identity != null &&
-                System.Threading.Thread.CurrentPrincipal.Identity.Name != null)
+            if (Thread.CurrentPrincipal?.Identity?.Name is string name)
             {
-              m_data.Identity = System.Threading.Thread.CurrentPrincipal.Identity.Name;
+              m_data.Identity = name;
             }
             else
             {
-              m_data.Identity = "";
+              m_data.Identity = string.Empty;
             }
           }
           catch (ObjectDisposedException)
           {
-            // This exception will occur if System.Threading.Thread.CurrentPrincipal.Identity is not null but
+            // This exception will occur if Thread.CurrentPrincipal.Identity is not null but
             // the getter of the property Name tries to access disposed objects.
             // Seen to happen on IIS 7 or greater with windows authentication.
             LogLog.Debug(declaringType,
                 "Object disposed exception while trying to get current thread principal. Error Ignored. Empty identity name.");
 
-            m_data.Identity = "";
+            m_data.Identity = string.Empty;
           }
-          catch (System.Security.SecurityException)
+          catch (SecurityException)
           {
             // This security exception will occur if the caller does not have 
             // some undefined set of SecurityPermission flags.
             LogLog.Debug(declaringType,
                 "Security exception while trying to get current thread principal. Error Ignored. Empty identity name.");
 
-            m_data.Identity = "";
+            m_data.Identity = string.Empty;
           }
         }
 
@@ -908,19 +862,11 @@
     /// <summary>
     /// Gets the AppDomain friendly name.
     /// </summary>
-    /// <value>
-    /// The AppDomain friendly name.
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Gets the AppDomain friendly name.
-    /// </para>
-    /// </remarks>
-    public string Domain
+    public string? Domain
     {
       get
       {
-        if (m_data.Domain == null && this.m_cacheUpdatable)
+        if (m_data.Domain is null && m_cacheUpdatable)
         {
           m_data.Domain = SystemInfo.ApplicationFriendlyName;
         }
@@ -960,16 +906,12 @@
       get
       {
         // If we have cached properties then return that otherwise changes will be lost
-        if (m_data.Properties != null)
+        if (m_data.Properties is not null)
         {
           return m_data.Properties;
         }
 
-        if (m_eventProperties == null)
-        {
-          m_eventProperties = new PropertiesDictionary();
-        }
-
+        m_eventProperties ??= new PropertiesDictionary();
         return m_eventProperties;
       }
     }
@@ -977,9 +919,6 @@
     /// <summary>
     /// The fixed fields in this event
     /// </summary>
-    /// <value>
-    /// The set of fields that are fixed in this event
-    /// </value>
     /// <remarks>
     /// <para>
     /// Fields will not be fixed if they have previously been fixed.
@@ -988,14 +927,10 @@
     /// </remarks>
     public FixFlags Fix
     {
-      get { return m_fixFlags; }
-      set { this.FixVolatileData(value); }
+      get => m_fixFlags;
+      set => FixVolatileData(value);
     }
 
-    #endregion Public Instance Properties
-
-    #region Implementation of ISerializable
-
     /// <summary>
     /// Serializes this object into the <see cref="SerializationInfo" /> provided.
     /// </summary>
@@ -1011,7 +946,7 @@
     /// is to be used outside that method.
     /// </para>
     /// </remarks>
-    [System.Security.SecurityCritical]
+    [SecurityCritical]
     [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand,
         SerializationFormatter = true)]
     public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
@@ -1037,10 +972,6 @@
       info.AddValue("Identity", m_data.Identity);
     }
 
-    #endregion Implementation of ISerializable
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Gets the portable data for this <see cref="LoggingEvent" />.
     /// </summary>
@@ -1090,7 +1021,7 @@
     /// </para>
     /// </remarks>
     [Obsolete("Use GetExceptionString instead")]
-    public string GetExceptionStrRep()
+    public string? GetExceptionStrRep()
     {
       return GetExceptionString();
     }
@@ -1108,26 +1039,26 @@
     /// <see cref="ILoggerRepository.RendererMap" />.
     /// </para>
     /// </remarks>
-    public string GetExceptionString()
+    public string? GetExceptionString()
     {
-      if (m_data.ExceptionString == null && this.m_cacheUpdatable)
+      if (m_data.ExceptionString is null && m_cacheUpdatable)
       {
-        if (m_thrownException != null)
+        if (ExceptionObject is not null)
         {
-          if (m_repository != null)
+          if (Repository is not null)
           {
             // Render exception using the repositories renderer map
-            m_data.ExceptionString = m_repository.RendererMap.FindAndRender(m_thrownException);
+            m_data.ExceptionString = Repository.RendererMap.FindAndRender(ExceptionObject);
           }
           else
           {
             // Very last resort
-            m_data.ExceptionString = m_thrownException.ToString();
+            m_data.ExceptionString = ExceptionObject.ToString();
           }
         }
         else
         {
-          m_data.ExceptionString = "";
+          m_data.ExceptionString = string.Empty;
         }
       }
 
@@ -1142,11 +1073,11 @@
     /// Some of the values in instances of <see cref="LoggingEvent"/>
     /// are considered volatile, that is the values are correct at the
     /// time the event is delivered to appenders, but will not be consistent
-    /// at any time afterwards. If an event is to be stored and then processed
+    /// at any time afterward. If an event is to be stored and then processed
     /// at a later time these volatile values must be fixed by calling
     /// <see cref="M:FixVolatileData()"/>. There is a performance penalty
     /// incurred by calling <see cref="M:FixVolatileData()"/> but it
-    /// is essential to maintaining data consistency.
+    /// is essential to maintain data consistency.
     /// </para>
     /// <para>
     /// Calling <see cref="M:FixVolatileData()"/> is equivalent to
@@ -1173,11 +1104,11 @@
     /// Some of the values in instances of <see cref="LoggingEvent"/>
     /// are considered volatile, that is the values are correct at the
     /// time the event is delivered to appenders, but will not be consistent
-    /// at any time afterwards. If an event is to be stored and then processed
+    /// at any time afterward. If an event is to be stored and then processed
     /// at a later time these volatile values must be fixed by calling
     /// <see cref="M:FixVolatileData()"/>. There is a performance penalty
     /// for incurred by calling <see cref="M:FixVolatileData()"/> but it
-    /// is essential to maintaining data consistency.
+    /// is essential to maintain data consistency.
     /// </para>
     /// <para>
     /// The <paramref name="fastButLoose"/> param controls the data that
@@ -1218,22 +1149,20 @@
     /// </remarks>
     protected virtual void FixVolatileData(FixFlags flags)
     {
-      object forceCreation = null;
-
-      //Unlock the cache so that new values can be stored
-      //This may not be ideal if we are no longer in the correct context
-      //and someone calls fix. 
+      // Unlock the cache so that new values can be stored
+      // This may not be ideal if we are no longer in the correct context
+      // and someone calls fix. 
       m_cacheUpdatable = true;
 
       // determine the flags that we are actually fixing
-      var updateFlags = (FixFlags)((flags ^ m_fixFlags) & flags);
+      FixFlags updateFlags = (flags ^ m_fixFlags) & flags;
 
       if (updateFlags > 0)
       {
         if ((updateFlags & FixFlags.Message) != 0)
         {
           // Force the message to be rendered
-          forceCreation = this.RenderedMessage;
+          _ = RenderedMessage;
 
           m_fixFlags |= FixFlags.Message;
         }
@@ -1241,7 +1170,7 @@
         if ((updateFlags & FixFlags.ThreadName) != 0)
         {
           // Grab the thread name
-          forceCreation = this.ThreadName;
+          _ = ThreadName;
 
           m_fixFlags |= FixFlags.ThreadName;
         }
@@ -1249,7 +1178,7 @@
         if ((updateFlags & FixFlags.LocationInfo) != 0)
         {
           // Force the location information to be loaded
-          forceCreation = this.LocationInformation;
+          _ = LocationInformation;
 
           m_fixFlags |= FixFlags.LocationInfo;
         }
@@ -1257,7 +1186,7 @@
         if ((updateFlags & FixFlags.UserName) != 0)
         {
           // Grab the user name
-          forceCreation = this.UserName;
+          _ = UserName;
 
           m_fixFlags |= FixFlags.UserName;
         }
@@ -1265,7 +1194,7 @@
         if ((updateFlags & FixFlags.Domain) != 0)
         {
           // Grab the domain name
-          forceCreation = this.Domain;
+          _ = Domain;
 
           m_fixFlags |= FixFlags.Domain;
         }
@@ -1273,7 +1202,7 @@
         if ((updateFlags & FixFlags.Identity) != 0)
         {
           // Grab the identity
-          forceCreation = this.Identity;
+          _ = Identity;
 
           m_fixFlags |= FixFlags.Identity;
         }
@@ -1281,7 +1210,7 @@
         if ((updateFlags & FixFlags.Exception) != 0)
         {
           // Force the exception text to be loaded
-          forceCreation = GetExceptionString();
+          _ = GetExceptionString();
 
           m_fixFlags |= FixFlags.Exception;
         }
@@ -1294,34 +1223,24 @@
         }
       }
 
-      // avoid warning CS0219
-      if (forceCreation != null)
-      {
-      }
-
-      //Finaly lock everything we've cached.
+      // Finally lock everything we've cached.
       m_cacheUpdatable = false;
     }
 
-    #endregion Public Instance Methods
-
-    #region Protected Instance Methods
-
     private void CreateCompositeProperties()
     {
       var compositeProperties = new CompositeProperties();
 
-      if (m_eventProperties != null)
+      if (m_eventProperties is not null)
       {
         compositeProperties.Add(m_eventProperties);
       }
       var logicalThreadProperties = LogicalThreadContext.Properties.GetProperties(false);
-      if (logicalThreadProperties != null)
+      if (logicalThreadProperties is not null)
       {
         compositeProperties.Add(logicalThreadProperties);
       }
-      var threadProperties = ThreadContext.Properties.GetProperties(false);
-      if (threadProperties != null)
+      if (ThreadContext.Properties.GetProperties(false) is PropertiesDictionary threadProperties)
       {
         compositeProperties.Add(threadProperties);
       }
@@ -1353,38 +1272,32 @@
 
     private void CacheProperties()
     {
-      if (m_data.Properties == null && this.m_cacheUpdatable)
+      if (m_data.Properties is null && m_cacheUpdatable)
       {
-        if (m_compositeProperties == null)
+        if (m_compositeProperties is null)
         {
           CreateCompositeProperties();
         }
 
-        var flattenedProperties = m_compositeProperties.Flatten();
+        var flattenedProperties = m_compositeProperties!.Flatten();
 
         var fixedProperties = new PropertiesDictionary();
 
         // Validate properties
-        foreach (DictionaryEntry entry in flattenedProperties)
+        foreach (KeyValuePair<string, object?> entry in flattenedProperties)
         {
-          var key = entry.Key as string;
+          object? val = entry.Value;
 
-          if (key != null)
+          // Fix any IFixingRequired objects
+          if (entry.Value is IFixingRequired fixingRequired)
           {
-            var val = entry.Value;
+            val = fixingRequired.GetFixedObject();
+          }
 
-            // Fix any IFixingRequired objects
-            var fixingRequired = val as IFixingRequired;
-            if (fixingRequired != null)
-            {
-              val = fixingRequired.GetFixedObject();
-            }
-
-            // Strip keys with null values
-            if (val != null)
-            {
-              fixedProperties[key] = val;
-            }
+          // Strip keys with null values
+          if (val is not null)
+          {
+            fixedProperties[entry.Key] = val;
           }
         }
 
@@ -1393,17 +1306,17 @@
     }
 
     /// <summary>
-    /// Lookup a composite property in this event
+    /// Looks up a composite property in this event
     /// </summary>
     /// <param name="key">the key for the property to lookup</param>
     /// <returns>the value for the property</returns>
     /// <remarks>
     /// <para>
-    /// This event has composite properties that combine together properties from
+    /// This event has composite properties that combine properties from
     /// several different contexts in the following order:
     /// <list type="definition">
     ///    <item>
-    ///     <term>this events properties</term>
+    ///     <term>this event's properties</term>
     ///     <description>
     ///     This event has <see cref="Properties"/> that can be set. These 
     ///     properties are specific to this event only.
@@ -1426,19 +1339,19 @@
     /// </list>
     /// </para>
     /// </remarks>
-    public object LookupProperty(string key)
+    public object? LookupProperty(string key)
     {
-      if (m_data.Properties != null)
+      if (m_data.Properties is not null)
       {
         return m_data.Properties[key];
       }
 
-      if (m_compositeProperties == null)
+      if (m_compositeProperties is null)
       {
         CreateCompositeProperties();
       }
 
-      return m_compositeProperties[key];
+      return m_compositeProperties![key];
     }
 
     /// <summary>
@@ -1467,13 +1380,9 @@
         CreateCompositeProperties();
       }
 
-      return m_compositeProperties.Flatten();
+      return m_compositeProperties!.Flatten();
     }
 
-    #endregion Public Instance Methods
-
-    #region Private Instance Fields
-
     /// <summary>
     /// The internal logging event data.
     /// </summary>
@@ -1482,45 +1391,28 @@
     /// <summary>
     /// Location information for the caller.
     /// </summary>
-    public LocationInfo LocationInfo => m_data.LocationInfo;
+    public LocationInfo? LocationInfo => m_data.LocationInfo;
 
     /// <summary>
     /// The internal logging event data.
     /// </summary>
-    private CompositeProperties m_compositeProperties;
+    private CompositeProperties? m_compositeProperties;
 
     /// <summary>
     /// The internal logging event data.
     /// </summary>
-    private PropertiesDictionary m_eventProperties;
+    private PropertiesDictionary? m_eventProperties;
 
     /// <summary>
     /// The fully qualified Type of the calling 
     /// logger class in the stack frame (i.e. the declaring type of the method).
     /// </summary>
-    private readonly Type m_callerStackBoundaryDeclaringType;
+    private readonly Type? m_callerStackBoundaryDeclaringType;
 
     /// <summary>
     /// The application supplied message of logging event.
     /// </summary>
-    private object m_message;
-
-    /// <summary>
-    /// The exception that was thrown.
-    /// </summary>
-    /// <remarks>
-    /// This is not serialized. The string representation
-    /// is serialized instead.
-    /// </remarks>
-    private readonly Exception m_thrownException;
-
-    /// <summary>
-    /// The repository that generated the logging event
-    /// </summary>
-    /// <remarks>
-    /// This is not serialized.
-    /// </remarks>
-    private ILoggerRepository m_repository = null;
+    private object? m_message;
 
     /// <summary>
     /// The fix state for this event
@@ -1535,15 +1427,11 @@
     /// Indicated that the internal cache is updateable (ie not fixed)
     /// </summary>
     /// <remarks>
-    /// This is a seperate flag to m_fixFlags as it allows incrementel fixing and simpler
+    /// This is a separate flag to m_fixFlags as it allows incremental fixing and simpler
     /// changes in the caching strategy.
     /// </remarks>
     private bool m_cacheUpdatable = true;
 
-    #endregion Private Instance Fields
-
-    #region Constants
-
     /// <summary>
     /// The key into the Properties map for the host name value.
     /// </summary>
@@ -1558,7 +1446,5 @@
     /// The key into the Properties map for the user name value.
     /// </summary>
     public const string UserNameProperty = "log4net:UserName";
-
-    #endregion
   }
 }
\ No newline at end of file
diff --git a/src/log4net/Core/WrapperMap.cs b/src/log4net/Core/WrapperMap.cs
index b2b30e5..16613ac 100644
--- a/src/log4net/Core/WrapperMap.cs
+++ b/src/log4net/Core/WrapperMap.cs
@@ -24,8 +24,6 @@
 
 namespace log4net.Core
 {
-  #region WrapperCreationHandler
-
   /// <summary>
   /// Delegate used to handle creation of new wrappers.
   /// </summary>
@@ -43,8 +41,6 @@
   /// </remarks>
   public delegate ILoggerWrapper WrapperCreationHandler(ILogger logger);
 
-  #endregion WrapperCreationHandler
-
   /// <summary>
   /// Maps between logger objects and wrapper objects.
   /// </summary>
@@ -52,7 +48,7 @@
   /// <para>
   /// This class maintains a mapping between <see cref="ILogger"/> objects and
   /// <see cref="ILoggerWrapper"/> objects. Use the <see cref="GetWrapper"/> method to 
-  /// lookup the <see cref="ILoggerWrapper"/> for the specified <see cref="ILogger"/>.
+  /// look up the <see cref="ILoggerWrapper"/> for the specified <see cref="ILogger"/>.
   /// </para>
   /// <para>
   /// New wrapper instances are created by the <see cref="CreateNewWrapperObject"/>
@@ -66,8 +62,6 @@
   /// <author>Gert Driesen</author>
   public class WrapperMap
   {
-    #region Public Instance Constructors
-
     /// <summary>
     /// Initializes a new instance of the <see cref="WrapperMap" />
     /// </summary>
@@ -83,13 +77,9 @@
       m_createWrapperHandler = createWrapperHandler;
 
       // Create the delegates for the event callbacks
-      m_shutdownHandler = new LoggerRepositoryShutdownEventHandler(ILoggerRepository_Shutdown);
+      m_shutdownHandler = ILoggerRepository_Shutdown;
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets the wrapper object for the specified logger.
     /// </summary>
@@ -99,41 +89,37 @@
     /// If the logger is null then the corresponding wrapper is null.
     /// </para>
     /// <para>
-    /// Looks up the wrapper it it has previously been requested and
+    /// Looks up the wrapper it has previously been requested and
     /// returns it. If the wrapper has never been requested before then
     /// the <see cref="CreateNewWrapperObject"/> virtual method is
     /// called.
     /// </para>
     /// </remarks>
-    public virtual ILoggerWrapper GetWrapper(ILogger logger)
+    public virtual ILoggerWrapper? GetWrapper(ILogger? logger)
     {
       // If the logger is null then the corresponding wrapper is null
-      if (logger == null)
+      if (logger is null)
       {
         return null;
       }
 
       lock (this)
       {
-        // Lookup hierarchy in map.
-        Hashtable wrappersMap = (Hashtable)m_repositories[logger.Repository];
-
-        if (wrappersMap == null)
+        // Look up hierarchy in map.
+        Hashtable? wrappersMap = (Hashtable?)Repositories[logger.Repository];
+        if (wrappersMap is null)
         {
           // Hierarchy does not exist in map.
           // Must register with hierarchy
-
           wrappersMap = new Hashtable();
-          m_repositories[logger.Repository] = wrappersMap;
+          Repositories[logger.Repository] = wrappersMap;
 
           // Register for config reset & shutdown on repository
           logger.Repository.ShutdownEvent += m_shutdownHandler;
         }
 
         // Look for the wrapper object in the map
-        ILoggerWrapper wrapperObject = wrappersMap[logger] as ILoggerWrapper;
-
-        if (wrapperObject == null)
+        if (wrappersMap[logger] is not ILoggerWrapper wrapperObject)
         {
           // No wrapper object exists for the specified logger
 
@@ -148,10 +134,6 @@
       }
     }
 
-    #endregion Public Instance Properties
-
-    #region Protected Instance Properties
-
     /// <summary>
     /// Gets the map of logger repositories.
     /// </summary>
@@ -165,14 +147,7 @@
     /// value being the corresponding <see cref="ILoggerWrapper"/>.
     /// </para>
     /// </remarks>
-    protected Hashtable Repositories
-    {
-      get { return this.m_repositories; }
-    }
-
-    #endregion Protected Instance Properties
-
-    #region Protected Instance Methods
+    protected Hashtable Repositories { get; } = new();
 
     /// <summary>
     /// Creates the wrapper object for the specified logger.
@@ -186,13 +161,9 @@
     /// can be overridden in a subclass.
     /// </para>
     /// </remarks>
-    protected virtual ILoggerWrapper CreateNewWrapperObject(ILogger logger)
+    protected virtual ILoggerWrapper? CreateNewWrapperObject(ILogger logger)
     {
-      if (m_createWrapperHandler != null)
-      {
-        return m_createWrapperHandler(logger);
-      }
-      return null;
+      return m_createWrapperHandler?.Invoke(logger);
     }
 
     /// <summary>
@@ -213,7 +184,7 @@
       lock (this)
       {
         // Remove the repository from map
-        m_repositories.Remove(repository);
+        Repositories.Remove(repository);
 
         // Unhook events from the repository
         repository.ShutdownEvent -= m_shutdownHandler;
@@ -227,33 +198,21 @@
     /// <param name="e">The event args.</param>
     private void ILoggerRepository_Shutdown(object sender, EventArgs e)
     {
-      ILoggerRepository repository = sender as ILoggerRepository;
-      if (repository != null)
+      if (sender is ILoggerRepository repository)
       {
         // Remove all repository from map
         RepositoryShutdown(repository);
       }
     }
 
-    #endregion Protected Instance Methods
-
-    #region Private Instance Variables
-
-    /// <summary>
-    /// Map of logger repositories to hashtables of ILogger to ILoggerWrapper mappings
-    /// </summary>
-    private readonly Hashtable m_repositories = new Hashtable();
-
     /// <summary>
     /// The handler to use to create the extension wrapper objects.
     /// </summary>
-    private readonly WrapperCreationHandler m_createWrapperHandler;
+    private readonly WrapperCreationHandler? m_createWrapperHandler;
 
     /// <summary>
     /// Internal reference to the delegate used to register for repository shutdown events.
     /// </summary>
     private readonly LoggerRepositoryShutdownEventHandler m_shutdownHandler;
-
-    #endregion Private Instance Variables
   }
 }
diff --git a/src/log4net/Filter/NdcFilter.cs b/src/log4net/Filter/NdcFilter.cs
index 4b0a3e0..b7b1c87 100644
--- a/src/log4net/Filter/NdcFilter.cs
+++ b/src/log4net/Filter/NdcFilter.cs
@@ -17,13 +17,6 @@
 //
 #endregion
 
-using System;
-using System.Text.RegularExpressions;
-
-using log4net;
-using log4net.Core;
-using log4net.Util;
-
 namespace log4net.Filter
 {
   /// <summary>
@@ -34,7 +27,7 @@
   /// Simple filter to match a string in the <see cref="NDC"/>
   /// </para>
   /// <para>
-  /// As the MDC has been replaced with named stacks stored in the
+  /// As the NDC has been replaced with named stacks stored in the
   /// properties collections the <see cref="PropertyFilter"/> should 
   /// be used instead.
   /// </para>
@@ -54,7 +47,7 @@
     /// </remarks>
     public NdcFilter()
     {
-      base.Key = "NDC";
+      Key = "NDC";
     }
   }
 }
diff --git a/src/log4net/Layout/LayoutSkeleton.cs b/src/log4net/Layout/LayoutSkeleton.cs
index 348eb07..06b959f 100644
--- a/src/log4net/Layout/LayoutSkeleton.cs
+++ b/src/log4net/Layout/LayoutSkeleton.cs
@@ -17,10 +17,8 @@
 //
 #endregion
 
-using System;
 using System.IO;
 
-using log4net;
 using log4net.Core;
 
 namespace log4net.Layout
@@ -50,42 +48,6 @@
   /// <author>Gert Driesen</author>
   public abstract class LayoutSkeleton : ILayout, IOptionHandler
   {
-    #region Member Variables
-
-    /// <summary>
-    /// The header text
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// See <see cref="Header"/> for more information.
-    /// </para>
-    /// </remarks>
-    private string m_header = null;
-
-    /// <summary>
-    /// The footer text
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// See <see cref="Footer"/> for more information.
-    /// </para>
-    /// </remarks>
-    private string m_footer = null;
-
-    /// <summary>
-    /// Flag indicating if this layout handles exceptions
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// <c>false</c> if this layout handles exceptions
-    /// </para>
-    /// </remarks>
-    private bool m_ignoresException = true;
-
-    #endregion
-
-    #region Constructors
-
     /// <summary>
     /// Empty default constructor
     /// </summary>
@@ -98,10 +60,6 @@
     {
     }
 
-    #endregion
-
-    #region Implementation of IOptionHandler
-
     /// <summary>
     /// Activate component options
     /// </summary>
@@ -123,10 +81,6 @@
     /// </remarks>
     public abstract void ActivateOptions();
 
-    #endregion
-
-    #region Implementation of ILayout
-
     /// <summary>
     /// Implement this method to create your own layout format.
     /// </summary>
@@ -149,7 +103,7 @@
     /// </remarks>
     public string Format(LoggingEvent loggingEvent)
     {
-      using StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
+      using var writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
       Format(writer, loggingEvent);
       return writer.ToString();
     }
@@ -168,10 +122,7 @@
     /// property.
     /// </para>
     /// </remarks>
-    public virtual string ContentType
-    {
-      get { return "text/plain"; }
-    }
+    public virtual string ContentType => "text/plain";
 
     /// <summary>
     /// The header for the layout format.
@@ -183,11 +134,7 @@
     /// are formatted and appended.
     /// </para>
     /// </remarks>
-    public virtual string Header
-    {
-      get { return m_header; }
-      set { m_header = value; }
-    }
+    public virtual string? Header { get; set; }
 
     /// <summary>
     /// The footer for the layout format.
@@ -199,11 +146,7 @@
     /// have been formatted and appended.
     /// </para>
     /// </remarks>
-    public virtual string Footer
-    {
-      get { return m_footer; }
-      set { m_footer = value; }
-    }
+    public virtual string? Footer { get; set; }
 
     /// <summary>
     /// Flag indicating if this layout handles exceptions
@@ -217,16 +160,10 @@
     /// object, then the layout should return <c>true</c>.
     /// </para>
     /// <para>
-    /// Set this value to override a this default setting. The default
+    /// Set this value to override the default setting. The default
     /// value is <c>true</c>, this layout does not handle the exception.
     /// </para>
     /// </remarks>
-    public virtual bool IgnoresException
-    {
-      get { return m_ignoresException; }
-      set { m_ignoresException = value; }
-    }
-
-    #endregion
+    public virtual bool IgnoresException { get; set; } = true;
   }
 }
diff --git a/src/log4net/Layout/Pattern/NdcPatternConverter.cs b/src/log4net/Layout/Pattern/NdcPatternConverter.cs
index a64dbb9..cc2293b 100644
--- a/src/log4net/Layout/Pattern/NdcPatternConverter.cs
+++ b/src/log4net/Layout/Pattern/NdcPatternConverter.cs
@@ -17,8 +17,6 @@
 //
 #endregion
 
-using System;
-using System.Text;
 using System.IO;
 
 using log4net.Core;
diff --git a/src/log4net/Layout/XmlLayout.cs b/src/log4net/Layout/XmlLayout.cs
index c23ecf9..c165074 100644
--- a/src/log4net/Layout/XmlLayout.cs
+++ b/src/log4net/Layout/XmlLayout.cs
@@ -18,6 +18,7 @@
 #endregion
 
 using System;
+using System.Collections.Generic;
 using System.Text;
 using System.Xml;
 
@@ -72,12 +73,10 @@
   /// <author>Gert Driesen</author>
   public class XmlLayout : XmlLayoutBase
   {
-    #region Public Instance Constructors
-
     /// <summary>
     /// Constructs an XmlLayout
     /// </summary>
-    public XmlLayout() : base()
+    public XmlLayout()
     {
     }
 
@@ -88,7 +87,7 @@
     /// <para>
     /// The <b>LocationInfo</b> option takes a boolean value. By
     /// default, it is set to false which means there will be no location
-    /// information output by this layout. If the the option is set to
+    /// information output by this layout. If the option is set to
     /// true, then the file name and line number of the statement
     /// at the origin of the log statement will be output. 
     /// </para>
@@ -102,10 +101,6 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// The prefix to use for all element names
     /// </summary>
@@ -116,15 +111,10 @@
     /// then no prefix will be written.
     /// </para>
     /// </remarks>
-    public string Prefix
-    {
-      get { return m_prefix; }
-      set { m_prefix = value; }
-    }
-
-
+    public string Prefix { get; set; } = PREFIX;
+    
     /// <summary>
-    /// Set whether or not to base64 encode the message.
+    /// Set whether to base64 encode the message.
     /// </summary>
     /// <remarks>
     /// <para>
@@ -136,14 +126,10 @@
     /// on the log message.
     /// </para>
     /// </remarks>
-    public bool Base64EncodeMessage
-    {
-      get { return m_base64Message; }
-      set { m_base64Message = value; }
-    }
+    public bool Base64EncodeMessage { get; set; }
 
     /// <summary>
-    /// Set whether or not to base64 encode the property values.
+    /// Set whether to base64 encode the property values.
     /// </summary>
     /// <remarks>
     /// <para>
@@ -155,15 +141,7 @@
     /// on the property values.
     /// </para>
     /// </remarks>
-    public bool Base64EncodeProperties
-    {
-      get { return m_base64Properties; }
-      set { m_base64Properties = value; }
-    }
-
-    #endregion Public Instance Properties
-
-    #region Implementation of IOptionHandler
+    public bool Base64EncodeProperties { get; set; }
 
     /// <summary>
     /// Initialize layout options
@@ -189,21 +167,17 @@
       base.ActivateOptions();
 
       // Cache the full element names including the prefix
-      if (m_prefix != null && m_prefix.Length > 0)
+      if (Prefix.Length > 0)
       {
-        m_elmEvent = m_prefix + ":" + ELM_EVENT;
-        m_elmMessage = m_prefix + ":" + ELM_MESSAGE;
-        m_elmProperties = m_prefix + ":" + ELM_PROPERTIES;
-        m_elmData = m_prefix + ":" + ELM_DATA;
-        m_elmException = m_prefix + ":" + ELM_EXCEPTION;
-        m_elmLocation = m_prefix + ":" + ELM_LOCATION;
+        m_elmEvent = Prefix + ":" + ELM_EVENT;
+        m_elmMessage = Prefix + ":" + ELM_MESSAGE;
+        m_elmProperties = Prefix + ":" + ELM_PROPERTIES;
+        m_elmData = Prefix + ":" + ELM_DATA;
+        m_elmException = Prefix + ":" + ELM_EXCEPTION;
+        m_elmLocation = Prefix + ":" + ELM_LOCATION;
       }
     }
 
-    #endregion Implementation of IOptionHandler
-
-    #region Override implementation of XMLLayoutBase
-
     /// <summary>
     /// Does the actual writing of the XML.
     /// </summary>
@@ -217,13 +191,13 @@
     /// </remarks>
     protected override void FormatXml(XmlWriter writer, LoggingEvent loggingEvent)
     {
-      writer.WriteStartElement(m_elmEvent, m_prefix, ELM_EVENT, m_prefix);
-      writer.WriteAttributeString(ATTR_LOGGER, loggingEvent.LoggerName);
+      writer.WriteStartElement(m_elmEvent, Prefix, ELM_EVENT, Prefix);
+      writer.WriteAttributeString(ATTR_LOGGER, loggingEvent.LoggerName!);
 
       writer.WriteAttributeString(ATTR_TIMESTAMP, XmlConvert.ToString(loggingEvent.TimeStamp, XmlDateTimeSerializationMode.Local));
 
       writer.WriteAttributeString(ATTR_LEVEL, loggingEvent.Level.DisplayName);
-      writer.WriteAttributeString(ATTR_THREAD, loggingEvent.ThreadName);
+      writer.WriteAttributeString(ATTR_THREAD, loggingEvent.ThreadName!);
 
       if (loggingEvent.Domain != null && loggingEvent.Domain.Length > 0)
       {
@@ -233,14 +207,14 @@
       {
         writer.WriteAttributeString(ATTR_IDENTITY, loggingEvent.Identity);
       }
-      if (loggingEvent.UserName != null && loggingEvent.UserName.Length > 0)
+      if (loggingEvent.UserName.Length > 0)
       {
         writer.WriteAttributeString(ATTR_USERNAME, loggingEvent.UserName);
       }
 
       // Append the message text
-      writer.WriteStartElement(m_elmMessage, m_prefix, ELM_MESSAGE, m_prefix);
-      if (!this.Base64EncodeMessage)
+      writer.WriteStartElement(m_elmMessage, Prefix, ELM_MESSAGE, Prefix);
+      if (!Base64EncodeMessage)
       {
         Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, this.InvalidCharReplacement);
       }
@@ -257,17 +231,17 @@
       // Append the properties text
       if (properties.Count > 0)
       {
-        writer.WriteStartElement(m_elmProperties, m_prefix, ELM_PROPERTIES, m_prefix);
-        foreach (System.Collections.DictionaryEntry entry in properties)
+        writer.WriteStartElement(m_elmProperties, Prefix, ELM_PROPERTIES, Prefix);
+        foreach (KeyValuePair<string, object?> entry in properties)
         {
-          writer.WriteStartElement(m_elmData, m_prefix, ELM_DATA, m_prefix);
-          writer.WriteAttributeString(ATTR_NAME, Transform.MaskXmlInvalidCharacters((string)entry.Key, this.InvalidCharReplacement));
+          writer.WriteStartElement(m_elmData, Prefix, ELM_DATA, Prefix);
+          writer.WriteAttributeString(ATTR_NAME, Transform.MaskXmlInvalidCharacters(entry.Key, InvalidCharReplacement));
 
           // Use an ObjectRenderer to convert the object to a string
-          string valueStr = null;
-          if (!this.Base64EncodeProperties)
+          string valueStr;
+          if (!Base64EncodeProperties)
           {
-            valueStr = Transform.MaskXmlInvalidCharacters(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value), this.InvalidCharReplacement);
+            valueStr = Transform.MaskXmlInvalidCharacters(loggingEvent.Repository.RendererMap.FindAndRender(entry.Value), InvalidCharReplacement);
           }
           else
           {
@@ -285,8 +259,8 @@
       if (exceptionStr != null && exceptionStr.Length > 0)
       {
         // Append the stack trace line
-        writer.WriteStartElement(m_elmException, m_prefix, ELM_EXCEPTION, m_prefix);
-        Transform.WriteEscapedXmlString(writer, exceptionStr, this.InvalidCharReplacement);
+        writer.WriteStartElement(m_elmException, Prefix, ELM_EXCEPTION, Prefix);
+        Transform.WriteEscapedXmlString(writer, exceptionStr, InvalidCharReplacement);
         writer.WriteEndElement();
       }
 
@@ -294,7 +268,7 @@
       {
         LocationInfo locationInfo = loggingEvent.LocationInformation;
 
-        writer.WriteStartElement(m_elmLocation, m_prefix, ELM_LOCATION, m_prefix);
+        writer.WriteStartElement(m_elmLocation, Prefix, ELM_LOCATION, Prefix);
         writer.WriteAttributeString(ATTR_CLASS, locationInfo.ClassName);
         writer.WriteAttributeString(ATTR_METHOD, locationInfo.MethodName);
         writer.WriteAttributeString(ATTR_FILE, locationInfo.FileName);
@@ -305,15 +279,6 @@
       writer.WriteEndElement();
     }
 
-    #endregion Override implementation of XMLLayoutBase
-
-    #region Private Instance Fields
-
-    /// <summary>
-    /// The prefix to use for all generated element names
-    /// </summary>
-    private string m_prefix = PREFIX;
-
     private string m_elmEvent = ELM_EVENT;
     private string m_elmMessage = ELM_MESSAGE;
     private string m_elmData = ELM_DATA;
@@ -321,19 +286,11 @@
     private string m_elmException = ELM_EXCEPTION;
     private string m_elmLocation = ELM_LOCATION;
 
-    private bool m_base64Message = false;
-    private bool m_base64Properties = false;
-
-    #endregion Private Instance Fields
-
-    #region Private Static Fields
-
     private const string PREFIX = "log4net";
 
     private const string ELM_EVENT = "event";
     private const string ELM_MESSAGE = "message";
     private const string ELM_PROPERTIES = "properties";
-    private const string ELM_GLOBAL_PROPERTIES = "global-properties";
     private const string ELM_DATA = "data";
     private const string ELM_EXCEPTION = "exception";
     private const string ELM_LOCATION = "locationInfo";
@@ -351,9 +308,6 @@
     private const string ATTR_LINE = "line";
     private const string ATTR_NAME = "name";
     private const string ATTR_VALUE = "value";
-
-
-    #endregion Private Static Fields
   }
 }
 
diff --git a/src/log4net/Layout/XmlLayoutBase.cs b/src/log4net/Layout/XmlLayoutBase.cs
index 798fcc6..0002c46 100644
--- a/src/log4net/Layout/XmlLayoutBase.cs
+++ b/src/log4net/Layout/XmlLayoutBase.cs
@@ -19,7 +19,6 @@
 
 using System;
 using System.IO;
-using System.Text;
 using System.Xml;
 
 using log4net.Util;
@@ -44,8 +43,6 @@
   /// <author>Gert Driesen</author>
   public abstract class XmlLayoutBase : LayoutSkeleton
   {
-    #region Protected Instance Constructors
-
     /// <summary>
     /// Protected constructor to support subclasses
     /// </summary>
@@ -80,13 +77,9 @@
     protected XmlLayoutBase(bool locationInfo)
     {
       IgnoresException = false;
-      m_locationInfo = locationInfo;
+      LocationInfo = locationInfo;
     }
 
-    #endregion Protected Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets a value indicating whether to include location information in 
     /// the XML events.
@@ -107,11 +100,8 @@
     /// appender as well.
     /// </para>
     /// </remarks>
-    public bool LocationInfo
-    {
-      get { return m_locationInfo; }
-      set { m_locationInfo = value; }
-    }
+    public bool LocationInfo { get; set; }
+
     /// <summary>
     /// The string to replace characters that can not be expressed in XML with.
     /// <remarks>
@@ -125,14 +115,7 @@
     /// </para>
     /// </remarks>
     /// </summary>
-    public string InvalidCharReplacement
-    {
-      get { return m_invalidCharReplacement; }
-      set { m_invalidCharReplacement = value; }
-    }
-    #endregion
-
-    #region Implementation of IOptionHandler
+    public string InvalidCharReplacement { get; set; } = "?";
 
     /// <summary>
     /// Initialize layout options
@@ -155,10 +138,6 @@
       // nothing to do
     }
 
-    #endregion Implementation of IOptionHandler
-
-    #region Override implementation of LayoutSkeleton
-
     /// <summary>
     /// Gets the content type output by this layout. 
     /// </summary>
@@ -193,9 +172,9 @@
     /// </remarks>
     public override void Format(TextWriter writer, LoggingEvent loggingEvent)
     {
-      if (loggingEvent == null)
+      if (loggingEvent is null)
       {
-        throw new ArgumentNullException("loggingEvent");
+        throw new ArgumentNullException(nameof(loggingEvent));
       }
       using XmlWriter xmlWriter = XmlWriterExtensions.CreateXmlWriter(writer);
       // Write the event to the writer
@@ -208,10 +187,6 @@
       // -> Dispose from using var will close & flush
     }
 
-    #endregion Override implementation of LayoutSkeleton
-
-    #region Protected Instance Methods
-
     /// <summary>
     /// Does the actual writing of the XML.
     /// </summary>
@@ -224,22 +199,5 @@
     /// </para>
     /// </remarks>
     protected abstract void FormatXml(XmlWriter writer, LoggingEvent loggingEvent);
-
-    #endregion Protected Instance Methods
-
-    #region Private Instance Fields
-
-    /// <summary>
-    /// Flag to indicate if location information should be included in
-    /// the XML events.
-    /// </summary>
-    private bool m_locationInfo = false;
-
-    /// <summary>
-    /// The string to replace invalid chars with
-    /// </summary>
-    private string m_invalidCharReplacement = "?";
-
-    #endregion Private Instance Fields
   }
 }
\ No newline at end of file
diff --git a/src/log4net/Layout/XmlLayoutSchemaLog4j.cs b/src/log4net/Layout/XmlLayoutSchemaLog4j.cs
index ef69e23..734a164 100644
--- a/src/log4net/Layout/XmlLayoutSchemaLog4j.cs
+++ b/src/log4net/Layout/XmlLayoutSchemaLog4j.cs
@@ -18,9 +18,8 @@
 #endregion
 
 using System;
-using System.Text;
+using System.Collections.Generic;
 using System.Xml;
-using System.IO;
 
 using log4net.Core;
 using log4net.Util;
@@ -39,21 +38,15 @@
   /// <author>Nicko Cadell</author>
   public class XmlLayoutSchemaLog4j : XmlLayoutBase
   {
-    #region Static Members
-
     /// <summary>
     /// The 1st of January 1970 in UTC
     /// </summary>
-    private static readonly DateTime s_date1970 = new DateTime(1970, 1, 1);
-
-    #endregion
-
-    #region Constructors
+    private static readonly DateTime s_date1970 = new(1970, 1, 1);
 
     /// <summary>
     /// Constructs an XMLLayoutSchemaLog4j
     /// </summary>
-    public XmlLayoutSchemaLog4j() : base()
+    public XmlLayoutSchemaLog4j()
     {
     }
 
@@ -64,7 +57,7 @@
     /// <para>
     /// The <b>LocationInfo</b> option takes a boolean value. By
     /// default, it is set to false which means there will be no location
-    /// information output by this layout. If the the option is set to
+    /// information output by this layout. If the option is set to
     /// true, then the file name and line number of the statement
     /// at the origin of the log statement will be output. 
     /// </para>
@@ -78,10 +71,6 @@
     {
     }
 
-    #endregion
-
-    #region Public Properties
-
     /// <summary>
     /// The version of the log4j schema to use.
     /// </summary>
@@ -102,8 +91,6 @@
       }
     }
 
-    #endregion
-
     /* Example log4j schema event
 
 <log4j:event logger="first logger" level="ERROR" thread="Thread-3" timestamp="1051494121460">
@@ -142,32 +129,31 @@
       // Translate logging events for log4j
 
       // Translate hostname property
-      if (loggingEvent.LookupProperty(LoggingEvent.HostNameProperty) != null &&
-        loggingEvent.LookupProperty("log4jmachinename") == null)
+      if (loggingEvent.LookupProperty(LoggingEvent.HostNameProperty) is not null &&
+        loggingEvent.LookupProperty("log4jmachinename") is null)
       {
         loggingEvent.GetProperties()["log4jmachinename"] = loggingEvent.LookupProperty(LoggingEvent.HostNameProperty);
       }
 
       // translate appdomain name
-      if (loggingEvent.LookupProperty("log4japp") == null &&
-        loggingEvent.Domain != null &&
+      if (loggingEvent.LookupProperty("log4japp") is null &&
+        loggingEvent.Domain is not null &&
         loggingEvent.Domain.Length > 0)
       {
         loggingEvent.GetProperties()["log4japp"] = loggingEvent.Domain;
       }
 
       // translate identity name
-      if (loggingEvent.Identity != null &&
+      if (loggingEvent.Identity is not null &&
         loggingEvent.Identity.Length > 0 &&
-        loggingEvent.LookupProperty(LoggingEvent.IdentityProperty) == null)
+        loggingEvent.LookupProperty(LoggingEvent.IdentityProperty) is null)
       {
         loggingEvent.GetProperties()[LoggingEvent.IdentityProperty] = loggingEvent.Identity;
       }
 
       // translate user name
-      if (loggingEvent.UserName != null &&
-        loggingEvent.UserName.Length > 0 &&
-        loggingEvent.LookupProperty(LoggingEvent.UserNameProperty) == null)
+      if (loggingEvent.UserName.Length > 0 &&
+          loggingEvent.LookupProperty(LoggingEvent.UserNameProperty) is null)
       {
         loggingEvent.GetProperties()[LoggingEvent.UserNameProperty] = loggingEvent.UserName;
       }
@@ -189,19 +175,19 @@
 
       // Append the message text
       writer.WriteStartElement("log4j:message", "log4j", "message", "log4net");
-      Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, this.InvalidCharReplacement);
+      Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage, InvalidCharReplacement);
       writer.WriteEndElement();
 
       object ndcObj = loggingEvent.LookupProperty("NDC");
-      if (ndcObj != null)
+      if (ndcObj is not null)
       {
-        string valueStr = loggingEvent.Repository.RendererMap.FindAndRender(ndcObj);
+        string? valueStr = loggingEvent.Repository.RendererMap.FindAndRender(ndcObj);
 
-        if (valueStr != null && valueStr.Length > 0)
+        if (valueStr is not null && valueStr.Length > 0)
         {
           // Append the NDC text
           writer.WriteStartElement("log4j:NDC", "log4j", "NDC", "log4net");
-          Transform.WriteEscapedXmlString(writer, valueStr, this.InvalidCharReplacement);
+          Transform.WriteEscapedXmlString(writer, valueStr, InvalidCharReplacement);
           writer.WriteEndElement();
         }
       }
@@ -211,10 +197,10 @@
       if (properties.Count > 0)
       {
         writer.WriteStartElement("log4j:properties", "log4j", "properties", "log4net");
-        foreach (System.Collections.DictionaryEntry entry in properties)
+        foreach (KeyValuePair<string, object?> entry in properties)
         {
           writer.WriteStartElement("log4j:data", "log4j", "data", "log4net");
-          writer.WriteAttributeString("name", (string)entry.Key);
+          writer.WriteAttributeString("name", entry.Key);
 
           // Use an ObjectRenderer to convert the object to a string
           string valueStr = loggingEvent.Repository.RendererMap.FindAndRender(entry.Value);
@@ -230,13 +216,13 @@
       {
         // Append the stack trace line
         writer.WriteStartElement("log4j:throwable", "log4j", "data", "log4net");
-        Transform.WriteEscapedXmlString(writer, exceptionStr, this.InvalidCharReplacement);
+        Transform.WriteEscapedXmlString(writer, exceptionStr, InvalidCharReplacement);
         writer.WriteEndElement();
       }
 
       if (LocationInfo)
       {
-        LocationInfo locationInfo = loggingEvent.LocationInformation;
+        LocationInfo? locationInfo = loggingEvent.LocationInformation;
 
         writer.WriteStartElement("log4j:locationInfo", "log4j", "locationInfo", "log4net");
         writer.WriteAttributeString("class", locationInfo.ClassName);
diff --git a/src/log4net/LogManager.cs b/src/log4net/LogManager.cs
index 939295e..c78b7f0 100644
--- a/src/log4net/LogManager.cs
+++ b/src/log4net/LogManager.cs
@@ -19,7 +19,7 @@
 
 using System;
 using System.Reflection;
-
+using log4net.Appender;
 using log4net.Core;
 using log4net.Repository;
 
@@ -55,24 +55,8 @@
   /// <seealso cref="ILog"/>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  public sealed class LogManager
+  public static class LogManager
   {
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="LogManager" /> class. 
-    /// </summary>
-    /// <remarks>
-    /// Uses a private access modifier to prevent instantiation of this class.
-    /// </remarks>
-    private LogManager()
-    {
-    }
-
-    #endregion Private Instance Constructors
-
-    #region Type Specific Manager Methods
-
     /// <overloads>Returns the named logger if it exists.</overloads>
     /// <summary>
     /// Returns the named logger if it exists.
@@ -85,7 +69,7 @@
     /// </remarks>
     /// <param name="name">The fully qualified logger name to look for.</param>
     /// <returns>The logger found, or <c>null</c> if no logger could be found.</returns>
-    public static ILog Exists(string name)
+    public static ILog? Exists(string name)
     {
       return Exists(Assembly.GetCallingAssembly(), name);
     }
@@ -142,7 +126,7 @@
     /// The logger found, or <c>null</c> if the logger doesn't exist in the specified 
     /// repository.
     /// </returns>
-    public static ILog Exists(string repository, string name)
+    public static ILog? Exists(string repository, string name)
     {
       return WrapLogger(LoggerManager.Exists(repository, name));
     }
@@ -157,13 +141,13 @@
     /// <c>null</c>.
     /// </para>
     /// </remarks>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <param name="name">The fully qualified logger name to look for.</param>
     /// <returns>
     /// The logger, or <c>null</c> if the logger doesn't exist in the specified
     /// assembly's repository.
     /// </returns>
-    public static ILog Exists(Assembly repositoryAssembly, string name)
+    public static ILog? Exists(Assembly repositoryAssembly, string name)
     {
       return WrapLogger(LoggerManager.Exists(repositoryAssembly, name));
     }
@@ -184,7 +168,7 @@
     /// <summary>
     /// Returns all the currently defined loggers in the specified assembly's repository.
     /// </summary>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <remarks>
     /// The root logger is <b>not</b> included in the returned array.
     /// </remarks>
@@ -215,7 +199,7 @@
     /// <returns>The logger with the name specified.</returns>
     public static ILog GetLogger(string repository, string name)
     {
-      return WrapLogger(LoggerManager.GetLogger(repository, name));
+      return WrapLogger(LoggerManager.GetLogger(repository, name))!;
     }
 
     /// <summary>
@@ -234,12 +218,12 @@
     /// log4net.
     /// </para>
     /// </remarks>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <param name="name">The name of the logger to retrieve.</param>
     /// <returns>The logger with the name specified.</returns>
     public static ILog GetLogger(Assembly repositoryAssembly, string name)
     {
-      return WrapLogger(LoggerManager.GetLogger(repositoryAssembly, name));
+      return WrapLogger(LoggerManager.GetLogger(repositoryAssembly, name))!;
     }
 
     /// <summary>
@@ -252,7 +236,7 @@
     /// <returns>The logger with the name specified.</returns>
     public static ILog GetLogger(Type type)
     {
-      return GetLogger(Assembly.GetCallingAssembly(), type.FullName);
+      return GetLogger(Assembly.GetCallingAssembly(), type.FullName!);
     }
 
     /// <summary>
@@ -266,7 +250,7 @@
     /// <returns>The logger with the name specified.</returns>
     public static ILog GetLogger(string repository, Type type)
     {
-      return WrapLogger(LoggerManager.GetLogger(repository, type));
+      return WrapLogger(LoggerManager.GetLogger(repository, type))!;
     }
 
     /// <summary>
@@ -275,18 +259,14 @@
     /// <remarks>
     /// Gets the logger for the fully qualified name of the type specified.
     /// </remarks>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     /// <param name="type">The full name of <paramref name="type"/> will be used as the name of the logger to retrieve.</param>
     /// <returns>The logger with the name specified.</returns>
     public static ILog GetLogger(Assembly repositoryAssembly, Type type)
     {
-      return WrapLogger(LoggerManager.GetLogger(repositoryAssembly, type));
+      return WrapLogger(LoggerManager.GetLogger(repositoryAssembly, type))!;
     }
 
-    #endregion Type Specific Manager Methods
-
-    #region Domain & Repository Manager Methods
-
     /// <summary>
     /// Shuts down the log4net system.
     /// </summary>
@@ -301,7 +281,7 @@
     /// Otherwise, pending logging events might be lost.
     /// </para>
     /// <para>The <c>shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
@@ -325,7 +305,7 @@
     /// Otherwise, pending logging events might be lost.
     /// </para>
     /// <para>The <c>shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
@@ -349,12 +329,12 @@
     /// Otherwise, pending logging events might be lost.
     /// </para>
     /// <para>The <c>shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
     /// </remarks>
-    /// <param name="repository">The repository to shutdown.</param>
+    /// <param name="repository">The repository to shut down.</param>
     public static void ShutdownRepository(string repository)
     {
       LoggerManager.ShutdownRepository(repository);
@@ -376,12 +356,12 @@
     /// </para>
     /// <para>
     /// The <c>shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
     /// </remarks>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     public static void ShutdownRepository(Assembly repositoryAssembly)
     {
       LoggerManager.ShutdownRepository(repositoryAssembly);
@@ -438,7 +418,7 @@
     /// message disabling is set to its default "off" value.
     /// </para>    
     /// </remarks>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository to reset.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository to reset.</param>
     public static void ResetConfiguration(Assembly repositoryAssembly)
     {
       LoggerManager.ResetConfiguration(repositoryAssembly);
@@ -488,7 +468,7 @@
     /// by the <paramref name="repositoryAssembly"/> argument.
     /// </para>
     /// </remarks>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     [Obsolete("Use GetRepository instead of GetLoggerRepository")]
     public static ILoggerRepository GetLoggerRepository(Assembly repositoryAssembly)
     {
@@ -537,7 +517,7 @@
     /// by the <paramref name="repositoryAssembly"/> argument.
     /// </para>
     /// </remarks>
-    /// <param name="repositoryAssembly">The assembly to use to lookup the repository.</param>
+    /// <param name="repositoryAssembly">The assembly to use to look up the repository.</param>
     public static ILoggerRepository GetRepository(Assembly repositoryAssembly)
     {
       return LoggerManager.GetRepository(repositoryAssembly);
@@ -738,33 +718,26 @@
     /// <summary>
     /// Flushes logging events buffered in all configured appenders in the default repository.
     /// </summary>
-    /// <param name="millisecondsTimeout">The maximum time in milliseconds to wait for logging events from asycnhronous appenders to be flushed.</param>
+    /// <param name="millisecondsTimeout">The maximum time in milliseconds to wait for logging events from asynchronous appenders to be flushed.</param>
     /// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns>
     public static bool Flush(int millisecondsTimeout)
     {
-      Appender.IFlushable flushableRepository = LoggerManager.GetRepository(Assembly.GetCallingAssembly()) as Appender.IFlushable;
-      if (flushableRepository == null)
+      if (LoggerManager.GetRepository(Assembly.GetCallingAssembly()) is not IFlushable flushableRepository)
       {
         return false;
       }
-      else
-      {
-        return flushableRepository.Flush(millisecondsTimeout);
-      }
+
+      return flushableRepository.Flush(millisecondsTimeout);
     }
 
-    #endregion Domain & Repository Manager Methods
-
-    #region Extension Handlers
-
     /// <summary>
     /// Looks up the wrapper object for the logger specified.
     /// </summary>
     /// <param name="logger">The logger to get the wrapper for.</param>
     /// <returns>The wrapper for the logger specified.</returns>
-    private static ILog WrapLogger(ILogger logger)
+    private static ILog? WrapLogger(ILogger? logger)
     {
-      return (ILog)s_wrapperMap.GetWrapper(logger);
+      return (ILog?)s_wrapperMap.GetWrapper(logger);
     }
 
     /// <summary>
@@ -793,15 +766,9 @@
       return new LogImpl(logger);
     }
 
-    #endregion
-
-    #region Private Static Fields
-
     /// <summary>
     /// The wrapper map to use to hold the <see cref="LogImpl"/> objects.
     /// </summary>
-    private static readonly WrapperMap s_wrapperMap = new WrapperMap(new WrapperCreationHandler(WrapperCreationHandler));
-
-    #endregion Private Static Fields
+    private static readonly WrapperMap s_wrapperMap = new(WrapperCreationHandler);
   }
 }
\ No newline at end of file
diff --git a/src/log4net/LogicalThreadContext.cs b/src/log4net/LogicalThreadContext.cs
index ca399f6..f19e230 100644
--- a/src/log4net/LogicalThreadContext.cs
+++ b/src/log4net/LogicalThreadContext.cs
@@ -82,73 +82,22 @@
   /// </example>
   /// <threadsafety static="true" instance="true" />
   /// <author>Nicko Cadell</author>
-  public sealed class LogicalThreadContext
+  public static class LogicalThreadContext
   {
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Private Constructor. 
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Uses a private access modifier to prevent instantiation of this class.
-    /// </para>
-    /// </remarks>
-    private LogicalThreadContext()
-    {
-    }
-
-    #endregion Private Instance Constructors
-
-    #region Public Static Properties
-
     /// <summary>
     /// The thread properties map
     /// </summary>
-    /// <value>
-    /// The thread properties map
-    /// </value>
     /// <remarks>
     /// <para>
     /// The <c>LogicalThreadContext</c> properties override any <see cref="ThreadContext"/> 
     /// or <see cref="GlobalContext"/> properties with the same name.
     /// </para>
     /// </remarks>
-    public static LogicalThreadContextProperties Properties
-    {
-      get { return s_properties; }
-    }
+    public static LogicalThreadContextProperties Properties { get; } = new();
 
     /// <summary>
-    /// The thread stacks
-    /// </summary>
-    /// <value>
-    /// stack map
-    /// </value>
-    /// <remarks>
-    /// <para>
     /// The logical thread stacks.
-    /// </para>
-    /// </remarks>
-    public static LogicalThreadContextStacks Stacks
-    {
-      get { return s_stacks; }
-    }
-
-    #endregion Public Static Properties
-
-    #region Private Static Fields
-
-    /// <summary>
-    /// The thread context properties instance
     /// </summary>
-    private static readonly LogicalThreadContextProperties s_properties = new LogicalThreadContextProperties();
-
-    /// <summary>
-    /// The thread context stacks instance
-    /// </summary>
-    private static readonly LogicalThreadContextStacks s_stacks = new LogicalThreadContextStacks(s_properties);
-
-    #endregion Private Static Fields
+    public static LogicalThreadContextStacks Stacks { get; } = new(Properties);
   }
 }
\ No newline at end of file
diff --git a/src/log4net/NDC.cs b/src/log4net/NDC.cs
index 9109cc6..ea939c9 100644
--- a/src/log4net/NDC.cs
+++ b/src/log4net/NDC.cs
@@ -44,7 +44,7 @@
   /// come into play.
   /// </para>
   /// <para>
-  /// Note that NDCs are managed on a per thread basis. The NDC class
+  /// Note that NDCs are managed on a per-thread basis. The NDC class
   /// is made up of static methods that operate on the context of the
   /// calling thread.
   /// </para>
@@ -62,24 +62,8 @@
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
   /*[Obsolete("NDC has been replaced by ThreadContext.Stacks")]*/
-  public sealed class NDC
+  public static class NDC
   {
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="NDC" /> class. 
-    /// </summary>
-    /// <remarks>
-    /// Uses a private access modifier to prevent instantiation of this class.
-    /// </remarks>
-    private NDC()
-    {
-    }
-
-    #endregion Private Instance Constructors
-
-    #region Public Static Properties
-
     /// <summary>
     /// Gets the current context depth.
     /// </summary>
@@ -106,10 +90,6 @@
       get { return ThreadContext.Stacks["NDC"].Count; }
     }
 
-    #endregion Public Static Properties
-
-    #region Public Static Methods
-
     /// <summary>
     /// Clears all the contextual information held on the current thread.
     /// </summary>
@@ -201,7 +181,7 @@
     /// </para>
     /// </remarks>
     /*[Obsolete("NDC has been replaced by ThreadContext.Stacks")]*/
-    public static string Pop()
+    public static string? Pop()
     {
       return ThreadContext.Stacks["NDC"].Pop();
     }
@@ -324,7 +304,7 @@
     {
       if (maxDepth >= 0)
       {
-        log4net.Util.ThreadContextStack stack = ThreadContext.Stacks["NDC"];
+        Util.ThreadContextStack stack = ThreadContext.Stacks["NDC"];
 
         if (maxDepth == 0)
         {
@@ -339,7 +319,5 @@
         }
       }
     }
-
-    #endregion Public Static Methods
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Repository/Hierarchy/DefaultLoggerFactory.cs b/src/log4net/Repository/Hierarchy/DefaultLoggerFactory.cs
index 2f55243..3aea766 100644
--- a/src/log4net/Repository/Hierarchy/DefaultLoggerFactory.cs
+++ b/src/log4net/Repository/Hierarchy/DefaultLoggerFactory.cs
@@ -35,55 +35,27 @@
   /// <author>Gert Driesen</author>
   internal class DefaultLoggerFactory : ILoggerFactory
   {
-    #region Internal Instance Constructors
-
     /// <summary>
-    /// Default constructor
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Initializes a new instance of the <see cref="DefaultLoggerFactory" /> class. 
-    /// </para>
-    /// </remarks>
-    internal DefaultLoggerFactory()
-    {
-    }
-
-    #endregion Internal Instance Constructors
-
-    #region Implementation of ILoggerFactory
-
-    /// <summary>
-    /// Create a new <see cref="Logger" /> instance
+    /// Create a new <see cref="Logger" /> instance with the specified name.
     /// </summary>
     /// <param name="repository">The <see cref="ILoggerRepository" /> that will own the <see cref="Logger" />.</param>
-    /// <param name="name">The name of the <see cref="Logger" />.</param>
+    /// <param name="name">The name of the <see cref="Logger" />. If <c>null</c>, the root logger is returned.</param>
     /// <returns>The <see cref="Logger" /> instance for the specified name.</returns>
     /// <remarks>
     /// <para>
-    /// Create a new <see cref="Logger" /> instance with the 
-    /// specified name.
-    /// </para>
-    /// <para>
     /// Called by the <see cref="Hierarchy"/> to create
     /// new named <see cref="Logger"/> instances.
     /// </para>
-    /// <para>
-    /// If the <paramref name="name"/> is <c>null</c> then the root logger
-    /// must be returned.
-    /// </para>
     /// </remarks>
-    public Logger CreateLogger(ILoggerRepository repository, string name)
+    public Logger CreateLogger(ILoggerRepository repository, string? name)
     {
-      if (name == null)
+      if (name is null)
       {
         return new RootLogger(repository.LevelMap.LookupWithDefault(Level.Debug));
       }
       return new LoggerImpl(name);
     }
 
-    #endregion
-
     /// <summary>
     /// Default internal subclass of <see cref="Logger"/>
     /// </summary>
@@ -97,15 +69,10 @@
     internal sealed class LoggerImpl : Logger
     {
       /// <summary>
-      /// Construct a new Logger
-      /// </summary>
-      /// <param name="name">the name of the logger</param>
-      /// <remarks>
-      /// <para>
       /// Initializes a new instance of the <see cref="LoggerImpl" /> class
       /// with the specified name. 
-      /// </para>
-      /// </remarks>
+      /// </summary>
+      /// <param name="name">the name of the logger</param>
       internal LoggerImpl(string name) : base(name)
       {
       }
diff --git a/src/log4net/Repository/Hierarchy/Hierarchy.cs b/src/log4net/Repository/Hierarchy/Hierarchy.cs
index 144b5c4..56dd296 100644
--- a/src/log4net/Repository/Hierarchy/Hierarchy.cs
+++ b/src/log4net/Repository/Hierarchy/Hierarchy.cs
@@ -18,16 +18,15 @@
 #endregion
 
 using System;
-using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
 using log4net.Appender;
 using log4net.Core;
-using log4net.Repository;
 using log4net.Util;
 
 namespace log4net.Repository.Hierarchy
 {
-  #region LoggerCreationEvent
-
   /// <summary>
   /// Delegate used to handle logger creation event notifications.
   /// </summary>
@@ -51,12 +50,7 @@
   /// </remarks>
   public class LoggerCreationEventArgs : EventArgs
   {
-    /// <summary>
-    /// The <see cref="Logger"/> created
-    /// </summary>
-    private Logger m_log;
-
-    /// <summary>
+      /// <summary>
     /// Constructor
     /// </summary>
     /// <param name="log">The <see cref="Logger"/> that has been created.</param>
@@ -68,7 +62,7 @@
     /// </remarks>
     public LoggerCreationEventArgs(Logger log)
     {
-      m_log = log;
+      Logger = log;
     }
 
     /// <summary>
@@ -82,14 +76,9 @@
     /// The <see cref="Logger"/> that has been created.
     /// </para>
     /// </remarks>
-    public Logger Logger
-    {
-      get { return m_log; }
-    }
+    public Logger Logger { get; }
   }
 
-  #endregion LoggerCreationEvent
-
   /// <summary>
   /// Hierarchical organization of loggers
   /// </summary>
@@ -121,8 +110,6 @@
   /// <author>Gert Driesen</author>
   public class Hierarchy : LoggerRepositorySkeleton, IBasicRepositoryConfigurator, IXmlRepositoryConfigurator
   {
-    #region Public Events
-
     /// <summary>
     /// Event used to notify that a logger has been created.
     /// </summary>
@@ -131,15 +118,7 @@
     /// Event raised when a logger is created.
     /// </para>
     /// </remarks>
-    public event LoggerCreationEventHandler LoggerCreatedEvent
-    {
-      add { m_loggerCreatedEvent += value; }
-      remove { m_loggerCreatedEvent -= value; }
-    }
-
-    #endregion Public Events
-
-    #region Public Instance Constructors
+    public event LoggerCreationEventHandler? LoggerCreatedEvent;
 
     /// <summary>
     /// Default constructor
@@ -193,20 +172,14 @@
     /// </remarks>
     public Hierarchy(PropertiesDictionary properties, ILoggerFactory loggerFactory) : base(properties)
     {
-      if (loggerFactory == null)
+      if (loggerFactory is null)
       {
-        throw new ArgumentNullException("loggerFactory");
+        throw new ArgumentNullException(nameof(loggerFactory));
       }
 
       m_defaultFactory = loggerFactory;
-
-      m_ht = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable());
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Has no appender warning been emitted
     /// </summary>
@@ -216,11 +189,7 @@
     /// about not having an appender warning.
     /// </para>
     /// </remarks>
-    public bool EmittedNoAppenderWarning
-    {
-      get { return m_emittedNoAppenderWarning; }
-      set { m_emittedNoAppenderWarning = value; }
-    }
+    public bool EmittedNoAppenderWarning { get; set; }
 
     /// <summary>
     /// Get the root of this hierarchy
@@ -234,11 +203,11 @@
     {
       get
       {
-        if (m_root == null)
+        if (m_root is null)
         {
           lock (this)
           {
-            if (m_root == null)
+            if (m_root is null)
             {
               // Create the root logger
               Logger root = m_defaultFactory.CreateLogger(this, null);
@@ -256,7 +225,6 @@
     /// <summary>
     /// Gets or sets the default <see cref="ILoggerFactory" /> instance.
     /// </summary>
-    /// <value>The default <see cref="ILoggerFactory" /></value>
     /// <remarks>
     /// <para>
     /// The logger factory is used to create logger instances.
@@ -264,21 +232,13 @@
     /// </remarks>
     public ILoggerFactory LoggerFactory
     {
-      get { return m_defaultFactory; }
+      get => m_defaultFactory;
       set
       {
-        if (value == null)
-        {
-          throw new ArgumentNullException("value");
-        }
-        m_defaultFactory = value;
+        m_defaultFactory = value ?? throw new ArgumentNullException(nameof(value));
       }
     }
 
-    #endregion Public Instance Properties
-
-    #region Override Implementation of LoggerRepositorySkeleton
-
     /// <summary>
     /// Test if a logger exists
     /// </summary>
@@ -290,17 +250,15 @@
     /// its reference, otherwise returns <c>null</c>.
     /// </para>
     /// </remarks>
-    public override ILogger Exists(string name)
+    public override ILogger? Exists(string name)
     {
-      if (name == null)
+      if (name is null)
       {
-        throw new ArgumentNullException("name");
+        throw new ArgumentNullException(nameof(name));
       }
 
-      lock (m_ht)
-      {
-        return m_ht[new LoggerKey(name)] as Logger;
-      }
+      m_ht.TryGetValue(new LoggerKey(name), out object? o);
+      return o as Logger;
     }
 
     /// <summary>
@@ -319,20 +277,17 @@
       // The accumulation in loggers is necessary because not all elements in
       // ht are Logger objects as there might be some ProvisionNodes
       // as well.
-      lock (m_ht)
-      {
-        System.Collections.ArrayList loggers = new System.Collections.ArrayList(m_ht.Count);
+      var loggers = new List<ILogger>(m_ht.Count);
 
-        // Iterate through m_ht values
-        foreach (object node in m_ht.Values)
+      // Iterate through m_ht values
+      foreach (object node in m_ht.Values)
+      {
+        if (node is Logger logger)
         {
-          if (node is Logger)
-          {
-            loggers.Add(node);
-          }
+          loggers.Add(logger);
         }
-        return (Logger[])loggers.ToArray(typeof(Logger));
       }
+      return loggers.ToArray();
     }
 
     /// <summary>
@@ -356,7 +311,7 @@
     {
       if (name == null)
       {
-        throw new ArgumentNullException("name");
+        throw new ArgumentNullException(nameof(name));
       }
 
       return GetLogger(name, m_defaultFactory);
@@ -378,34 +333,31 @@
     /// </para>
     /// <para>
     /// The <c>Shutdown</c> method is careful to close nested
-    /// appenders before closing regular appenders. This is allows
+    /// appenders before closing regular appenders. This allows
     /// configurations where a regular appender is attached to a logger
     /// and again to a nested appender.
     /// </para>
     /// </remarks>
     public override void Shutdown()
     {
-      LogLog.Debug(declaringType, "Shutdown called on Hierarchy [" + this.Name + "]");
+      LogLog.Debug(declaringType, $"Shutdown called on Hierarchy [{Name}]");
 
       // begin by closing nested appenders
       Root.CloseNestedAppenders();
 
-      lock (m_ht)
+      ILogger[] currentLoggers = GetCurrentLoggers();
+
+      foreach (Logger logger in currentLoggers.OfType<Logger>())
       {
-        ILogger[] currentLoggers = this.GetCurrentLoggers();
+        logger.CloseNestedAppenders();
+      }
 
-        foreach (Logger logger in currentLoggers)
-        {
-          logger.CloseNestedAppenders();
-        }
+      // then, remove all appenders
+      Root.RemoveAllAppenders();
 
-        // then, remove all appenders
-        Root.RemoveAllAppenders();
-
-        foreach (Logger logger in currentLoggers)
-        {
-          logger.RemoveAllAppenders();
-        }
+      foreach (Logger logger in currentLoggers.OfType<Logger>())
+      {
+        logger.RemoveAllAppenders();
       }
 
       base.Shutdown();
@@ -436,16 +388,12 @@
       Root.Level = LevelMap.LookupWithDefault(Level.Debug);
       Threshold = LevelMap.LookupWithDefault(Level.All);
 
-      // the synchronization is needed to prevent hashtable surprises
-      lock (m_ht)
-      {
-        Shutdown(); // nested locks are OK  
+      Shutdown(); // nested locks are OK  
 
-        foreach (Logger l in this.GetCurrentLoggers())
-        {
-          l.Level = null;
-          l.Additivity = true;
-        }
+      foreach (Logger logger in GetCurrentLoggers().OfType<Logger>())
+      {
+        logger.Level = null;
+        logger.Additivity = true;
       }
 
       base.ResetConfiguration();
@@ -472,12 +420,15 @@
     /// </remarks>
     public override void Log(LoggingEvent logEvent)
     {
-      if (logEvent == null)
+      if (logEvent is null)
       {
-        throw new ArgumentNullException("logEvent");
+        throw new ArgumentNullException(nameof(logEvent));
       }
 
-      this.GetLogger(logEvent.LoggerName, m_defaultFactory).Log(logEvent);
+      if (logEvent.LoggerName is not null)
+      {
+        GetLogger(logEvent.LoggerName, m_defaultFactory).Log(logEvent);
+      }
     }
 
     /// <summary>
@@ -494,38 +445,31 @@
     /// The list returned is unordered but does not contain duplicates.
     /// </para>
     /// </remarks>
-    public override Appender.IAppender[] GetAppenders()
+    public override IAppender[] GetAppenders()
     {
-      System.Collections.ArrayList appenderList = new System.Collections.ArrayList();
+      var appenderList = new List<IAppender>();
 
       CollectAppenders(appenderList, Root);
 
-      foreach (Logger logger in GetCurrentLoggers())
+      foreach (Logger logger in GetCurrentLoggers().OfType<Logger>())
       {
         CollectAppenders(appenderList, logger);
       }
 
-      return (Appender.IAppender[])appenderList.ToArray(typeof(Appender.IAppender));
+      return appenderList.ToArray();
     }
 
-    #endregion Override Implementation of LoggerRepositorySkeleton
-
-    #region Private Static Methods
-
     /// <summary>
     /// Collect the appenders from an <see cref="IAppenderAttachable"/>.
     /// The appender may also be a container.
     /// </summary>
-    /// <param name="appenderList"></param>
-    /// <param name="appender"></param>
-    private static void CollectAppender(System.Collections.ArrayList appenderList, Appender.IAppender appender)
+    private static void CollectAppender(List<IAppender> appenderList, IAppender appender)
     {
       if (!appenderList.Contains(appender))
       {
         appenderList.Add(appender);
 
-        IAppenderAttachable container = appender as IAppenderAttachable;
-        if (container != null)
+        if (appender is IAppenderAttachable container)
         {
           CollectAppenders(appenderList, container);
         }
@@ -535,20 +479,14 @@
     /// <summary>
     /// Collect the appenders from an <see cref="IAppenderAttachable"/> container
     /// </summary>
-    /// <param name="appenderList"></param>
-    /// <param name="container"></param>
-    private static void CollectAppenders(System.Collections.ArrayList appenderList, IAppenderAttachable container)
+    private static void CollectAppenders(List<IAppender> appenderList, IAppenderAttachable container)
     {
-      foreach (Appender.IAppender appender in container.Appenders)
+      foreach (IAppender appender in container.Appenders)
       {
         CollectAppender(appenderList, appender);
       }
     }
 
-    #endregion
-
-    #region Implementation of IBasicRepositoryConfigurator
-
     /// <summary>
     /// Initialize the log4net system using the specified appender
     /// </summary>
@@ -580,7 +518,7 @@
     /// </remarks>
     protected void BasicRepositoryConfigure(params IAppender[] appenders)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
@@ -598,10 +536,6 @@
       OnConfigurationChanged(new ConfigurationChangedEventArgs(configurationMessages));
     }
 
-    #endregion Implementation of IBasicRepositoryConfigurator
-
-    #region Implementation of IXmlRepositoryConfigurator
-
     /// <summary>
     /// Initialize the log4net system using the specified config
     /// </summary>
@@ -624,11 +558,11 @@
     /// </remarks>
     protected void XmlRepositoryConfigure(System.Xml.XmlElement element)
     {
-      ArrayList configurationMessages = new ArrayList();
+      var configurationMessages = new List<LogLog>();
 
       using (new LogLog.LogReceivedAdapter(configurationMessages))
       {
-        XmlHierarchyConfigurator config = new XmlHierarchyConfigurator(this);
+        var config = new XmlHierarchyConfigurator(this);
         config.Configure(element);
       }
 
@@ -640,10 +574,6 @@
       OnConfigurationChanged(new ConfigurationChangedEventArgs(configurationMessages));
     }
 
-    #endregion Implementation of IXmlRepositoryConfigurator
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Test if this hierarchy is disabled for the specified <see cref="Level"/>.
     /// </summary>
@@ -667,10 +597,9 @@
     /// </remarks>
     public bool IsDisabled(Level level)
     {
-      // Cast level to object for performance
-      if ((object)level == null)
+      if (level is null)
       {
-        throw new ArgumentNullException("level");
+        throw new ArgumentNullException(nameof(level));
       }
 
       if (Configured)
@@ -700,10 +629,7 @@
     /// </remarks>
     public void Clear()
     {
-      lock (m_ht)
-      {
-        m_ht.Clear();
-      }
+      m_ht.Clear();
     }
 
     /// <summary>
@@ -725,61 +651,52 @@
     {
       if (name == null)
       {
-        throw new ArgumentNullException("name");
+        throw new ArgumentNullException(nameof(name));
       }
       if (factory == null)
       {
-        throw new ArgumentNullException("factory");
+        throw new ArgumentNullException(nameof(factory));
       }
 
-      LoggerKey key = new LoggerKey(name);
+      var key = new LoggerKey(name);
 
       // Synchronize to prevent write conflicts. Read conflicts (in
       // GetEffectiveLevel() method) are possible only if variable
       // assignments are non-atomic.
 
-      lock (m_ht)
+      if (!m_ht.TryGetValue(key, out object? node))
       {
-        Logger logger = null;
+        return CreateLogger(null);
+      }
 
-        Object node = m_ht[key];
-        if (node == null)
+      if (node is Logger nodeLogger)
+      {
+        return nodeLogger;
+      }
+
+      if (node is ProvisionNode nodeProvisionNode)
+      {
+        return CreateLogger(l => UpdateChildren(nodeProvisionNode, l));
+      }
+
+      // It should be impossible to arrive here but let's keep the compiler happy.
+      return null!;
+
+      Logger CreateLogger(Action<Logger>? extraInit)
+      {
+        // Use GetOrAdd in case the logger was added after checking above.
+        return (Logger)m_ht.GetOrAdd(key, _ =>
         {
-          logger = factory.CreateLogger(this, name);
+          Logger logger = factory.CreateLogger(this, name);
           logger.Hierarchy = this;
-          m_ht[key] = logger;
+          extraInit?.Invoke(logger);
           UpdateParents(logger);
           OnLoggerCreationEvent(logger);
           return logger;
-        }
-
-        Logger nodeLogger = node as Logger;
-        if (nodeLogger != null)
-        {
-          return nodeLogger;
-        }
-
-        ProvisionNode nodeProvisionNode = node as ProvisionNode;
-        if (nodeProvisionNode != null)
-        {
-          logger = factory.CreateLogger(this, name);
-          logger.Hierarchy = this;
-          m_ht[key] = logger;
-          UpdateChildren(nodeProvisionNode, logger);
-          UpdateParents(logger);
-          OnLoggerCreationEvent(logger);
-          return logger;
-        }
-
-        // It should be impossible to arrive here but let's keep the compiler happy.
-        return null;
+        });
       }
     }
 
-    #endregion Public Instance Methods
-
-    #region Protected Instance Methods
-
     /// <summary>
     /// Sends a logger creation event to all registered listeners
     /// </summary>
@@ -789,17 +706,9 @@
     /// </remarks>
     protected virtual void OnLoggerCreationEvent(Logger logger)
     {
-      LoggerCreationEventHandler handler = m_loggerCreatedEvent;
-      if (handler != null)
-      {
-        handler(this, new LoggerCreationEventArgs(logger));
-      }
+      LoggerCreatedEvent?.Invoke(this, new LoggerCreationEventArgs(logger));
     }
 
-    #endregion Protected Instance Methods
-
-    #region Private Instance Methods
-
     /// <summary>
     /// Updates all the parents of the specified logger
     /// </summary>
@@ -822,7 +731,7 @@
     ///      <description>
     ///      The entry is <paramref name="log"/>'s nearest existing parent. We 
     ///      update <paramref name="log"/>'s parent field with this entry. We also break from 
-    ///      he loop because updating our parent's parent is our parent's 
+    ///      the loop because updating our parent's parent is our parent's 
     ///      responsibility.
     ///      </description>
     ///    </item>
@@ -846,40 +755,35 @@
       {
         string substr = name.Substring(0, i);
 
-        LoggerKey key = new LoggerKey(substr); // simple constructor
-        Object node = m_ht[key];
+        var key = new LoggerKey(substr);
+        m_ht.TryGetValue(key, out object? node);
+
         // Create a provision node for a future parent.
-        if (node == null)
+        if (node is null)
         {
-          ProvisionNode pn = new ProvisionNode(log);
-          m_ht[key] = pn;
+          m_ht[key] = new ProvisionNode(log);
         }
         else
         {
-          Logger nodeLogger = node as Logger;
-          if (nodeLogger != null)
+          if (node is Logger nodeLogger)
           {
             parentFound = true;
             log.Parent = nodeLogger;
             break; // no need to update the ancestors of the closest ancestor
           }
+
+          if (node is ProvisionNode nodeProvisionNode)
+          {
+            nodeProvisionNode.Add(log);
+          }
           else
           {
-            ProvisionNode nodeProvisionNode = node as ProvisionNode;
-            if (nodeProvisionNode != null)
-            {
-              nodeProvisionNode.Add(log);
-            }
-            else
-            {
-              LogLog.Error(declaringType, "Unexpected object type [" + node.GetType() + "] in ht.", new LogException());
-            }
+            LogLog.Error(declaringType, $"Unexpected object type [{node.GetType()}] in ht.", new LogException());
           }
         }
         if (i == 0)
         {
-          // logger name starts with a dot
-          // and we've hit the start
+          // logger name starts with a dot and we've hit the start
           break;
         }
       }
@@ -887,15 +791,13 @@
       // If we could not find any existing parents, then link with root.
       if (!parentFound)
       {
-        log.Parent = this.Root;
+        log.Parent = Root;
       }
     }
 
     /// <summary>
     /// Replace a <see cref="ProvisionNode"/> with a <see cref="Logger"/> in the hierarchy.
     /// </summary>
-    /// <param name="pn"></param>
-    /// <param name="log"></param>
     /// <remarks>
     /// <para>
     /// We update the links for all the children that placed themselves
@@ -917,13 +819,11 @@
     /// </remarks>
     private static void UpdateChildren(ProvisionNode pn, Logger log)
     {
-      for (int i = 0; i < pn.Count; i++)
+      foreach (Logger childLogger in pn)
       {
-        Logger childLogger = (Logger)pn[i];
-
         // Unless this child already points to a correct (lower) parent,
         // make log.Parent point to childLogger.Parent and childLogger.Parent to log.
-        if (!childLogger.Parent.Name.StartsWith(log.Name))
+        if (childLogger.Parent is not null && !childLogger.Parent.Name.StartsWith(log.Name, StringComparison.Ordinal))
         {
           log.Parent = childLogger.Parent;
           childLogger.Parent = log;
@@ -937,27 +837,31 @@
     /// <param name="levelEntry">the level values</param>
     /// <remarks>
     /// <para>
-    /// Define or redefine a Level using the values in the <see cref="LevelEntry"/> argument
-    /// </para>
-    /// <para>
     /// Supports setting levels via the configuration file.
     /// </para>
     /// </remarks>
     internal void AddLevel(LevelEntry levelEntry)
     {
-      if (levelEntry == null) throw new ArgumentNullException("levelEntry");
-      if (levelEntry.Name == null) throw new ArgumentNullException("levelEntry.Name");
+      if (levelEntry is null)
+      {
+        throw new ArgumentNullException(nameof(levelEntry));
+      }
+      if (levelEntry.Name is null)
+      {
+        throw new ArgumentNullException("levelEntry.Name");
+      }
 
       // Lookup replacement value
       if (levelEntry.Value == -1)
       {
-        Level previousLevel = LevelMap[levelEntry.Name];
-        if (previousLevel == null)
+        if (LevelMap[levelEntry.Name] is Level previousLevel)
         {
-          throw new InvalidOperationException("Cannot redefine level [" + levelEntry.Name + "] because it is not defined in the LevelMap. To define the level supply the level value.");
+          levelEntry.Value = previousLevel.Value;
         }
-
-        levelEntry.Value = previousLevel.Value;
+        else
+        {
+          throw new InvalidOperationException($"Cannot redefine level [{levelEntry.Name}] because it is not defined in the LevelMap. To define the level supply the level value.");
+        }
       }
 
       LevelMap.Add(levelEntry.Name, levelEntry.Value, levelEntry.DisplayName);
@@ -971,12 +875,8 @@
     /// A class to hold the value, name and display name for a level
     /// </para>
     /// </remarks>
-    internal class LevelEntry
+    internal sealed class LevelEntry
     {
-      private int m_levelValue = -1;
-      private string m_levelName = null;
-      private string m_levelDisplayName = null;
-
       /// <summary>
       /// Value of the level
       /// </summary>
@@ -986,11 +886,7 @@
       /// up for the current level with the same name.
       /// </para>
       /// </remarks>
-      public int Value
-      {
-        get { return m_levelValue; }
-        set { m_levelValue = value; }
-      }
+      public int Value { get; set; } = -1;
 
       /// <summary>
       /// Name of the level
@@ -1003,11 +899,7 @@
       /// The name of the level.
       /// </para>
       /// </remarks>
-      public string Name
-      {
-        get { return m_levelName; }
-        set { m_levelName = value; }
-      }
+      public string? Name { get; set; }
 
       /// <summary>
       /// Display name for the level
@@ -1020,11 +912,7 @@
       /// The display name of the level.
       /// </para>
       /// </remarks>
-      public string DisplayName
-      {
-        get { return m_levelDisplayName; }
-        set { m_levelDisplayName = value; }
-      }
+      public string? DisplayName { get; set; }
 
       /// <summary>
       /// Override <c>Object.ToString</c> to return sensible debug info
@@ -1032,7 +920,7 @@
       /// <returns>string info about this object</returns>
       public override string ToString()
       {
-        return "LevelEntry(Value=" + m_levelValue + ", Name=" + m_levelName + ", DisplayName=" + m_levelDisplayName + ")";
+        return $"LevelEntry(Value={Value}, Name={Name}, DisplayName={DisplayName})";
       }
     }
 
@@ -1050,28 +938,22 @@
     /// </remarks>
     internal void AddProperty(PropertyEntry propertyEntry)
     {
-      if (propertyEntry == null) throw new ArgumentNullException("propertyEntry");
-      if (propertyEntry.Key == null) throw new ArgumentNullException("propertyEntry.Key");
+      if (propertyEntry is null)
+      {
+        throw new ArgumentNullException(nameof(propertyEntry));
+      }
+      if (propertyEntry.Key is null)
+      {
+        throw new ArgumentNullException("propertyEntry.Key");
+      }
 
       Properties[propertyEntry.Key] = propertyEntry.Value;
     }
 
-    #endregion Private Instance Methods
-
-    #region Private Instance Fields
-
     private ILoggerFactory m_defaultFactory;
 
-    private System.Collections.Hashtable m_ht;
-    private Logger m_root;
-
-    private bool m_emittedNoAppenderWarning = false;
-
-    private event LoggerCreationEventHandler m_loggerCreatedEvent;
-
-    #endregion Private Instance Fields
-
-    #region Private Static Fields
+    private readonly ConcurrentDictionary<LoggerKey, object> m_ht = new(LoggerKey.ComparerInstance);
+    private Logger? m_root;
 
     /// <summary>
     /// The fully qualified type of the Hierarchy class.
@@ -1081,7 +963,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(Hierarchy);
-
-    #endregion Private Static Fields
   }
 }
diff --git a/src/log4net/Repository/Hierarchy/ILoggerFactory.cs b/src/log4net/Repository/Hierarchy/ILoggerFactory.cs
index 9826a97..5720d4e 100644
--- a/src/log4net/Repository/Hierarchy/ILoggerFactory.cs
+++ b/src/log4net/Repository/Hierarchy/ILoggerFactory.cs
@@ -59,6 +59,6 @@
     /// must be returned.
     /// </para>
     /// </remarks>
-    Logger CreateLogger(ILoggerRepository repository, string name);
+    Logger CreateLogger(ILoggerRepository repository, string? name);
   }
 }
diff --git a/src/log4net/Repository/Hierarchy/Logger.cs b/src/log4net/Repository/Hierarchy/Logger.cs
index 255a3b5..e04da5b 100644
--- a/src/log4net/Repository/Hierarchy/Logger.cs
+++ b/src/log4net/Repository/Hierarchy/Logger.cs
@@ -142,7 +142,6 @@
     /// Gets or sets the <see cref="Hierarchy"/> where this 
     /// <c>Logger</c> instance is attached to.
     /// </summary>
-    /// <value>The hierarchy that this logger belongs to.</value>
     /// <remarks>
     /// <para>
     /// This logger must be attached to a single <see cref="Hierarchy"/>.
@@ -160,11 +159,6 @@
     /// <value>
     /// The <see cref="Level"/> of this logger.
     /// </value>
-    /// <remarks>
-    /// <para>
-    /// The assigned <see cref="Level"/> can be <c>null</c>.
-    /// </para>
-    /// </remarks>
     public virtual Level? Level { get; set; }
 
     /// <summary>
@@ -174,10 +168,6 @@
     /// <param name="newAppender">An appender to add to this logger</param>
     /// <remarks>
     /// <para>
-    /// Add <paramref name="newAppender"/> to the list of appenders of this
-    /// Logger instance.
-    /// </para>
-    /// <para>
     /// If <paramref name="newAppender"/> is already in the list of
     /// appenders, then it won't be added again.
     /// </para>
@@ -205,14 +195,10 @@
     /// Get the appenders contained in this logger as an 
     /// <see cref="System.Collections.ICollection"/>.
     /// </summary>
-    /// <returns>A collection of the appenders in this logger</returns>
-    /// <remarks>
-    /// <para>
-    /// Get the appenders contained in this logger as an 
-    /// <see cref="System.Collections.ICollection"/>. If no appenders 
+    /// <returns>
+    /// A collection of the appenders in this logger. If no appenders 
     /// can be found, then a <see cref="EmptyCollection"/> is returned.
-    /// </para>
-    /// </remarks>
+    /// </returns>
     public virtual AppenderCollection Appenders 
     {
       get
@@ -241,11 +227,6 @@
     /// </summary>
     /// <param name="name">The name of the appender to lookup</param>
     /// <returns>The appender with the name specified, or <c>null</c>.</returns>
-    /// <remarks>
-    /// <para>
-    /// Returns the named appender, or null if the appender is not found.
-    /// </para>
-    /// </remarks>
     public virtual IAppender? GetAppender(string? name) 
     {
       m_appenderLock.AcquireReaderLock();
@@ -265,13 +246,10 @@
     }
 
     /// <summary>
-    /// Remove all previously added appenders from this Logger instance.
+    /// Removes all previously added appenders from this Logger instance.
     /// </summary>
     /// <remarks>
     /// <para>
-    /// Remove all previously added appenders from this Logger instance.
-    /// </para>
-    /// <para>
     /// This is useful when re-reading configuration information.
     /// </para>
     /// </remarks>
@@ -299,7 +277,6 @@
     /// <returns>The appender removed from the list</returns>
     /// <remarks>
     /// <para>
-    /// Remove the appender passed as parameter form the list of appenders.
     /// The appender removed is not closed.
     /// If you are discarding the appender you must call
     /// <see cref="IAppender.Close"/> on the appender removed.
@@ -329,7 +306,6 @@
     /// <returns>The appender removed from the list</returns>
     /// <remarks>
     /// <para>
-    /// Remove the named appender passed as parameter form the list of appenders.
     /// The appender removed is not closed.
     /// If you are discarding the appender you must call
     /// <see cref="IAppender.Close"/> on the appender removed.
@@ -355,18 +331,11 @@
     /// <summary>
     /// Gets the logger name.
     /// </summary>
-    /// <value>
-    /// The name of the logger.
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// The name of this logger
-    /// </para>
-    /// </remarks>
     public virtual string Name { get; }
 
     /// <summary>
-    /// This generic form is intended to be used by wrappers.
+    /// Generates a logging event for the specified <paramref name="level"/> using
+    /// the <paramref name="message"/> and <paramref name="exception"/>.
     /// </summary>
     /// <param name="callerStackBoundaryDeclaringType">The declaring type of the method that is
     /// the stack boundary into the logging system for this call.</param>
@@ -375,8 +344,7 @@
     /// <param name="exception">The exception to log, including its stack trace.</param>
     /// <remarks>
     /// <para>
-    /// Generate a logging event for the specified <paramref name="level"/> using
-    /// the <paramref name="message"/> and <paramref name="exception"/>.
+    /// This generic form is intended to be used by wrappers.
     /// </para>
     /// <para>
     /// This method must not throw any exception to the caller.
@@ -398,13 +366,13 @@
     }
 
     /// <summary>
-    /// This is the most generic printing method that is intended to be used 
-    /// by wrappers.
+    /// Logs the specified logging event through this logger.
     /// </summary>
     /// <param name="logEvent">The event being logged.</param>
     /// <remarks>
     /// <para>
-    /// Logs the specified logging event through this logger.
+    /// This is the most generic printing method that is intended to be used 
+    /// by wrappers.
     /// </para>
     /// <para>
     /// This method must not throw any exception to the caller.
@@ -437,9 +405,6 @@
     /// </returns>
     /// <remarks>
     /// <para>
-    /// Test if this logger is going to log events of the specified <paramref name="level"/>.
-    /// </para>
-    /// <para>
     /// This method must not throw any exception to the caller.
     /// </para>
     /// </remarks>
@@ -467,15 +432,6 @@
     /// Gets the <see cref="ILoggerRepository"/> where this 
     /// <c>Logger</c> instance is attached to.
     /// </summary>
-    /// <value>
-    /// The <see cref="ILoggerRepository" /> that this logger belongs to.
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Gets the <see cref="ILoggerRepository"/> where this 
-    /// <c>Logger</c> instance is attached to.
-    /// </para>
-    /// </remarks>
     public ILoggerRepository? Repository => m_hierarchy;
 
     /// <summary>
diff --git a/src/log4net/Repository/Hierarchy/LoggerKey.cs b/src/log4net/Repository/Hierarchy/LoggerKey.cs
index ee72165..de16cbc 100644
--- a/src/log4net/Repository/Hierarchy/LoggerKey.cs
+++ b/src/log4net/Repository/Hierarchy/LoggerKey.cs
@@ -18,6 +18,7 @@
 #endregion
 
 using System;
+using System.Collections.Generic;
 
 namespace log4net.Repository.Hierarchy
 {
@@ -38,10 +39,8 @@
   /// </remarks>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  internal sealed class LoggerKey
+  internal readonly struct LoggerKey
   {
-    #region Internal Instance Constructors
-
     /// <summary>
     /// Construct key with string name
     /// </summary>
@@ -68,10 +67,6 @@
       m_hashCache = name.GetHashCode();
     }
 
-    #endregion Internal Instance Constructors
-
-    #region Override implementation of Object
-
     /// <summary>
     /// Returns a hash code for the current instance.
     /// </summary>
@@ -86,43 +81,19 @@
       return m_hashCache;
     }
 
-    /// <summary>
-    /// Determines whether two <see cref="LoggerKey" /> instances 
-    /// are equal.
-    /// </summary>
-    /// <param name="obj">The <see cref="object" /> to compare with the current <see cref="LoggerKey" />.</param>
-    /// <returns>
-    /// <c>true</c> if the specified <see cref="object" /> is equal to the current <see cref="LoggerKey" />; otherwise, <c>false</c>.
-    /// </returns>
-    /// <remarks>
-    /// <para>
-    /// Compares the references of the interned strings.
-    /// </para>
-    /// </remarks>
-    public override bool Equals(object obj)
-    {
-      // Compare reference type of this against argument
-      if (((object)this) == obj)
-      {
-        return true;
-      }
-
-      LoggerKey objKey = obj as LoggerKey;
-      if (objKey != null)
-      {
-        // Compare reference types rather than string's overloaded ==
-        return (((object)m_name) == ((object)objKey.m_name));
-      }
-      return false;
-    }
-
-    #endregion
-
-    #region Private Instance Fields
-
     private readonly string m_name;
     private readonly int m_hashCache;
 
-    #endregion Private Instance Fields
+    public static Comparer ComparerInstance { get; } = new();
+
+    public sealed class Comparer : IEqualityComparer<LoggerKey>
+    {
+      public bool Equals(LoggerKey x, LoggerKey y)
+      {
+        return x.m_hashCache == y.m_hashCache && x.m_name == y.m_name;
+      }
+
+      public int GetHashCode(LoggerKey obj) => obj.m_hashCache;
+    }
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Repository/Hierarchy/ProvisionNode.cs b/src/log4net/Repository/Hierarchy/ProvisionNode.cs
index 9262a47..4762851 100644
--- a/src/log4net/Repository/Hierarchy/ProvisionNode.cs
+++ b/src/log4net/Repository/Hierarchy/ProvisionNode.cs
@@ -17,8 +17,7 @@
 //
 #endregion
 
-using System;
-using System.Collections;
+using System.Collections.Generic;
 
 namespace log4net.Repository.Hierarchy
 {
@@ -38,7 +37,7 @@
   /// </remarks>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  internal sealed class ProvisionNode : ArrayList
+  internal sealed class ProvisionNode : List<Logger>
   {
     /// <summary>
     /// Create a new provision node with child node
@@ -50,9 +49,9 @@
     /// with the specified child logger.
     /// </para>
     /// </remarks>
-    internal ProvisionNode(Logger log) : base()
+    internal ProvisionNode(Logger log)
     {
-      this.Add(log);
+      Add(log);
     }
   }
 }
diff --git a/src/log4net/Repository/Hierarchy/XmlHierarchyConfigurator.cs b/src/log4net/Repository/Hierarchy/XmlHierarchyConfigurator.cs
index 7f149ac..a1d7769 100644
--- a/src/log4net/Repository/Hierarchy/XmlHierarchyConfigurator.cs
+++ b/src/log4net/Repository/Hierarchy/XmlHierarchyConfigurator.cs
@@ -19,6 +19,7 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Globalization;
 using System.Reflection;
 using System.Xml;
@@ -48,8 +49,6 @@
       Overwrite
     }
 
-    #region Public Instance Constructors
-
     /// <summary>
     /// Construct the configurator for a hierarchy
     /// </summary>
@@ -63,13 +62,8 @@
     public XmlHierarchyConfigurator(Hierarchy hierarchy)
     {
       m_hierarchy = hierarchy;
-      m_appenderBag = new Hashtable();
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Configure the hierarchy by parsing a DOM tree of XML elements.
     /// </summary>
@@ -79,9 +73,9 @@
     /// Configure the hierarchy by parsing a DOM tree of XML elements.
     /// </para>
     /// </remarks>
-    public void Configure(XmlElement element)
+    public void Configure(XmlElement? element)
     {
-      if (element == null || m_hierarchy == null)
+      if (element is null)
       {
         return;
       }
@@ -90,7 +84,7 @@
 
       if (rootElementName != CONFIGURATION_TAG)
       {
-        LogLog.Error(declaringType, "Xml element is - not a <" + CONFIGURATION_TAG + "> element.");
+        LogLog.Error(declaringType, $"Xml element is - not a <{CONFIGURATION_TAG}> element.");
         return;
       }
 
@@ -98,7 +92,7 @@
       {
         // Look for a emitDebug attribute to enable internal debug
         string emitDebugAttribute = element.GetAttribute(EMIT_INTERNAL_DEBUG_ATTR);
-        LogLog.Debug(declaringType, EMIT_INTERNAL_DEBUG_ATTR + " attribute [" + emitDebugAttribute + "].");
+        LogLog.Debug(declaringType, $"{EMIT_INTERNAL_DEBUG_ATTR} attribute [{emitDebugAttribute}].");
 
         if (emitDebugAttribute.Length > 0 && emitDebugAttribute != "null")
         {
@@ -106,7 +100,7 @@
         }
         else
         {
-          LogLog.Debug(declaringType, "Ignoring " + EMIT_INTERNAL_DEBUG_ATTR + " attribute.");
+          LogLog.Debug(declaringType, $"Ignoring {EMIT_INTERNAL_DEBUG_ATTR} attribute.");
         }
       }
 
@@ -114,7 +108,7 @@
       {
         // Look for a debug attribute to enable internal debug
         string debugAttribute = element.GetAttribute(INTERNAL_DEBUG_ATTR);
-        LogLog.Debug(declaringType, INTERNAL_DEBUG_ATTR + " attribute [" + debugAttribute + "].");
+        LogLog.Debug(declaringType, $"{INTERNAL_DEBUG_ATTR} attribute [{debugAttribute}].");
 
         if (debugAttribute.Length > 0 && debugAttribute != "null")
         {
@@ -122,38 +116,45 @@
         }
         else
         {
-          LogLog.Debug(declaringType, "Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
+          LogLog.Debug(declaringType, $"Ignoring {INTERNAL_DEBUG_ATTR} attribute.");
         }
 
         string confDebug = element.GetAttribute(CONFIG_DEBUG_ATTR);
         if (confDebug.Length > 0 && confDebug != "null")
         {
-          LogLog.Warn(declaringType, "The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
-          LogLog.Warn(declaringType, "Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
+          LogLog.Warn(declaringType, $"The \"{CONFIG_DEBUG_ATTR}\" attribute is deprecated.");
+          LogLog.Warn(declaringType, $"Use the \"{INTERNAL_DEBUG_ATTR}\" attribute instead.");
           LogLog.InternalDebugging = OptionConverter.ToBoolean(confDebug, true);
         }
       }
 
       // Default mode is merge
-      ConfigUpdateMode configUpdateMode = ConfigUpdateMode.Merge;
+      ConfigUpdateMode? configUpdateMode = ConfigUpdateMode.Merge;
 
       // Look for the config update attribute
-      string configUpdateModeAttribute = element.GetAttribute(CONFIG_UPDATE_MODE_ATTR);
-      if (configUpdateModeAttribute != null && configUpdateModeAttribute.Length > 0)
+      string? configUpdateModeAttribute = element.GetAttribute(CONFIG_UPDATE_MODE_ATTR);
+      if (!string.IsNullOrEmpty(configUpdateModeAttribute))
       {
         // Parse the attribute
         try
         {
-          configUpdateMode = (ConfigUpdateMode)OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute);
+          if (OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute) is ConfigUpdateMode mode)
+          {
+            configUpdateMode = mode;
+          }
+          else
+          {
+            LogLog.Error(declaringType, $"Invalid {CONFIG_UPDATE_MODE_ATTR} attribute value [{configUpdateModeAttribute}]");
+          }
         }
         catch
         {
-          LogLog.Error(declaringType, "Invalid " + CONFIG_UPDATE_MODE_ATTR + " attribute value [" + configUpdateModeAttribute + "]");
+          LogLog.Error(declaringType, $"Invalid {CONFIG_UPDATE_MODE_ATTR} attribute value [{configUpdateModeAttribute}]");
         }
       }
 
       // IMPL: The IFormatProvider argument to Enum.ToString() is deprecated in .NET 2.0
-      LogLog.Debug(declaringType, "Configuration update mode [" + configUpdateMode.ToString() + "].");
+      LogLog.Debug(declaringType, $"Configuration update mode [{configUpdateMode}].");
 
       // Only reset configuration if overwrite flag specified
       if (configUpdateMode == ConfigUpdateMode.Overwrite)
@@ -206,27 +207,23 @@
 
       // Lastly set the hierarchy threshold
       string thresholdStr = element.GetAttribute(THRESHOLD_ATTR);
-      LogLog.Debug(declaringType, "Hierarchy Threshold [" + thresholdStr + "]");
+      LogLog.Debug(declaringType, $"Hierarchy Threshold [{thresholdStr}]");
       if (thresholdStr.Length > 0 && thresholdStr != "null")
       {
         Level thresholdLevel = (Level)ConvertStringTo(typeof(Level), thresholdStr);
-        if (thresholdLevel != null)
+        if (thresholdLevel is not null)
         {
           m_hierarchy.Threshold = thresholdLevel;
         }
         else
         {
-          LogLog.Warn(declaringType, "Unable to set hierarchy threshold using value [" + thresholdStr + "] (with acceptable conversion types)");
+          LogLog.Warn(declaringType, $"Unable to set hierarchy threshold using value [{thresholdStr}] (with acceptable conversion types)");
         }
       }
 
       // Done reading config
     }
 
-    #endregion Public Instance Methods
-
-    #region Protected Instance Methods
-
     /// <summary>
     /// Parse appenders by IDREF.
     /// </summary>
@@ -238,47 +235,42 @@
     /// the appender.
     /// </para>
     /// </remarks>
-    protected IAppender FindAppenderByReference(XmlElement appenderRef)
+    protected IAppender? FindAppenderByReference(XmlElement appenderRef)
     {
-      string appenderName = appenderRef.GetAttribute(REF_ATTR);
+      string? appenderName = appenderRef.GetAttribute(REF_ATTR);
 
-      IAppender appender = (IAppender)m_appenderBag[appenderName];
-      if (appender != null)
+      if (m_appenderBag.TryGetValue(appenderName, out IAppender? appender))
       {
         return appender;
       }
-      else
+
+      // Find the element with that id
+      XmlElement? element = null;
+
+      if (!string.IsNullOrEmpty(appenderName) && appenderRef.OwnerDocument is not null)
       {
-        // Find the element with that id
-        XmlElement element = null;
-
-        if (appenderName != null && appenderName.Length > 0)
+        foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(APPENDER_TAG))
         {
-          foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(APPENDER_TAG))
+          if (curAppenderElement.GetAttribute("name") == appenderName)
           {
-            if (curAppenderElement.GetAttribute("name") == appenderName)
-            {
-              element = curAppenderElement;
-              break;
-            }
+            element = curAppenderElement;
+            break;
           }
         }
-
-        if (element == null)
-        {
-          LogLog.Error(declaringType, "XmlHierarchyConfigurator: No appender named [" + appenderName + "] could be found.");
-          return null;
-        }
-        else
-        {
-          appender = ParseAppender(element);
-          if (appender != null)
-          {
-            m_appenderBag[appenderName] = appender;
-          }
-          return appender;
-        }
       }
+
+      if (element is null)
+      {
+        LogLog.Error(declaringType, $"XmlHierarchyConfigurator: No appender named [{appenderName}] could be found.");
+        return null;
+      }
+
+      appender = ParseAppender(element);
+      if (appender is not null)
+      {
+        m_appenderBag[appenderName] = appender;
+      }
+      return appender;
     }
 
     /// <summary>
@@ -292,20 +284,20 @@
     /// the appender instance.
     /// </para>
     /// </remarks>
-    protected IAppender ParseAppender(XmlElement appenderElement)
+    protected IAppender? ParseAppender(XmlElement appenderElement)
     {
       string appenderName = appenderElement.GetAttribute(NAME_ATTR);
       string typeName = appenderElement.GetAttribute(TYPE_ATTR);
 
-      LogLog.Debug(declaringType, "Loading Appender [" + appenderName + "] type: [" + typeName + "]");
+      LogLog.Debug(declaringType, $"Loading Appender [{appenderName}] type: [{typeName}]");
       try
       {
-        IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true));
+        IAppender appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true)!);
         appender.Name = appenderName;
 
         foreach (XmlNode currentNode in appenderElement.ChildNodes)
         {
-          /* We're only interested in Elements */
+          // We're only interested in Elements
           if (currentNode.NodeType == XmlNodeType.Element)
           {
             XmlElement currentElement = (XmlElement)currentNode;
@@ -315,20 +307,19 @@
             {
               string refName = currentElement.GetAttribute(REF_ATTR);
 
-              IAppenderAttachable appenderContainer = appender as IAppenderAttachable;
-              if (appenderContainer != null)
+              if (appender is IAppenderAttachable appenderContainer)
               {
-                LogLog.Debug(declaringType, "Attaching appender named [" + refName + "] to appender named [" + appender.Name + "].");
+                LogLog.Debug(declaringType, $"Attaching appender named [{refName}] to appender named [{appender.Name}].");
 
                 IAppender referencedAppender = FindAppenderByReference(currentElement);
-                if (referencedAppender != null)
+                if (referencedAppender is not null)
                 {
                   appenderContainer.AddAppender(referencedAppender);
                 }
               }
               else
               {
-                LogLog.Error(declaringType, "Requesting attachment of appender named [" + refName + "] to appender named [" + appender.Name + "] which does not implement log4net.Core.IAppenderAttachable.");
+                LogLog.Error(declaringType, $"Requesting attachment of appender named [{refName}] to appender named [{appender.Name}] which does not implement log4net.Core.IAppenderAttachable.");
               }
             }
             else
@@ -339,20 +330,19 @@
           }
         }
 
-        IOptionHandler optionHandler = appender as IOptionHandler;
-        if (optionHandler != null)
+        if (appender is IOptionHandler optionHandler)
         {
           optionHandler.ActivateOptions();
         }
 
-        LogLog.Debug(declaringType, "Created Appender [" + appenderName + "]");
+        LogLog.Debug(declaringType, $"Created Appender [{appenderName}]");
         return appender;
       }
       catch (Exception ex)
       {
         // Yes, it's ugly.  But all exceptions point to the same problem: we can't create an Appender
 
-        LogLog.Error(declaringType, "Could not create Appender [" + appenderName + "] of type [" + typeName + "]. Reported error follows.", ex);
+        LogLog.Error(declaringType, $"Could not create Appender [{appenderName}] of type [{typeName}]. Reported error follows.", ex);
         return null;
       }
     }
@@ -371,19 +361,21 @@
       // Create a new log4net.Logger object from the <logger> element.
       string loggerName = loggerElement.GetAttribute(NAME_ATTR);
 
-      LogLog.Debug(declaringType, "Retrieving an instance of log4net.Repository.Logger for logger [" + loggerName + "].");
-      Logger log = m_hierarchy.GetLogger(loggerName) as Logger;
+      LogLog.Debug(declaringType, $"Retrieving an instance of log4net.Repository.Logger for logger [{loggerName}].");
 
       // Setting up a logger needs to be an atomic operation, in order
       // to protect potential log operations while logger
       // configuration is in progress.
-      lock (log)
+      if (m_hierarchy.GetLogger(loggerName) is Logger log)
       {
-        bool additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(ADDITIVITY_ATTR), true);
+        lock (log)
+        {
+          bool additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(ADDITIVITY_ATTR), true);
 
-        LogLog.Debug(declaringType, "Setting [" + log.Name + "] additivity to [" + additivity + "].");
-        log.Additivity = additivity;
-        ParseChildrenOfLoggerElement(loggerElement, log, false);
+          LogLog.Debug(declaringType, $"Setting [{log.Name}] additivity to [{additivity}].");
+          log.Additivity = additivity;
+          ParseChildrenOfLoggerElement(loggerElement, log, false);
+        }
       }
     }
 
@@ -433,17 +425,17 @@
           {
             IAppender appender = FindAppenderByReference(currentElement);
             string refName = currentElement.GetAttribute(REF_ATTR);
-            if (appender != null)
+            if (appender is not null)
             {
-              LogLog.Debug(declaringType, "Adding appender named [" + refName + "] to logger [" + log.Name + "].");
+              LogLog.Debug(declaringType, $"Adding appender named [{refName}] to logger [{log.Name}].");
               log.AddAppender(appender);
             }
             else
             {
-              LogLog.Error(declaringType, "Appender named [" + refName + "] not found.");
+              LogLog.Error(declaringType, $"Appender named [{refName}] not found.");
             }
           }
-          else if (currentElement.LocalName == LEVEL_TAG || currentElement.LocalName == PRIORITY_TAG)
+          else if (currentElement.LocalName is LEVEL_TAG or PRIORITY_TAG)
           {
             ParseLevel(currentElement, log, isRoot);
           }
@@ -454,8 +446,7 @@
         }
       }
 
-      IOptionHandler optionHandler = log as IOptionHandler;
-      if (optionHandler != null)
+      if (log is IOptionHandler optionHandler)
       {
         optionHandler.ActivateOptions();
       }
@@ -475,23 +466,21 @@
       string renderingClassName = element.GetAttribute(RENDERING_TYPE_ATTR);
       string renderedClassName = element.GetAttribute(RENDERED_TYPE_ATTR);
 
-      LogLog.Debug(declaringType, "Rendering class [" + renderingClassName + "], Rendered class [" + renderedClassName + "].");
+      LogLog.Debug(declaringType, $"Rendering class [{renderingClassName}], Rendered class [{renderedClassName}].");
       IObjectRenderer renderer = (IObjectRenderer)OptionConverter.InstantiateByClassName(renderingClassName, typeof(IObjectRenderer), null);
-      if (renderer == null)
+      if (renderer is null)
       {
-        LogLog.Error(declaringType, "Could not instantiate renderer [" + renderingClassName + "].");
+        LogLog.Error(declaringType, $"Could not instantiate renderer [{renderingClassName}].");
         return;
       }
-      else
+
+      try
       {
-        try
-        {
-          m_hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true), renderer);
-        }
-        catch (Exception e)
-        {
-          LogLog.Error(declaringType, "Could not find class [" + renderedClassName + "].", e);
-        }
+        m_hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true)!, renderer);
+      }
+      catch (Exception e)
+      {
+        LogLog.Error(declaringType, $"Could not find class [{renderedClassName}].", e);
       }
     }
 
@@ -515,7 +504,7 @@
       }
 
       string levelStr = element.GetAttribute(VALUE_ATTR);
-      LogLog.Debug(declaringType, "Logger [" + loggerName + "] Level string is [" + levelStr + "].");
+      LogLog.Debug(declaringType, $"Logger [{loggerName}] Level string is [{levelStr}].");
 
       if (INHERITED == levelStr)
       {
@@ -525,20 +514,20 @@
         }
         else
         {
-          LogLog.Debug(declaringType, "Logger [" + loggerName + "] level set to inherit from parent.");
+          LogLog.Debug(declaringType, $"Logger [{loggerName}] level set to inherit from parent.");
           log.Level = null;
         }
       }
       else
       {
-        log.Level = log.Hierarchy.LevelMap[levelStr];
-        if (log.Level == null)
+        log.Level = log.Hierarchy?.LevelMap[levelStr];
+        if (log.Level is null)
         {
-          LogLog.Error(declaringType, "Undefined level [" + levelStr + "] on Logger [" + loggerName + "].");
+          LogLog.Error(declaringType, $"Undefined level [{levelStr}] on Logger [{loggerName}].");
         }
         else
         {
-          LogLog.Debug(declaringType, "Logger [" + loggerName + "] level set to [name=\"" + log.Level.Name + "\",value=" + log.Level.Value + "].");
+          LogLog.Debug(declaringType, $"Logger [{loggerName}] level set to [name=\"{log.Level.Name}\",value={log.Level.Value}].");
         }
       }
     }
@@ -565,21 +554,20 @@
       string name = element.GetAttribute(NAME_ATTR);
 
       // If the name attribute does not exist then use the name of the element
-      if (element.LocalName != PARAM_TAG || name == null || name.Length == 0)
+      if (element.LocalName != PARAM_TAG || name.Length == 0)
       {
         name = element.LocalName;
       }
 
       // Look for the property on the target object
       Type targetType = target.GetType();
-      Type propertyType = null;
+      Type? propertyType = null;
 
-      PropertyInfo propInfo = null;
-      MethodInfo methInfo = null;
+      MethodInfo? methInfo = null;
 
       // Try to find a writable property
-      propInfo = targetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
-      if (propInfo != null && propInfo.CanWrite)
+      PropertyInfo? propInfo = targetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
+      if (propInfo is not null && propInfo.CanWrite)
       {
         // found a property
         propertyType = propInfo.PropertyType;
@@ -590,22 +578,21 @@
 
         // look for a method with the signature Add<property>(type)
         methInfo = FindMethodInfo(targetType, name);
-
-        if (methInfo != null)
+        if (methInfo is not null)
         {
           propertyType = methInfo.GetParameters()[0].ParameterType;
         }
       }
 
-      if (propertyType == null)
+      if (propertyType is null)
       {
-        LogLog.Error(declaringType, "XmlHierarchyConfigurator: Cannot find Property [" + name + "] to set object on [" + target.ToString() + "]");
+        LogLog.Error(declaringType, $"XmlHierarchyConfigurator: Cannot find Property [{name}] to set object on [{target}]");
       }
       else
       {
-        string propertyValue = null;
+        string? propertyValue = null;
 
-        if (element.GetAttributeNode(VALUE_ATTR) != null)
+        if (element.GetAttributeNode(VALUE_ATTR) is not null)
         {
           propertyValue = element.GetAttribute(VALUE_ATTR);
         }
@@ -614,9 +601,9 @@
           // Concatenate the CDATA and Text nodes together
           foreach (XmlNode childNode in element.ChildNodes)
           {
-            if (childNode.NodeType == XmlNodeType.CDATA || childNode.NodeType == XmlNodeType.Text)
+            if (childNode.NodeType is XmlNodeType.CDATA or XmlNodeType.Text)
             {
-              if (propertyValue == null)
+              if (propertyValue is null)
               {
                 propertyValue = childNode.InnerText;
               }
@@ -628,7 +615,7 @@
           }
         }
 
-        if (propertyValue != null)
+        if (propertyValue is not null)
         {
           try
           {
@@ -652,14 +639,14 @@
 
           // Check if a specific subtype is specified on the element using the 'type' attribute
           string subTypeString = element.GetAttribute(TYPE_ATTR);
-          if (subTypeString != null && subTypeString.Length > 0)
+          if (subTypeString.Length > 0)
           {
             // Read the explicit subtype
             try
             {
-              Type subType = SystemInfo.GetTypeFromString(subTypeString, true, true);
+              Type subType = SystemInfo.GetTypeFromString(subTypeString, true, true)!;
 
-              LogLog.Debug(declaringType, "Parameter [" + name + "] specified subtype [" + subType.FullName + "]");
+              LogLog.Debug(declaringType, $"Parameter [{name}] specified subtype [{subType.FullName}]");
 
               if (!propertyType.IsAssignableFrom(subType))
               {
@@ -669,12 +656,12 @@
                   // Must re-convert to the real property type
                   parsedObjectConversionTargetType = propertyType;
 
-                  // Use sub type as intermediary type
+                  // Use subtype as intermediary type
                   propertyType = subType;
                 }
                 else
                 {
-                  LogLog.Error(declaringType, "subtype [" + subType.FullName + "] set on [" + name + "] is not a subclass of property type [" + propertyType.FullName + "] and there are no acceptable type conversions.");
+                  LogLog.Error(declaringType, $"subtype [{subType.FullName}] set on [{name}] is not a subclass of property type [{propertyType.FullName}] and there are no acceptable type conversions.");
                 }
               }
               else
@@ -686,28 +673,28 @@
             }
             catch (Exception ex)
             {
-              LogLog.Error(declaringType, "Failed to find type [" + subTypeString + "] set on [" + name + "]", ex);
+              LogLog.Error(declaringType, $"Failed to find type [{subTypeString}] set on [{name}]", ex);
             }
           }
 
           // Now try to convert the string value to an acceptable type
           // to pass to this property.
 
-          object convertedValue = ConvertStringTo(propertyType, propertyValue);
+          object? convertedValue = ConvertStringTo(propertyType, propertyValue);
 
           // Check if we need to do an additional conversion
-          if (convertedValue != null && parsedObjectConversionTargetType != null)
+          if (convertedValue is not null && parsedObjectConversionTargetType is not null)
           {
-            LogLog.Debug(declaringType, "Performing additional conversion of value from [" + convertedValue.GetType().Name + "] to [" + parsedObjectConversionTargetType.Name + "]");
+            LogLog.Debug(declaringType, $"Performing additional conversion of value from [{convertedValue.GetType().Name}] to [{parsedObjectConversionTargetType.Name}]");
             convertedValue = OptionConverter.ConvertTypeTo(convertedValue, parsedObjectConversionTargetType);
           }
 
-          if (convertedValue != null)
+          if (convertedValue is not null)
           {
-            if (propInfo != null)
+            if (propInfo is not null)
             {
               // Got a converted result
-              LogLog.Debug(declaringType, "Setting Property [" + propInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue.ToString() + "]");
+              LogLog.Debug(declaringType, $"Setting Property [{propInfo.Name}] to {convertedValue.GetType().Name} value [{convertedValue}]");
 
               try
               {
@@ -716,33 +703,33 @@
               }
               catch (TargetInvocationException targetInvocationEx)
               {
-                LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
+                LogLog.Error(declaringType, $"Failed to set parameter [{propInfo.Name}] on object [{target}] using value [{convertedValue}]", targetInvocationEx.InnerException);
               }
             }
-            else if (methInfo != null)
+            else
             {
               // Got a converted result
-              LogLog.Debug(declaringType, "Setting Collection Property [" + methInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue.ToString() + "]");
+              LogLog.Debug(declaringType, $"Setting Collection Property [{methInfo.Name}] to {convertedValue.GetType().Name} value [{convertedValue}]");
 
               try
               {
                 // Pass to the property
-                methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] { convertedValue }, CultureInfo.InvariantCulture);
+                methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { convertedValue }, CultureInfo.InvariantCulture);
               }
               catch (TargetInvocationException targetInvocationEx)
               {
-                LogLog.Error(declaringType, "Failed to set parameter [" + name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException);
+                LogLog.Error(declaringType, $"Failed to set parameter [{name}] on object [{target}] using value [{convertedValue}]", targetInvocationEx.InnerException);
               }
             }
           }
           else
           {
-            LogLog.Warn(declaringType, "Unable to set property [" + name + "] on object [" + target + "] using value [" + propertyValue + "] (with acceptable conversion types)");
+            LogLog.Warn(declaringType, $"Unable to set property [{name}] on object [{target}] using value [{propertyValue}] (with acceptable conversion types)");
           }
         }
         else
         {
-          object createdObject = null;
+          object? createdObject;
 
           if (propertyType == typeof(string) && !HasAttributesOrElements(element))
           {
@@ -751,12 +738,12 @@
             // This is necessary because while the String is a class it does not have
             // a default constructor that creates an empty string, which is the behavior
             // we are trying to simulate and would be expected from CreateObjectFromXml
-            createdObject = "";
+            createdObject = string.Empty;
           }
           else
           {
             // No value specified
-            Type defaultObjectType = null;
+            Type? defaultObjectType = null;
             if (IsTypeConstructible(propertyType))
             {
               defaultObjectType = propertyType;
@@ -765,16 +752,16 @@
             createdObject = CreateObjectFromXml(element, defaultObjectType, propertyType);
           }
 
-          if (createdObject == null)
+          if (createdObject is null)
           {
-            LogLog.Error(declaringType, "Failed to create object to set param: " + name);
+            LogLog.Error(declaringType, $"Failed to create object to set param: {name}");
           }
           else
           {
-            if (propInfo != null)
+            if (propInfo is not null)
             {
               // Got a converted result
-              LogLog.Debug(declaringType, "Setting Property [" + propInfo.Name + "] to object [" + createdObject + "]");
+              LogLog.Debug(declaringType, $"Setting Property [{propInfo.Name}] to object [{createdObject}]");
 
               try
               {
@@ -783,22 +770,22 @@
               }
               catch (TargetInvocationException targetInvocationEx)
               {
-                LogLog.Error(declaringType, "Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
+                LogLog.Error(declaringType, $"Failed to set parameter [{propInfo.Name}] on object [{target}] using value [{createdObject}]", targetInvocationEx.InnerException);
               }
             }
-            else if (methInfo != null)
+            else
             {
               // Got a converted result
-              LogLog.Debug(declaringType, "Setting Collection Property [" + methInfo.Name + "] to object [" + createdObject + "]");
+              LogLog.Debug(declaringType, $"Setting Collection Property [{methInfo.Name}] to object [{createdObject}]");
 
               try
               {
                 // Pass to the property
-                methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new object[] { createdObject }, CultureInfo.InvariantCulture);
+                methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { createdObject }, CultureInfo.InvariantCulture);
               }
               catch (TargetInvocationException targetInvocationEx)
               {
-                LogLog.Error(declaringType, "Failed to set parameter [" + methInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException);
+                LogLog.Error(declaringType, $"Failed to set parameter [{methInfo.Name}] on object [{target}] using value [{createdObject}]", targetInvocationEx.InnerException);
               }
             }
           }
@@ -832,8 +819,8 @@
     {
       if (type.IsClass && !type.IsAbstract)
       {
-        ConstructorInfo defaultConstructor = type.GetConstructor(new Type[0]);
-        if (defaultConstructor != null && !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate)
+        ConstructorInfo defaultConstructor = type.GetConstructor(Type.EmptyTypes);
+        if (defaultConstructor is not null && !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate)
         {
           return true;
         }
@@ -854,7 +841,7 @@
     /// The method must take a single parameter.
     /// </para>
     /// </remarks>
-    private MethodInfo FindMethodInfo(Type targetType, string name)
+    private static MethodInfo? FindMethodInfo(Type targetType, string name)
     {
       string requiredMethodNameA = name;
       string requiredMethodNameB = "Add" + name;
@@ -873,7 +860,7 @@
             // Found matching method name
 
             // Look for version with one arg only
-            System.Reflection.ParameterInfo[] methParams = methInfo.GetParameters();
+            ParameterInfo[] methParams = methInfo.GetParameters();
             if (methParams.Length == 1)
             {
               return methInfo;
@@ -895,17 +882,17 @@
     /// <c>null</c> when the conversion could not be performed.
     /// </para>
     /// </returns>
-    protected object ConvertStringTo(Type type, string value)
+    protected object? ConvertStringTo(Type type, string value)
     {
       // Hack to allow use of Level in property
       if (typeof(Level) == type)
       {
         // Property wants a level
-        Level levelValue = m_hierarchy.LevelMap[value];
+        Level? levelValue = m_hierarchy.LevelMap[value];
 
-        if (levelValue == null)
+        if (levelValue is null)
         {
-          LogLog.Error(declaringType, "XmlHierarchyConfigurator: Unknown Level Specified [" + value + "]");
+          LogLog.Error(declaringType, $"XmlHierarchyConfigurator: Unknown Level Specified [{value}]");
         }
 
         return levelValue;
@@ -932,24 +919,22 @@
     /// <paramref name="typeConstraint"/> type.
     /// </para>
     /// </remarks>
-    protected object CreateObjectFromXml(XmlElement element, Type defaultTargetType, Type typeConstraint)
+    protected object? CreateObjectFromXml(XmlElement element, Type? defaultTargetType, Type? typeConstraint)
     {
-      Type objectType = null;
+      Type? objectType;
 
       // Get the object type
       string objectTypeString = element.GetAttribute(TYPE_ATTR);
-      if (objectTypeString == null || objectTypeString.Length == 0)
+      if (objectTypeString.Length == 0)
       {
-        if (defaultTargetType == null)
+        if (defaultTargetType is null)
         {
-          LogLog.Error(declaringType, "Object type not specified. Cannot create object of type [" + typeConstraint.FullName + "]. Missing Value or Type.");
+          LogLog.Error(declaringType, $"Object type not specified. Cannot create object of type [{typeConstraint?.FullName}]. Missing Value or Type.");
           return null;
         }
-        else
-        {
-          // Use the default object type
-          objectType = defaultTargetType;
-        }
+
+        // Use the default object type
+        objectType = defaultTargetType;
       }
       else
       {
@@ -960,7 +945,7 @@
         }
         catch (Exception ex)
         {
-          LogLog.Error(declaringType, "Failed to find type [" + objectTypeString + "]", ex);
+          LogLog.Error(declaringType, $"Failed to find type [{objectTypeString}]", ex);
           return null;
         }
       }
@@ -968,7 +953,7 @@
       bool requiresConversion = false;
 
       // Got the object type. Check that it meets the typeConstraint
-      if (typeConstraint != null)
+      if (typeConstraint is not null)
       {
         if (!typeConstraint.IsAssignableFrom(objectType))
         {
@@ -979,21 +964,22 @@
           }
           else
           {
-            LogLog.Error(declaringType, "Object type [" + objectType.FullName + "] is not assignable to type [" + typeConstraint.FullName + "]. There are no acceptable type conversions.");
+            LogLog.Error(declaringType, $"Object type [{objectType!.FullName}] is not assignable to type [{typeConstraint.FullName}]. There are no acceptable type conversions.");
             return null;
           }
         }
       }
 
       // Create using the default constructor
-      object createdObject = null;
+      object? createdObject;
       try
       {
-        createdObject = Activator.CreateInstance(objectType);
+        createdObject = Activator.CreateInstance(objectType!);
       }
       catch (Exception createInstanceEx)
       {
-        LogLog.Error(declaringType, "XmlHierarchyConfigurator: Failed to construct object of type [" + objectType.FullName + "] Exception: " + createInstanceEx.ToString());
+        LogLog.Error(declaringType, $"XmlHierarchyConfigurator: Failed to construct object of type [{objectType!.FullName}] Exception: {createInstanceEx}");
+        return null;
       }
 
       // Set any params on object
@@ -1006,8 +992,7 @@
       }
 
       // Check if we need to call ActivateOptions
-      IOptionHandler optionHandler = createdObject as IOptionHandler;
-      if (optionHandler != null)
+      if (createdObject is IOptionHandler optionHandler)
       {
         optionHandler.ActivateOptions();
       }
@@ -1019,16 +1004,12 @@
         // Convert the object type
         return OptionConverter.ConvertTypeTo(createdObject, typeConstraint);
       }
-      else
-      {
-        // The object is of the correct type
-        return createdObject;
-      }
+
+      // The object is of the correct type
+      return createdObject;
     }
 
-    #endregion Protected Instance Methods
-
-    private bool HasCaseInsensitiveEnvironment
+    private static bool HasCaseInsensitiveEnvironment
     {
       get
       {
@@ -1037,12 +1018,8 @@
       }
     }
 
-    private IDictionary CreateCaseInsensitiveWrapper(IDictionary dict)
+    private static IDictionary CreateCaseInsensitiveWrapper(IDictionary dict)
     {
-      if (dict == null)
-      {
-        return dict;
-      }
       Hashtable hash = SystemInfo.CreateCaseInsensitiveHashtable();
       foreach (DictionaryEntry entry in dict)
       {
@@ -1051,8 +1028,6 @@
       return hash;
     }
 
-    #region Private Constants
-
     // String constants used while parsing the XML data
     private const string CONFIGURATION_TAG = "log4net";
     private const string RENDERER_TAG = "renderer";
@@ -1084,24 +1059,16 @@
     // flag used on the level element
     private const string INHERITED = "inherited";
 
-    #endregion Private Constants
-
-    #region Private Instance Fields
-
     /// <summary>
     /// key: appenderName, value: appender.
     /// </summary>
-    private Hashtable m_appenderBag;
+    private readonly Dictionary<string, IAppender> m_appenderBag = new(StringComparer.Ordinal);
 
     /// <summary>
     /// The Hierarchy being configured.
     /// </summary>
     private readonly Hierarchy m_hierarchy;
 
-    #endregion Private Instance Fields
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the XmlHierarchyConfigurator class.
     /// </summary>
@@ -1110,7 +1077,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(XmlHierarchyConfigurator);
-
-    #endregion Private Static Fields
   }
 }
diff --git a/src/log4net/Repository/ILoggerRepository.cs b/src/log4net/Repository/ILoggerRepository.cs
index ac6638a..ad0b0d9 100644
--- a/src/log4net/Repository/ILoggerRepository.cs
+++ b/src/log4net/Repository/ILoggerRepository.cs
@@ -174,17 +174,17 @@
     /// <summary>
     /// Event to notify that the repository has been shut down.
     /// </summary>
-    event LoggerRepositoryShutdownEventHandler ShutdownEvent;
+    event LoggerRepositoryShutdownEventHandler? ShutdownEvent;
 
     /// <summary>
     /// Event to notify that the repository has had its configuration reset to default.
     /// </summary>
-    event LoggerRepositoryConfigurationResetEventHandler ConfigurationReset;
+    event LoggerRepositoryConfigurationResetEventHandler? ConfigurationReset;
 
     /// <summary>
     /// Event to notify that the repository's configuration has changed.
     /// </summary>
-    event LoggerRepositoryConfigurationChangedEventHandler ConfigurationChanged;
+    event LoggerRepositoryConfigurationChangedEventHandler? ConfigurationChanged;
 
     /// <summary>
     /// Repository specific properties.
diff --git a/src/log4net/Repository/LoggerRepositorySkeleton.cs b/src/log4net/Repository/LoggerRepositorySkeleton.cs
index 11afa00..ed04559 100644
--- a/src/log4net/Repository/LoggerRepositorySkeleton.cs
+++ b/src/log4net/Repository/LoggerRepositorySkeleton.cs
@@ -24,6 +24,7 @@
 using log4net.Util;
 using log4net.Plugin;
 using System.Threading;
+using log4net.Appender;
 
 namespace log4net.Repository
 {
@@ -41,25 +42,12 @@
   /// </remarks>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  public abstract class LoggerRepositorySkeleton : ILoggerRepository, Appender.IFlushable
+  public abstract class LoggerRepositorySkeleton : ILoggerRepository, IFlushable
   {
-    #region Member Variables
-
-    private string m_name;
-    private RendererMap m_rendererMap;
-    private PluginMap m_pluginMap;
-    private LevelMap m_levelMap;
-    private Level m_threshold;
-    private bool m_configured;
-    private ICollection m_configurationMessages;
-    private event LoggerRepositoryShutdownEventHandler m_shutdownEvent;
-    private event LoggerRepositoryConfigurationResetEventHandler m_configurationResetEvent;
-    private event LoggerRepositoryConfigurationChangedEventHandler m_configurationChangedEvent;
-    private PropertiesDictionary m_properties;
-
-    #endregion
-
-    #region Constructors
+    private readonly RendererMap m_rendererMap = new();
+    private readonly LevelMap m_levelMap = new();
+    private Level m_threshold = Level.All;  // Don't disable any levels by default.
+    private ICollection m_configurationMessages = EmptyCollection.Instance;
 
     /// <summary>
     /// Default Constructor
@@ -84,23 +72,11 @@
     /// </remarks>
     protected LoggerRepositorySkeleton(PropertiesDictionary properties)
     {
-      m_properties = properties;
-      m_rendererMap = new RendererMap();
-      m_pluginMap = new PluginMap(this);
-      m_levelMap = new LevelMap();
-      m_configurationMessages = EmptyCollection.Instance;
-      m_configured = false;
-
+      Properties = properties;
+      PluginMap = new PluginMap(this);
       AddBuiltinLevels();
-
-      // Don't disable any levels by default.
-      m_threshold = Level.All;
     }
 
-    #endregion
-
-    #region Implementation of ILoggerRepository
-
     /// <summary>
     /// The name of the repository
     /// </summary>
@@ -114,11 +90,7 @@
     /// stored by the <see cref="IRepositorySelector"/>.
     /// </para>
     /// </remarks>
-    public virtual string Name
-    {
-      get { return m_name; }
-      set { m_name = value; }
-    }
+    public virtual string? Name { get; set; }
 
     /// <summary>
     /// The threshold for all events in this repository
@@ -133,10 +105,10 @@
     /// </remarks>
     public virtual Level Threshold
     {
-      get { return m_threshold; }
+      get => m_threshold;
       set
       {
-        if (value != null)
+        if (value is not null)
         {
           m_threshold = value;
         }
@@ -164,10 +136,7 @@
     /// <see cref="IObjectRenderer"/> objects.
     /// </para>
     /// </remarks>
-    public virtual RendererMap RendererMap
-    {
-      get { return m_rendererMap; }
-    }
+    public virtual RendererMap RendererMap => m_rendererMap;
 
     /// <summary>
     /// The plugin map for this repository.
@@ -181,10 +150,7 @@
     /// that have been attached to this repository.
     /// </para>
     /// </remarks>
-    public virtual PluginMap PluginMap
-    {
-      get { return m_pluginMap; }
-    }
+    public virtual PluginMap PluginMap { get; }
 
     /// <summary>
     /// Get the level map for the Repository.
@@ -199,10 +165,7 @@
     /// this repository.
     /// </para>
     /// </remarks>
-    public virtual LevelMap LevelMap
-    {
-      get { return m_levelMap; }
-    }
+    public virtual LevelMap LevelMap => m_levelMap;
 
     /// <summary>
     /// Test if logger exists
@@ -328,20 +291,16 @@
     /// Flag indicates if this repository has been configured.
     /// </para>
     /// </remarks>
-    public virtual bool Configured
-    {
-      get { return m_configured; }
-      set { m_configured = value; }
-    }
+    public virtual bool Configured { get; set; }
 
     /// <summary>
-    /// Contains a list of internal messages captures during the 
+    /// Contains a list of internal messages captured during the 
     /// last configuration.
     /// </summary>
     public virtual ICollection ConfigurationMessages
     {
-      get { return m_configurationMessages; }
-      set { m_configurationMessages = value; }
+      get => m_configurationMessages;
+      set => m_configurationMessages = value;
     }
 
     /// <summary>
@@ -355,11 +314,7 @@
     /// Event raised when the repository has been shutdown.
     /// </para>
     /// </remarks>
-    public event LoggerRepositoryShutdownEventHandler ShutdownEvent
-    {
-      add { m_shutdownEvent += value; }
-      remove { m_shutdownEvent -= value; }
-    }
+    public event LoggerRepositoryShutdownEventHandler? ShutdownEvent;
 
     /// <summary>
     /// Event to notify that the repository has had its configuration reset.
@@ -373,11 +328,7 @@
     /// reset to default.
     /// </para>
     /// </remarks>
-    public event LoggerRepositoryConfigurationResetEventHandler ConfigurationReset
-    {
-      add { m_configurationResetEvent += value; }
-      remove { m_configurationResetEvent -= value; }
-    }
+    public event LoggerRepositoryConfigurationResetEventHandler? ConfigurationReset;
 
     /// <summary>
     /// Event to notify that the repository has had its configuration changed.
@@ -390,11 +341,7 @@
     /// Event raised when the repository's configuration has been changed.
     /// </para>
     /// </remarks>
-    public event LoggerRepositoryConfigurationChangedEventHandler ConfigurationChanged
-    {
-      add { m_configurationChangedEvent += value; }
-      remove { m_configurationChangedEvent -= value; }
-    }
+    public event LoggerRepositoryConfigurationChangedEventHandler? ConfigurationChanged;
 
     /// <summary>
     /// Repository specific properties
@@ -405,10 +352,7 @@
     /// <remarks>
     /// These properties can be specified on a repository specific basis
     /// </remarks>
-    public PropertiesDictionary Properties
-    {
-      get { return m_properties; }
-    }
+    public PropertiesDictionary Properties { get; }
 
     /// <summary>
     /// Returns all the Appenders that are configured as an Array.
@@ -419,11 +363,7 @@
     /// Returns all the Appenders that are configured as an Array.
     /// </para>
     /// </remarks>
-    public abstract log4net.Appender.IAppender[] GetAppenders();
-
-    #endregion
-
-    #region Private Static Fields
+    public abstract IAppender[] GetAppenders();
 
     /// <summary>
     /// The fully qualified type of the LoggerRepositorySkeleton class.
@@ -434,8 +374,6 @@
     /// </remarks>
     private static readonly Type declaringType = typeof(LoggerRepositorySkeleton);
 
-    #endregion Private Static Fields
-
     private void AddBuiltinLevels()
     {
       // Add the predefined levels to the map
@@ -481,11 +419,11 @@
     {
       if (typeToRender == null)
       {
-        throw new ArgumentNullException("typeToRender");
+        throw new ArgumentNullException(nameof(typeToRender));
       }
       if (rendererInstance == null)
       {
-        throw new ArgumentNullException("rendererInstance");
+        throw new ArgumentNullException(nameof(rendererInstance));
       }
 
       m_rendererMap.Put(typeToRender, rendererInstance);
@@ -500,18 +438,10 @@
     /// Notify any listeners that this repository is shutting down.
     /// </para>
     /// </remarks>
-    protected virtual void OnShutdown(EventArgs e)
+    protected virtual void OnShutdown(EventArgs? e)
     {
-      if (e == null)
-      {
-        e = EventArgs.Empty;
-      }
-
-      LoggerRepositoryShutdownEventHandler handler = m_shutdownEvent;
-      if (handler != null)
-      {
-        handler(this, e);
-      }
+      e ??= EventArgs.Empty;
+      ShutdownEvent?.Invoke(this, e);
     }
 
     /// <summary>
@@ -523,18 +453,10 @@
     /// Notify any listeners that this repository's configuration has been reset.
     /// </para>
     /// </remarks>
-    protected virtual void OnConfigurationReset(EventArgs e)
+    protected virtual void OnConfigurationReset(EventArgs? e)
     {
-      if (e == null)
-      {
-        e = EventArgs.Empty;
-      }
-
-      LoggerRepositoryConfigurationResetEventHandler handler = m_configurationResetEvent;
-      if (handler != null)
-      {
-        handler(this, e);
-      }
+      e ??= EventArgs.Empty;
+      ConfigurationReset?.Invoke(this, e);
     }
 
     /// <summary>
@@ -546,18 +468,10 @@
     /// Notify any listeners that this repository's configuration has changed.
     /// </para>
     /// </remarks>
-    protected virtual void OnConfigurationChanged(EventArgs e)
+    protected virtual void OnConfigurationChanged(EventArgs? e)
     {
-      if (e == null)
-      {
-        e = EventArgs.Empty;
-      }
-
-      LoggerRepositoryConfigurationChangedEventHandler handler = m_configurationChangedEvent;
-      if (handler != null)
-      {
-        handler(this, e);
-      }
+      e ??= EventArgs.Empty;
+      ConfigurationChanged?.Invoke(this, e);
     }
 
     /// <summary>
@@ -589,12 +503,12 @@
     /// <summary>
     /// Flushes all configured Appenders that implement <see cref="log4net.Appender.IFlushable"/>.
     /// </summary>
-    /// <param name="millisecondsTimeout">The maximum time in milliseconds to wait for logging events from asycnhronous appenders to be flushed,
+    /// <param name="millisecondsTimeout">The maximum time in milliseconds to wait for logging events from asynchronous appenders to be flushed,
     /// or <see cref="Timeout.Infinite"/> to wait indefinitely.</param>
     /// <returns><c>True</c> if all logging events were flushed successfully, else <c>false</c>.</returns>
     public bool Flush(int millisecondsTimeout)
     {
-      if (millisecondsTimeout < -1) throw new ArgumentOutOfRangeException("millisecondsTimeout", "Timeout must be -1 (Timeout.Infinite) or non-negative");
+      if (millisecondsTimeout < -1) throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), "Timeout must be -1 (Timeout.Infinite) or non-negative");
 
       // Assume success until one of the appenders fails
       bool result = true;
@@ -603,11 +517,14 @@
       DateTime startTimeUtc = DateTime.UtcNow;
 
       // Do buffering appenders first.  These may be forwarding to other appenders
-      foreach (log4net.Appender.IAppender appender in GetAppenders())
+      foreach (IAppender appender in GetAppenders())
       {
-        log4net.Appender.IFlushable flushable = appender as log4net.Appender.IFlushable;
-        if (flushable == null) continue;
-        if (appender is Appender.BufferingAppenderSkeleton)
+        if (appender is not IFlushable flushable)
+        {
+          continue;
+        }
+
+        if (appender is BufferingAppenderSkeleton)
         {
           int timeout = GetWaitTime(startTimeUtc, millisecondsTimeout);
           if (!flushable.Flush(timeout)) result = false;
@@ -615,11 +532,14 @@
       }
 
       // Do non-buffering appenders.
-      foreach (log4net.Appender.IAppender appender in GetAppenders())
+      foreach (IAppender appender in GetAppenders())
       {
-        log4net.Appender.IFlushable flushable = appender as log4net.Appender.IFlushable;
-        if (flushable == null) continue;
-        if (!(appender is Appender.BufferingAppenderSkeleton))
+        if (appender is not IFlushable flushable)
+        {
+          continue;
+        }
+
+        if (appender is not BufferingAppenderSkeleton)
         {
           int timeout = GetWaitTime(startTimeUtc, millisecondsTimeout);
           if (!flushable.Flush(timeout)) result = false;
diff --git a/src/log4net/ThreadContext.cs b/src/log4net/ThreadContext.cs
index e84dba5..6d17de1 100644
--- a/src/log4net/ThreadContext.cs
+++ b/src/log4net/ThreadContext.cs
@@ -17,9 +17,6 @@
 //
 #endregion
 
-using System;
-using System.Collections;
-
 using log4net.Util;
 
 namespace log4net
@@ -67,26 +64,8 @@
   /// </example>
   /// <threadsafety static="true" instance="true" />
   /// <author>Nicko Cadell</author>
-  public sealed class ThreadContext
+  public static class ThreadContext
   {
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Private Constructor. 
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Uses a private access modifier to prevent instantiation of this class.
-    /// </para>
-    /// </remarks>
-    private ThreadContext()
-    {
-    }
-
-    #endregion Private Instance Constructors
-
-    #region Public Static Properties
-
     /// <summary>
     /// The thread properties map
     /// </summary>
@@ -99,10 +78,7 @@
     /// properties with the same name.
     /// </para>
     /// </remarks>
-    public static ThreadContextProperties Properties
-    {
-      get { return s_properties; }
-    }
+    public static ThreadContextProperties Properties { get; } = new();
 
     /// <summary>
     /// The thread stacks
@@ -115,25 +91,6 @@
     /// The thread local stacks.
     /// </para>
     /// </remarks>
-    public static ThreadContextStacks Stacks
-    {
-      get { return s_stacks; }
-    }
-
-    #endregion Public Static Properties
-
-    #region Private Static Fields
-
-    /// <summary>
-    /// The thread context properties instance
-    /// </summary>
-    private static readonly ThreadContextProperties s_properties = new ThreadContextProperties();
-
-    /// <summary>
-    /// The thread context stacks instance
-    /// </summary>
-    private static readonly ThreadContextStacks s_stacks = new ThreadContextStacks(s_properties);
-
-    #endregion Private Static Fields
+    public static ThreadContextStacks Stacks { get; } = new(Properties);
   }
 }
diff --git a/src/log4net/Util/CompositeProperties.cs b/src/log4net/Util/CompositeProperties.cs
index 66ac7b7..5e1350f 100644
--- a/src/log4net/Util/CompositeProperties.cs
+++ b/src/log4net/Util/CompositeProperties.cs
@@ -17,8 +17,7 @@
 //
 #endregion
 
-using System;
-using System.Collections;
+using System.Collections.Generic;
 
 namespace log4net.Util
 {
@@ -34,14 +33,8 @@
   /// <author>Nicko Cadell</author>
   public sealed class CompositeProperties
   {
-    #region Private Instance Fields
-
-    private PropertiesDictionary m_flattened = null;
-    private ArrayList m_nestedProperties = new ArrayList();
-
-    #endregion Private Instance Fields
-
-    #region Public Instance Constructors
+    private PropertiesDictionary? m_flattened;
+    private readonly List<ReadOnlyPropertiesDictionary> m_nestedProperties = new();
 
     /// <summary>
     /// Constructor
@@ -55,10 +48,6 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets the value of a property
     /// </summary>
@@ -78,12 +67,12 @@
     /// <c>null</c> is returned.
     /// </para>
     /// </remarks>
-    public object this[string key]
+    public object? this[string key]
     {
       get
       {
         // Look in the flattened properties first
-        if (m_flattened != null)
+        if (m_flattened is not null)
         {
           return m_flattened[key];
         }
@@ -91,19 +80,15 @@
         // Look for the key in all the nested properties
         foreach (ReadOnlyPropertiesDictionary cur in m_nestedProperties)
         {
-          if (cur.Contains(key))
+          if (cur.TryGetValue(key, out object? val))
           {
-            return cur[key];
+            return val;
           }
         }
         return null;
       }
     }
 
-    #endregion Public Instance Properties
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Add a Properties Dictionary to this composite collection
     /// </summary>
@@ -132,24 +117,22 @@
     /// </remarks>
     public PropertiesDictionary Flatten()
     {
-      if (m_flattened == null)
+      if (m_flattened is null)
       {
         m_flattened = new PropertiesDictionary();
 
         for (int i = m_nestedProperties.Count; --i >= 0;)
         {
-          ReadOnlyPropertiesDictionary cur = (ReadOnlyPropertiesDictionary)m_nestedProperties[i];
+          ReadOnlyPropertiesDictionary cur = m_nestedProperties[i];
 
-          foreach (DictionaryEntry entry in cur)
+          foreach (KeyValuePair<string, object?> entry in cur)
           {
-            m_flattened[(string)entry.Key] = entry.Value;
+            m_flattened[entry.Key] = entry.Value;
           }
         }
       }
       return m_flattened;
     }
-
-    #endregion Public Instance Methods
   }
 }
 
diff --git a/src/log4net/Util/ContextPropertiesBase.cs b/src/log4net/Util/ContextPropertiesBase.cs
index 4727461..b75b0d0 100644
--- a/src/log4net/Util/ContextPropertiesBase.cs
+++ b/src/log4net/Util/ContextPropertiesBase.cs
@@ -17,34 +17,19 @@
 //
 #endregion
 
-using System;
-using System.Collections;
+#nullable enable
 
 namespace log4net.Util
 {
   /// <summary>
   /// Base class for Context Properties implementations
   /// </summary>
-  /// <remarks>
-  /// <para>
-  /// This class defines a basic property get set accessor
-  /// </para>
-  /// </remarks>
   /// <author>Nicko Cadell</author>
   public abstract class ContextPropertiesBase
   {
     /// <summary>
-    /// Gets or sets the value of a property
+    /// Gets or sets the value of a property.
     /// </summary>
-    /// <value>
-    /// The value for the property with the specified key
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Gets or sets the value of a property
-    /// </para>
-    /// </remarks>
-    public abstract object this[string key] { get; set; }
+    public abstract object? this[string key] { get; set; }
   }
 }
-
diff --git a/src/log4net/Util/GlobalContextProperties.cs b/src/log4net/Util/GlobalContextProperties.cs
index d516804..9d5c1ce 100644
--- a/src/log4net/Util/GlobalContextProperties.cs
+++ b/src/log4net/Util/GlobalContextProperties.cs
@@ -17,9 +17,6 @@
 //
 #endregion
 
-using System;
-using System.Collections;
-
 namespace log4net.Util
 {
   /// <summary>
@@ -38,8 +35,6 @@
   /// <author>Nicko Cadell</author>
   public sealed class GlobalContextProperties : ContextPropertiesBase
   {
-    #region Private Instance Fields
-
     /// <summary>
     /// The read only copy of the properties.
     /// </summary>
@@ -49,16 +44,12 @@
     /// reordering reads and writes of this thread performed on different threads.
     /// </para>
     /// </remarks>
-    private volatile ReadOnlyPropertiesDictionary m_readOnlyProperties = new ReadOnlyPropertiesDictionary();
+    private volatile ReadOnlyPropertiesDictionary m_readOnlyProperties = new();
 
     /// <summary>
     /// Lock object used to synchronize updates within this instance
     /// </summary>
-    private readonly object m_syncRoot = new object();
-
-    #endregion Private Instance Fields
-
-    #region Public Instance Constructors
+    private readonly object m_syncRoot = new();
 
     /// <summary>
     /// Constructor
@@ -72,10 +63,6 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets or sets the value of a property
     /// </summary>
@@ -89,29 +76,22 @@
     /// the properties is created.
     /// </para>
     /// </remarks>
-    public override object this[string key]
+    public override object? this[string key]
     {
-      get
-      {
-        return m_readOnlyProperties[key];
-      }
+      get => m_readOnlyProperties[key];
       set
       {
         lock (m_syncRoot)
         {
-          PropertiesDictionary mutableProps = new PropertiesDictionary(m_readOnlyProperties);
-
-          mutableProps[key] = value;
-
+          var mutableProps = new PropertiesDictionary(m_readOnlyProperties)
+          {
+            [key] = value
+          };
           m_readOnlyProperties = new ReadOnlyPropertiesDictionary(mutableProps);
         }
       }
     }
 
-    #endregion Public Instance Properties
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Remove a property from the global context
     /// </summary>
@@ -128,10 +108,8 @@
       {
         if (m_readOnlyProperties.Contains(key))
         {
-          PropertiesDictionary mutableProps = new PropertiesDictionary(m_readOnlyProperties);
-
+          var mutableProps = new PropertiesDictionary(m_readOnlyProperties);
           mutableProps.Remove(key);
-
           m_readOnlyProperties = new ReadOnlyPropertiesDictionary(mutableProps);
         }
       }
@@ -148,10 +126,6 @@
       }
     }
 
-    #endregion Public Instance Methods
-
-    #region Internal Instance Methods
-
     /// <summary>
     /// Get a readonly immutable copy of the properties
     /// </summary>
@@ -166,8 +140,5 @@
     {
       return m_readOnlyProperties;
     }
-
-    #endregion Internal Instance Methods
   }
 }
-
diff --git a/src/log4net/Util/LogLog.cs b/src/log4net/Util/LogLog.cs
index 6a1d79e..4ce84aa 100644
--- a/src/log4net/Util/LogLog.cs
+++ b/src/log4net/Util/LogLog.cs
@@ -19,6 +19,7 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Diagnostics;
 
 namespace log4net.Util
@@ -28,7 +29,7 @@
   /// </summary>
   /// <param name="source"></param>
   /// <param name="e"></param>
-  public delegate void LogReceivedEventHandler(object source, LogReceivedEventArgs e);
+  public delegate void LogReceivedEventHandler(object? source, LogReceivedEventArgs e);
 
   /// <summary>
   /// Outputs log statements from within the log4net assembly.
@@ -52,37 +53,22 @@
     /// <summary>
     /// The event raised when an internal message has been received.
     /// </summary>
-    public static event LogReceivedEventHandler LogReceived;
-
-    private readonly Type source;
-    private readonly DateTime timeStampUtc;
-    private readonly string prefix;
-    private readonly string message;
-    private readonly Exception exception;
+    public static event LogReceivedEventHandler? LogReceived;
 
     /// <summary>
     /// The Type that generated the internal message.
     /// </summary>
-    public Type Source
-    {
-      get { return source; }
-    }
+    public Type Source { get; }
 
     /// <summary>
     /// The DateTime stamp of when the internal message was received.
     /// </summary>
-    public DateTime TimeStamp
-    {
-      get { return timeStampUtc.ToLocalTime(); }
-    }
+    public DateTime TimeStamp => TimeStampUtc.ToLocalTime();
 
     /// <summary>
     /// The UTC DateTime stamp of when the internal message was received.
     /// </summary>
-    public DateTime TimeStampUtc
-    {
-      get { return timeStampUtc; }
-    }
+    public DateTime TimeStampUtc { get; }
 
     /// <summary>
     /// A string indicating the severity of the internal message.
@@ -92,18 +78,12 @@
     /// "log4net:ERROR ", 
     /// "log4net:WARN "
     /// </remarks>
-    public string Prefix
-    {
-      get { return prefix; }
-    }
+    public string Prefix { get; }
 
     /// <summary>
     /// The internal log message.
     /// </summary>
-    public string Message
-    {
-      get { return message; }
-    }
+    public string Message { get; }
 
     /// <summary>
     /// The Exception related to the message.
@@ -111,10 +91,7 @@
     /// <remarks>
     /// Optional. Will be null if no Exception was passed.
     /// </remarks>
-    public Exception Exception
-    {
-      get { return exception; }
-    }
+    public Exception? Exception { get; }
 
     /// <summary>
     /// Formats Prefix, Source, and Message in the same format as the value
@@ -126,29 +103,19 @@
       return Prefix + Source.Name + ": " + Message;
     }
 
-    #region Private Instance Constructors
-
     /// <summary>
     /// Initializes a new instance of the <see cref="LogLog" /> class. 
     /// </summary>
-    /// <param name="source"></param>
-    /// <param name="prefix"></param>
-    /// <param name="message"></param>
-    /// <param name="exception"></param>
-    public LogLog(Type source, string prefix, string message, Exception exception)
+    public LogLog(Type source, string prefix, string message, Exception? exception)
     {
-      timeStampUtc = DateTime.UtcNow;
+      TimeStampUtc = DateTime.UtcNow;
 
-      this.source = source;
-      this.prefix = prefix;
-      this.message = message;
-      this.exception = exception;
+      Source = source;
+      Prefix = prefix;
+      Message = message;
+      Exception = exception;
     }
 
-    #endregion Private Instance Constructors
-
-    #region Static Constructor
-
     /// <summary>
     /// Static constructor that initializes logging by reading 
     /// settings from the application configuration file.
@@ -184,10 +151,6 @@
       }
     }
 
-    #endregion Static Constructor
-
-    #region Public Static Properties
-
     /// <summary>
     /// Gets or sets a value indicating whether log4net internal logging
     /// is enabled or disabled.
@@ -224,11 +187,7 @@
     /// </configuration>
     /// </code>
     /// </example>
-    public static bool InternalDebugging
-    {
-      get { return s_debugEnabled; }
-      set { s_debugEnabled = value; }
-    }
+    public static bool InternalDebugging { get; set; }
 
     /// <summary>
     /// Gets or sets a value indicating whether log4net should generate no output
@@ -264,24 +223,12 @@
     /// </configuration>
     /// </code>
     /// </example>
-    public static bool QuietMode
-    {
-      get { return s_quietMode; }
-      set { s_quietMode = value; }
-    }
+    public static bool QuietMode { get; set; }
 
     /// <summary>
     /// 
     /// </summary>
-    public static bool EmitInternalMessages
-    {
-      get { return s_emitInternalMessages; }
-      set { s_emitInternalMessages = value; }
-    }
-
-    #endregion Public Static Properties
-
-    #region Public Static Methods
+    public static bool EmitInternalMessages { get; set; } = true;
 
     /// <summary>
     /// Raises the LogReceived event when an internal messages is received.
@@ -290,12 +237,9 @@
     /// <param name="prefix"></param>
     /// <param name="message"></param>
     /// <param name="exception"></param>
-    public static void OnLogReceived(Type source, string prefix, string message, Exception exception)
+    public static void OnLogReceived(Type source, string prefix, string message, Exception? exception)
     {
-      if (LogReceived != null)
-      {
-        LogReceived(null, new LogReceivedEventArgs(new LogLog(source, prefix, message, exception)));
-      }
+      LogReceived?.Invoke(null, new LogReceivedEventArgs(new LogLog(source, prefix, message, exception)));
     }
 
     /// <summary>
@@ -309,10 +253,7 @@
     /// Test if LogLog.Debug is enabled for output.
     /// </para>
     /// </remarks>
-    public static bool IsDebugEnabled
-    {
-      get { return s_debugEnabled && !s_quietMode; }
-    }
+    public static bool IsDebugEnabled => InternalDebugging && !QuietMode;
 
     /// <summary>
     /// Writes log4net internal debug messages to the 
@@ -352,7 +293,7 @@
     ///  the string "log4net: ".
     /// </para>
     /// </remarks>
-    public static void Debug(Type source, string message, Exception exception)
+    public static void Debug(Type source, string message, Exception? exception)
     {
       if (IsDebugEnabled)
       {
@@ -375,15 +316,7 @@
     /// <value>
     /// <c>true</c> if Warn is enabled
     /// </value>
-    /// <remarks>
-    /// <para>
-    /// Test if LogLog.Warn is enabled for output.
-    /// </para>
-    /// </remarks>
-    public static bool IsWarnEnabled
-    {
-      get { return !s_quietMode; }
-    }
+    public static bool IsWarnEnabled => !QuietMode;
 
     /// <summary>
     /// Writes log4net internal warning messages to the 
@@ -423,7 +356,7 @@
     ///  the string "log4net:WARN ".
     /// </para>
     /// </remarks>
-    public static void Warn(Type source, string message, Exception exception)
+    public static void Warn(Type source, string message, Exception? exception)
     {
       if (IsWarnEnabled)
       {
@@ -451,10 +384,7 @@
     /// Test if LogLog.Error is enabled for output.
     /// </para>
     /// </remarks>
-    public static bool IsErrorEnabled
-    {
-      get { return !s_quietMode; }
-    }
+    public static bool IsErrorEnabled => !QuietMode;
 
     /// <summary>
     /// Writes log4net internal error messages to the 
@@ -494,7 +424,7 @@
     ///  the string "log4net:ERROR ".
     /// </para>
     /// </remarks>
-    public static void Error(Type source, string message, Exception exception)
+    public static void Error(Type source, string message, Exception? exception)
     {
       if (IsErrorEnabled)
       {
@@ -511,8 +441,6 @@
       }
     }
 
-    #endregion Public Static Methods
-
     /// <summary>
     /// Writes output to the standard output stream.  
     /// </summary>
@@ -520,8 +448,6 @@
     /// <remarks>
     /// <para>
     /// Writes to both Console.Out and System.Diagnostics.Trace.
-    /// Note that the System.Diagnostics.Trace is not supported
-    /// on the Compact Framework.
     /// </para>
     /// <para>
     /// If the AppDomain is not configured with a config file then
@@ -571,67 +497,45 @@
       }
     }
 
-    #region Private Static Fields
-
-    /// <summary>
-    ///  Default debug level
-    /// </summary>
-    private static bool s_debugEnabled = false;
-
-    /// <summary>
-    /// In quietMode not even errors generate any output.
-    /// </summary>
-    private static bool s_quietMode = false;
-
-    private static bool s_emitInternalMessages = true;
-
     private const string PREFIX = "log4net: ";
     private const string ERR_PREFIX = "log4net:ERROR ";
     private const string WARN_PREFIX = "log4net:WARN ";
 
-    #endregion Private Static Fields
-
     /// <summary>
     /// Subscribes to the LogLog.LogReceived event and stores messages
     /// to the supplied IList instance.
     /// </summary>
     public class LogReceivedAdapter : IDisposable
     {
-      private readonly IList items;
-      private readonly LogReceivedEventHandler handler;
+      private readonly LogReceivedEventHandler m_handler;
 
       /// <summary>
       /// 
       /// </summary>
       /// <param name="items"></param>
-      public LogReceivedAdapter(IList items)
+      public LogReceivedAdapter(List<LogLog> items)
       {
-        this.items = items;
-
-        handler = new LogReceivedEventHandler(LogLog_LogReceived);
-
-        LogReceived += handler;
+        Items = items;
+        m_handler = LogLog_LogReceived;
+        LogReceived += m_handler;
       }
 
       void LogLog_LogReceived(object source, LogReceivedEventArgs e)
       {
-        items.Add(e.LogLog);
+        Items.Add(e.LogLog);
       }
 
       /// <summary>
       /// 
       /// </summary>
-      public IList Items
-      {
-        get { return items; }
-      }
+      public List<LogLog> Items { get; }
 
       /// <summary>
       /// 
       /// </summary>
       public void Dispose()
       {
-        LogReceived -= handler;
+        LogReceived -= m_handler;
       }
     }
   }
@@ -641,23 +545,18 @@
   /// </summary>
   public class LogReceivedEventArgs : EventArgs
   {
-    private readonly LogLog loglog;
-
     /// <summary>
     /// 
     /// </summary>
     /// <param name="loglog"></param>
     public LogReceivedEventArgs(LogLog loglog)
     {
-      this.loglog = loglog;
+      this.LogLog = loglog;
     }
 
     /// <summary>
     /// 
     /// </summary>
-    public LogLog LogLog
-    {
-      get { return loglog; }
-    }
+    public LogLog LogLog { get; }
   }
 }
diff --git a/src/log4net/Util/LogicalThreadContextProperties.cs b/src/log4net/Util/LogicalThreadContextProperties.cs
index 28020c5..f42a6d2 100644
--- a/src/log4net/Util/LogicalThreadContextProperties.cs
+++ b/src/log4net/Util/LogicalThreadContextProperties.cs
@@ -26,6 +26,8 @@
 using CallContext = System.Threading.AsyncLocal<log4net.Util.PropertiesDictionary>;
 #endif
 
+#nullable enable
+
 namespace log4net.Util
 {
   /// <summary>
@@ -64,9 +66,7 @@
     /// <summary>
     /// Flag used to disable this context if we don't have permission to access the CallContext.
     /// </summary>
-    private bool m_disabled = false;
-
-    #region Public Instance Constructors
+    private bool m_disabled;
 
     /// <summary>
     /// Constructor
@@ -80,49 +80,27 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
-    /// <summary>
-    /// Gets or sets the value of a property
-    /// </summary>
-    /// <value>
-    /// The value for the property with the specified key
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Get or set the property value for the <paramref name="key"/> specified.
-    /// </para>
-    /// </remarks>
-    public override object this[string key]
+    /// <inheritdoc/>
+    public override object? this[string key]
     {
       get
       {
         // Don't create the dictionary if it does not already exist
-        PropertiesDictionary dictionary = GetProperties(false);
-        if (dictionary != null)
-        {
-          return dictionary[key];
-        }
-        return null;
+        PropertiesDictionary? dictionary = GetProperties(false);
+        return dictionary?[key];
       }
       set
       {
         // Force the dictionary to be created
-        PropertiesDictionary props = GetProperties(true);
+        PropertiesDictionary props = GetProperties(true)!;
         // Reason for cloning the dictionary below: object instances set on the CallContext
         // need to be immutable to correctly flow through async/await
-        PropertiesDictionary immutableProps = new PropertiesDictionary(props);
+        var immutableProps = new PropertiesDictionary(props);
         immutableProps[key] = value;
         SetLogicalProperties(immutableProps);
       }
     }
 
-    #endregion Public Instance Properties
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Remove a property
     /// </summary>
@@ -134,7 +112,7 @@
     /// </remarks>
     public void Remove(string key)
     {
-      PropertiesDictionary dictionary = GetProperties(false);
+      PropertiesDictionary? dictionary = GetProperties(false);
       if (dictionary != null)
       {
         PropertiesDictionary immutableProps = new PropertiesDictionary(dictionary);
@@ -153,22 +131,17 @@
     /// </remarks>
     public void Clear()
     {
-      PropertiesDictionary dictionary = GetProperties(false);
-      if (dictionary != null)
+      PropertiesDictionary? dictionary = GetProperties(false);
+      if (dictionary is not null)
       {
-        PropertiesDictionary immutableProps = new PropertiesDictionary();
-        SetLogicalProperties(immutableProps);
+        SetLogicalProperties(new PropertiesDictionary());
       }
     }
 
-    #endregion Public Instance Methods
-
-    #region Internal Instance Methods
-
     /// <summary>
     /// Get the PropertiesDictionary stored in the LocalDataStoreSlot for this thread.
     /// </summary>
-    /// <param name="create">create the dictionary if it does not exist, otherwise return null if is does not exist</param>
+    /// <param name="create">create the dictionary if it does not exist, otherwise return null if it does not exist</param>
     /// <returns>the properties for this thread</returns>
     /// <remarks>
     /// <para>
@@ -177,13 +150,13 @@
     /// caller must clone the collection before doings so.
     /// </para>
     /// </remarks>
-    internal PropertiesDictionary GetProperties(bool create)
+    internal PropertiesDictionary? GetProperties(bool create)
     {
       if (!m_disabled)
       {
         try
         {
-          PropertiesDictionary properties = GetLogicalProperties();
+          PropertiesDictionary? properties = GetLogicalProperties();
           if (properties == null && create)
           {
             properties = new PropertiesDictionary();
@@ -208,20 +181,16 @@
       return null;
     }
 
-    #endregion Internal Instance Methods
-
-    #region Private Static Methods
-
     /// <summary>
     /// Gets the call context get data.
     /// </summary>
-    /// <returns>The peroperties dictionary stored in the call context</returns>
+    /// <returns>The properties dictionary stored in the call context</returns>
     /// <remarks>
     /// The <see cref="CallContext"/> method GetData security link demand, therefore we must
-    /// put the method call in a seperate method that we can wrap in an exception handler.
+    /// put the method call in a separate method that we can wrap in an exception handler.
     /// </remarks>
-    [System.Security.SecuritySafeCritical]
-    private static PropertiesDictionary GetLogicalProperties()
+    [SecuritySafeCritical]
+    private static PropertiesDictionary? GetLogicalProperties()
     {
 #if NETSTANDARD2_0_OR_GREATER
       return AsyncLocalDictionary.Value;
@@ -236,9 +205,9 @@
     /// <param name="properties">The properties.</param>
     /// <remarks>
     /// The <see cref="CallContext"/> method SetData has a security link demand, therefore we must
-    /// put the method call in a seperate method that we can wrap in an exception handler.
+    /// put the method call in a separate method that we can wrap in an exception handler.
     /// </remarks>
-    [System.Security.SecuritySafeCritical]
+    [SecuritySafeCritical]
     private static void SetLogicalProperties(PropertiesDictionary properties)
     {
 #if NETSTANDARD2_0_OR_GREATER
@@ -248,10 +217,6 @@
 #endif
     }
 
-    #endregion
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the LogicalThreadContextProperties class.
     /// </summary>
@@ -260,7 +225,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(LogicalThreadContextProperties);
-
-    #endregion Private Static Fields
   }
 }
\ No newline at end of file
diff --git a/src/log4net/Util/LogicalThreadContextStack.cs b/src/log4net/Util/LogicalThreadContextStack.cs
index 6a92248..fd407e3 100644
--- a/src/log4net/Util/LogicalThreadContextStack.cs
+++ b/src/log4net/Util/LogicalThreadContextStack.cs
@@ -18,13 +18,13 @@
 #endregion
 
 using System;
-using System.Collections;
-
+using System.Collections.Generic;
 using log4net.Core;
 
+#nullable enable
+
 namespace log4net.Util
 {
-
   /// <summary>
   /// Delegate type used for LogicalThreadContextStack's callbacks.
   /// </summary>
@@ -33,36 +33,25 @@
   /// <summary>
   /// Implementation of Stack for the <see cref="log4net.LogicalThreadContext"/>
   /// </summary>
-  /// <remarks>
-  /// <para>
-  /// Implementation of Stack for the <see cref="log4net.LogicalThreadContext"/>
-  /// </para>
-  /// </remarks>
   /// <author>Nicko Cadell</author>
   public sealed class LogicalThreadContextStack : IFixingRequired
   {
-    #region Private Instance Fields
-
     /// <summary>
     /// The stack store.
     /// </summary>
-    private Stack m_stack = new Stack();
+    private Stack<StackFrame> m_stack = new();
 
     /// <summary>
     /// The name of this <see cref="log4net.Util.LogicalThreadContextStack"/> within the
     /// <see cref="log4net.Util.LogicalThreadContextProperties"/>.
     /// </summary>
-    private string m_propertyKey;
+    private readonly string m_propertyKey;
 
     /// <summary>
     /// The callback used to let the <see cref="log4net.Util.LogicalThreadContextStacks"/> register a
     /// new instance of a <see cref="log4net.Util.LogicalThreadContextStack"/>.
     /// </summary>
-    private TwoArgAction<string, LogicalThreadContextStack> m_registerNew;
-
-    #endregion Private Instance Fields
-
-    #region Public Instance Constructors
+    private readonly TwoArgAction<string, LogicalThreadContextStack> m_registerNew;
 
     /// <summary>
     /// Internal constructor
@@ -78,16 +67,9 @@
       m_registerNew = registerNew;
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Properties
-
     /// <summary>
-    /// The number of messages in the stack
+    /// Gets the number of messages in the stack.
     /// </summary>
-    /// <value>
-    /// The current number of messages in the stack
-    /// </value>
     /// <remarks>
     /// <para>
     /// The current number of messages in the stack. That is
@@ -95,14 +77,7 @@
     /// minus the number of times <see cref="Pop"/> has been called.
     /// </para>
     /// </remarks>
-    public int Count
-    {
-      get { return m_stack.Count; }
-    }
-
-    #endregion // Public Properties
-
-    #region Public Methods
+    public int Count => m_stack.Count;
 
     /// <summary>
     /// Clears all the contextual information held in this stack.
@@ -135,17 +110,16 @@
     /// empty string (not <see langword="null"/>) is returned.
     /// </para>
     /// </remarks>
-    public string Pop()
+    public string? Pop()
     {
       // copy current stack
-      Stack stack = new Stack(new Stack(m_stack));
-      string result = "";
+      var stack = new Stack<StackFrame>(new Stack<StackFrame>(m_stack));
+      string? result = string.Empty;
       if (stack.Count > 0)
       {
-        result = ((StackFrame)(stack.Pop())).Message;
+        result = stack.Pop().Message;
       }
-      LogicalThreadContextStack ltcs = new LogicalThreadContextStack(m_propertyKey, m_registerNew);
-      ltcs.m_stack = stack;
+      var ltcs = new LogicalThreadContextStack(m_propertyKey, m_registerNew) { m_stack = stack };
       m_registerNew(m_propertyKey, ltcs);
       return result;
     }
@@ -173,11 +147,11 @@
     ///  }
     /// </code>
     /// </example>
-    public IDisposable Push(string message)
+    public IDisposable Push(string? message)
     {
       // do modifications on a copy
-      Stack stack = new Stack(new Stack(m_stack));
-      stack.Push(new StackFrame(message, (stack.Count > 0) ? (StackFrame)stack.Peek() : null));
+      var stack = new Stack<StackFrame>(new Stack<StackFrame>(m_stack));
+      stack.Push(new StackFrame(message, (stack.Count > 0) ? stack.Peek() : null));
 
       LogicalThreadContextStack contextStack = new LogicalThreadContextStack(m_propertyKey, m_registerNew);
       contextStack.m_stack = stack;
@@ -195,54 +169,31 @@
     /// empty string (not <see langword="null"/>) is returned.
     /// </para>
     /// </remarks>
-    public string Peek()
+    public string? Peek()
     {
-      Stack stack = m_stack;
+      Stack<StackFrame> stack = m_stack;
       if (stack.Count > 0)
       {
-        return ((StackFrame)stack.Peek()).Message;
+        return stack.Peek().Message;
       }
-      return "";
+      return string.Empty;
     }
 
-    #endregion Public Methods
-
-    #region Internal Methods
-
     /// <summary>
     /// Gets the current context information for this stack.
     /// </summary>
     /// <returns>The current context information.</returns>
-    internal string GetFullMessage()
+    internal string? GetFullMessage()
     {
-      Stack stack = m_stack;
+      Stack<StackFrame> stack = m_stack;
       if (stack.Count > 0)
       {
-        return ((StackFrame)(stack.Peek())).FullMessage;
+        return stack.Peek().FullMessage;
       }
       return null;
     }
 
     /// <summary>
-    /// Gets and sets the internal stack used by this <see cref="LogicalThreadContextStack"/>
-    /// </summary>
-    /// <value>The internal storage stack</value>
-    /// <remarks>
-    /// <para>
-    /// This property is provided only to support backward compatability 
-    /// of the <see cref="NDC"/>. Tytpically the internal stack should not
-    /// be modified.
-    /// </para>
-    /// </remarks>
-    internal Stack InternalStack
-    {
-      get { return m_stack; }
-      set { m_stack = value; }
-    }
-
-    #endregion Internal Methods
-
-    /// <summary>
     /// Gets the current context information for this stack.
     /// </summary>
     /// <returns>Gets the current context information</returns>
@@ -251,44 +202,23 @@
     /// Gets the current context information for this stack.
     /// </para>
     /// </remarks>
-    public override string ToString()
+    public override string? ToString()
     {
       return GetFullMessage();
     }
 
     /// <summary>
-    /// Get a portable version of this object
+    /// Gets a cross-thread portable version of this object
     /// </summary>
-    /// <returns>the portable instance of this object</returns>
-    /// <remarks>
-    /// <para>
-    /// Get a cross thread portable version of this object
-    /// </para>
-    /// </remarks>
-    object IFixingRequired.GetFixedObject()
-    {
-      return GetFullMessage();
-    }
+    object? IFixingRequired.GetFixedObject() => GetFullMessage();
 
     /// <summary>
     /// Inner class used to represent a single context frame in the stack.
     /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Inner class used to represent a single context frame in the stack.
-    /// </para>
-    /// </remarks>
-    private sealed class StackFrame
+    internal sealed class StackFrame
     {
-      #region Private Instance Fields
-
-      private readonly string m_message;
-      private readonly StackFrame m_parent;
-      private string m_fullMessage = null;
-
-      #endregion
-
-      #region Internal Instance Constructors
+      private readonly StackFrame? m_parent;
+      private string? m_fullMessage;
 
       /// <summary>
       /// Constructor
@@ -301,9 +231,9 @@
       /// with the specified message and parent context.
       /// </para>
       /// </remarks>
-      internal StackFrame(string message, StackFrame parent)
+      internal StackFrame(string? message, StackFrame? parent)
       {
-        m_message = message;
+        Message = message;
         m_parent = parent;
 
         if (parent == null)
@@ -312,10 +242,6 @@
         }
       }
 
-      #endregion Internal Instance Constructors
-
-      #region Internal Instance Properties
-
       /// <summary>
       /// Get the message.
       /// </summary>
@@ -325,10 +251,7 @@
       /// Get the message.
       /// </para>
       /// </remarks>
-      internal string Message
-      {
-        get { return m_message; }
-      }
+      internal string? Message { get; }
 
       /// <summary>
       /// Gets the full text of the context down to the root level.
@@ -341,19 +264,18 @@
       /// Gets the full text of the context down to the root level.
       /// </para>
       /// </remarks>
-      internal string FullMessage
+      internal string? FullMessage
       {
         get
         {
           if (m_fullMessage == null && m_parent != null)
           {
-            m_fullMessage = string.Concat(m_parent.FullMessage, " ", m_message);
+            m_fullMessage = string.Concat(m_parent.FullMessage, " ", Message);
           }
+
           return m_fullMessage;
         }
       }
-
-      #endregion Internal Instance Properties
     }
 
     /// <summary>
@@ -365,23 +287,17 @@
     /// with the <see langword="using"/> pattern to remove the stack frame at the end of the scope.
     /// </para>
     /// </remarks>
-    private struct AutoPopStackFrame : IDisposable
+    private readonly struct AutoPopStackFrame : IDisposable
     {
-      #region Private Instance Fields
-
       /// <summary>
       /// The depth to trim the stack to when this instance is disposed
       /// </summary>
-      private int m_frameDepth;
+      private readonly int m_frameDepth;
 
       /// <summary>
       /// The outer LogicalThreadContextStack.
       /// </summary>
-      private LogicalThreadContextStack m_logicalThreadContextStack;
-
-      #endregion Private Instance Fields
-
-      #region Internal Instance Constructors
+      private readonly LogicalThreadContextStack m_logicalThreadContextStack;
 
       /// <summary>
       /// Constructor
@@ -400,10 +316,6 @@
         m_logicalThreadContextStack = logicalThreadContextStack;
       }
 
-      #endregion Internal Instance Constructors
-
-      #region Implementation of IDisposable
-
       /// <summary>
       /// Returns the stack to the correct depth.
       /// </summary>
@@ -414,22 +326,20 @@
       /// </remarks>
       public void Dispose()
       {
-        if (m_frameDepth >= 0 && m_logicalThreadContextStack.m_stack != null)
+        if (m_frameDepth >= 0)
         {
-          Stack stack = new Stack(new Stack(m_logicalThreadContextStack.m_stack));
+          var stack = new Stack<StackFrame>(new Stack<StackFrame>(m_logicalThreadContextStack.m_stack));
           while (stack.Count > m_frameDepth)
           {
             stack.Pop();
           }
-          LogicalThreadContextStack ltcs = new LogicalThreadContextStack(m_logicalThreadContextStack.m_propertyKey, m_logicalThreadContextStack.m_registerNew);
-          ltcs.m_stack = stack;
-          m_logicalThreadContextStack.m_registerNew(m_logicalThreadContextStack.m_propertyKey,
-            ltcs);
+          var ltcs = new LogicalThreadContextStack(m_logicalThreadContextStack.m_propertyKey, m_logicalThreadContextStack.m_registerNew)
+          {
+            m_stack = stack
+          };
+          m_logicalThreadContextStack.m_registerNew(m_logicalThreadContextStack.m_propertyKey, ltcs);
         }
       }
-
-      #endregion Implementation of IDisposable
     }
-
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Util/LogicalThreadContextStacks.cs b/src/log4net/Util/LogicalThreadContextStacks.cs
index 0d32d80..f797f48 100644
--- a/src/log4net/Util/LogicalThreadContextStacks.cs
+++ b/src/log4net/Util/LogicalThreadContextStacks.cs
@@ -24,18 +24,11 @@
   /// <summary>
   /// Implementation of Stacks collection for the <see cref="log4net.LogicalThreadContext"/>
   /// </summary>
-  /// <remarks>
-  /// <para>
-  /// Implementation of Stacks collection for the <see cref="log4net.LogicalThreadContext"/>
-  /// </para>
-  /// </remarks>
   /// <author>Nicko Cadell</author>
   public sealed class LogicalThreadContextStacks
   {
     private readonly LogicalThreadContextProperties m_properties;
 
-    #region Public Instance Constructors
-
     /// <summary>
     /// Internal constructor
     /// </summary>
@@ -49,10 +42,6 @@
       m_properties = properties;
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets the named thread context stack
     /// </summary>
@@ -74,7 +63,7 @@
         if (propertyValue == null)
         {
           // Stack does not exist, create
-          stack = new LogicalThreadContextStack(key, registerNew);
+          stack = new LogicalThreadContextStack(key, RegisterNew);
           m_properties[key] = stack;
         }
         else
@@ -96,7 +85,7 @@
 
             LogLog.Error(declaringType, "ThreadContextStacks: Request for stack named [" + key + "] failed because a property with the same name exists which is a [" + propertyValue.GetType().Name + "] with value [" + propertyValueString + "]");
 
-            stack = new LogicalThreadContextStack(key, registerNew);
+            stack = new LogicalThreadContextStack(key, RegisterNew);
           }
         }
 
@@ -104,19 +93,11 @@
       }
     }
 
-    #endregion Public Instance Properties
-
-    #region Private Instance Fields
-
-    private void registerNew(string stackName, LogicalThreadContextStack stack)
+    private void RegisterNew(string stackName, LogicalThreadContextStack stack)
     {
       m_properties[stackName] = stack;
     }
 
-    #endregion Private Instance Fields
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the ThreadContextStacks class.
     /// </summary>
@@ -125,7 +106,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(LogicalThreadContextStacks);
-
-    #endregion Private Static Fields
   }
 }
\ No newline at end of file
diff --git a/src/log4net/Util/OptionConverter.cs b/src/log4net/Util/OptionConverter.cs
index 8171213..8e5033e 100644
--- a/src/log4net/Util/OptionConverter.cs
+++ b/src/log4net/Util/OptionConverter.cs
@@ -25,6 +25,8 @@
 using log4net.Core;
 using log4net.Util.TypeConverters;
 
+#nullable enable
+
 namespace log4net.Util
 {
   /// <summary>
@@ -37,26 +39,8 @@
   /// </remarks>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  public sealed class OptionConverter
+  public static class OptionConverter
   {
-    #region Private Instance Constructors
-
-    /// <summary>
-    /// Initializes a new instance of the <see cref="OptionConverter" /> class. 
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Uses a private access modifier to prevent instantiation of this class.
-    /// </para>
-    /// </remarks>
-    private OptionConverter()
-    {
-    }
-
-    #endregion Private Instance Constructors
-
-    #region Public Static Methods
-
     /// <summary>
     /// Converts a string to a <see cref="bool" /> value.
     /// </summary>
@@ -70,9 +54,9 @@
     /// Otherwise, <paramref name="defaultValue"/> is returned.
     /// </para>
     /// </remarks>
-    public static bool ToBoolean(string argValue, bool defaultValue)
+    public static bool ToBoolean(string? argValue, bool defaultValue)
     {
-      if (argValue != null && argValue.Length > 0)
+      if (!string.IsNullOrEmpty(argValue))
       {
         try
         {
@@ -80,7 +64,7 @@
         }
         catch (Exception e)
         {
-          LogLog.Error(declaringType, "[" + argValue + "] is not in proper bool form.", e);
+          LogLog.Error(declaringType, $"[{argValue}] is not in proper bool form.", e);
         }
       }
       return defaultValue;
@@ -102,9 +86,9 @@
     /// cannot be converted to a <see cref="long" /> value.
     /// </para>
     /// </remarks>
-    public static long ToFileSize(string argValue, long defaultValue)
+    public static long ToFileSize(string? argValue, long defaultValue)
     {
-      if (argValue == null)
+      if (argValue is null)
       {
         return defaultValue;
       }
@@ -113,35 +97,32 @@
       long multiplier = 1;
       int index;
 
-      if ((index = s.IndexOf("KB")) != -1)
+      if ((index = s.IndexOf("KB", StringComparison.Ordinal)) != -1)
       {
         multiplier = 1024;
         s = s.Substring(0, index);
       }
-      else if ((index = s.IndexOf("MB")) != -1)
+      else if ((index = s.IndexOf("MB", StringComparison.Ordinal)) != -1)
       {
         multiplier = 1024 * 1024;
         s = s.Substring(0, index);
       }
-      else if ((index = s.IndexOf("GB")) != -1)
+      else if ((index = s.IndexOf("GB", StringComparison.Ordinal)) != -1)
       {
         multiplier = 1024 * 1024 * 1024;
         s = s.Substring(0, index);
       }
-      if (s != null)
-      {
-        // Try again to remove whitespace between the number and the size specifier
-        s = s.Trim();
 
-        long longVal;
-        if (SystemInfo.TryParse(s, out longVal))
-        {
-          return longVal * multiplier;
-        }
-        else
-        {
-          LogLog.Error(declaringType, "OptionConverter: [" + s + "] is not in the correct file size syntax.");
-        }
+      // Try again to remove whitespace between the number and the size specifier
+      s = s.Trim();
+
+      if (SystemInfo.TryParse(s, out long longVal))
+      {
+        return longVal * multiplier;
+      }
+      else
+      {
+        LogLog.Error(declaringType, $"OptionConverter: [{s}] is not in the correct file size syntax.");
       }
       return defaultValue;
     }
@@ -161,11 +142,11 @@
     /// to convert the string value into the specified target type.
     /// </para>
     /// </remarks>
-    public static object ConvertStringTo(Type target, string txt)
+    public static object? ConvertStringTo(Type target, string txt)
     {
-      if (target == null)
+      if (target is null)
       {
-        throw new ArgumentNullException("target");
+        throw new ArgumentNullException(nameof(target));
       }
 
       // If we want a string we already have the correct type
@@ -175,8 +156,8 @@
       }
 
       // First lets try to find a type converter
-      IConvertFrom typeConverter = ConverterRegistry.GetConvertFrom(target);
-      if (typeConverter != null && typeConverter.CanConvertFrom(typeof(string)))
+      IConvertFrom? typeConverter = ConverterRegistry.GetConvertFrom(target);
+      if (typeConverter is not null && typeConverter.CanConvertFrom(typeof(string)))
       {
         // Found appropriate converter
         return typeConverter.ConvertFrom(txt);
@@ -196,8 +177,7 @@
           // to an arbitrary type T there will be a static method defined on type T called Parse
           // that will take an argument of type string. i.e. T.Parse(string)->T we call this
           // method to convert the string to the type required by the property.
-          System.Reflection.MethodInfo meth = target.GetMethod("Parse", new Type[] { typeof(string) });
-          if (meth != null)
+          if (target.GetMethod("Parse", new[] { typeof(string) }) is MethodInfo meth)
           {
             // Call the Parse method
             return meth.Invoke(null, BindingFlags.InvokeMethod, null, new object[] { txt }, CultureInfo.InvariantCulture);
@@ -222,9 +202,9 @@
     /// <para>
     /// </para>
     /// </remarks>
-    public static bool CanConvertTypeTo(Type sourceType, Type targetType)
+    public static bool CanConvertTypeTo(Type? sourceType, Type? targetType)
     {
-      if (sourceType == null || targetType == null)
+      if (sourceType is null || targetType is null)
       {
         return false;
       }
@@ -236,8 +216,7 @@
       }
 
       // Look for a To converter
-      IConvertTo tcSource = ConverterRegistry.GetConvertTo(sourceType, targetType);
-      if (tcSource != null)
+      if (ConverterRegistry.GetConvertTo(sourceType, targetType) is IConvertTo tcSource)
       {
         if (tcSource.CanConvertTo(targetType))
         {
@@ -246,8 +225,7 @@
       }
 
       // Look for a From converter
-      IConvertFrom tcTarget = ConverterRegistry.GetConvertFrom(targetType);
-      if (tcTarget != null)
+      if (ConverterRegistry.GetConvertFrom(targetType) is IConvertFrom tcTarget)
       {
         if (tcTarget.CanConvertFrom(sourceType))
         {
@@ -280,8 +258,7 @@
       }
 
       // Look for a TO converter
-      IConvertTo tcSource = ConverterRegistry.GetConvertTo(sourceType, targetType);
-      if (tcSource != null)
+      if (ConverterRegistry.GetConvertTo(sourceType, targetType) is IConvertTo tcSource)
       {
         if (tcSource.CanConvertTo(targetType))
         {
@@ -290,8 +267,7 @@
       }
 
       // Look for a FROM converter
-      IConvertFrom tcTarget = ConverterRegistry.GetConvertFrom(targetType);
-      if (tcTarget != null)
+      if (ConverterRegistry.GetConvertFrom(targetType) is IConvertFrom tcTarget)
       {
         if (tcTarget.CanConvertFrom(sourceType))
         {
@@ -299,41 +275,9 @@
         }
       }
 
-      throw new ArgumentException("Cannot convert source object [" + sourceInstance.ToString() + "] to target type [" + targetType.Name + "]", "sourceInstance");
+      throw new ArgumentException($"Cannot convert source object [{sourceInstance}] to target type [{targetType.Name}]", nameof(sourceInstance));
     }
 
-    //    /// <summary>
-    //    /// Finds the value corresponding to <paramref name="key"/> in 
-    //    /// <paramref name="props"/> and then perform variable substitution 
-    //    /// on the found value.
-    //    /// </summary>
-    //    /// <param name="key">The key to lookup.</param>
-    //    /// <param name="props">The association to use for lookups.</param>
-    //    /// <returns>The substituted result.</returns>
-    //    public static string FindAndSubst(string key, System.Collections.IDictionary props) 
-    //    {
-    //      if (props == null)
-    //      {
-    //        throw new ArgumentNullException("props");
-    //      }
-    //
-    //      string v = props[key] as string;
-    //      if (v == null) 
-    //      {
-    //        return null;    
-    //      }
-    //  
-    //      try 
-    //      {
-    //        return SubstituteVariables(v, props);
-    //      } 
-    //      catch(Exception e) 
-    //      {
-    //        LogLog.Error(declaringType, "OptionConverter: Bad option value [" + v + "].", e);
-    //        return v;
-    //      }  
-    //    }
-
     /// <summary>
     /// Instantiates an object given a class name.
     /// </summary>
@@ -351,23 +295,28 @@
     /// not be instantiated, then <paramref name="defaultValue"/> is returned.
     /// </para>
     /// </remarks>
-    public static object InstantiateByClassName(string className, Type superClass, object defaultValue)
+    public static object? InstantiateByClassName(string? className, Type superClass, object? defaultValue)
     {
-      if (className != null)
+      if (className is not null)
       {
         try
         {
-          Type classObj = SystemInfo.GetTypeFromString(className, true, true);
-          if (!superClass.IsAssignableFrom(classObj))
+          Type? classObj = SystemInfo.GetTypeFromString(className, true, true);
+          if (classObj is not null)
           {
-            LogLog.Error(declaringType, "OptionConverter: A [" + className + "] object is not assignable to a [" + superClass.FullName + "] variable.");
-            return defaultValue;
+            if (!superClass.IsAssignableFrom(classObj))
+            {
+              LogLog.Error(declaringType, $"OptionConverter: A [{className}] object is not assignable to a [{superClass.FullName}] variable.");
+              return defaultValue;
+            }
+            return Activator.CreateInstance(classObj);
           }
-          return Activator.CreateInstance(classObj);
+
+          LogLog.Error(declaringType, $"Could not find class [{className}].");
         }
         catch (Exception e)
         {
-          LogLog.Error(declaringType, "Could not instantiate class [" + className + "].", e);
+          LogLog.Error(declaringType, $"Could not instantiate class [{className}].", e);
         }
       }
       return defaultValue;
@@ -425,7 +374,7 @@
 
       while (true)
       {
-        j = value.IndexOf(DELIM_START, i);
+        j = value.IndexOf(DELIM_START, i, StringComparison.Ordinal);
         if (j == -1)
         {
           if (i == 0)
@@ -451,9 +400,7 @@
             j += DELIM_START_LEN;
             string key = value.Substring(j, k - j);
 
-            string replacement = props[key] as string;
-
-            if (replacement != null)
+            if (props[key] is string replacement)
             {
               buf.Append(replacement);
             }
@@ -463,10 +410,6 @@
       }
     }
 
-    #endregion Public Static Methods
-
-    #region Private Static Methods
-
     /// <summary>
     /// Converts the string representation of the name or numeric value of one or 
     /// more enumerated constants to an equivalent enumerated object.
@@ -475,15 +418,11 @@
     /// <param name="value">The enum string value.</param>
     /// <param name="ignoreCase">If <c>true</c>, ignore case; otherwise, regard case.</param>
     /// <returns>An object of type <paramref name="enumType" /> whose value is represented by <paramref name="value" />.</returns>
-    private static object ParseEnum(System.Type enumType, string value, bool ignoreCase)
+    private static object ParseEnum(Type enumType, string value, bool ignoreCase)
     {
       return Enum.Parse(enumType, value, ignoreCase);
     }
 
-    #endregion Private Static Methods
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the OptionConverter class.
     /// </summary>
@@ -497,7 +436,5 @@
     private const char DELIM_STOP = '}';
     private const int DELIM_START_LEN = 2;
     private const int DELIM_STOP_LEN = 1;
-
-    #endregion Private Static Fields
   }
 }
\ No newline at end of file
diff --git a/src/log4net/Util/PatternConverter.cs b/src/log4net/Util/PatternConverter.cs
index efa921a..d5b3a35 100644
--- a/src/log4net/Util/PatternConverter.cs
+++ b/src/log4net/Util/PatternConverter.cs
@@ -40,8 +40,6 @@
   /// <author>Gert Driesen</author>
   public abstract class PatternConverter
   {
-    #region Protected Instance Constructors
-
     /// <summary>
     /// Protected constructor
     /// </summary>
@@ -54,25 +52,10 @@
     {
     }
 
-    #endregion Protected Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
-    /// Get the next pattern converter in the chain
+    /// Gets the next pattern converter in the chain.
     /// </summary>
-    /// <value>
-    /// the next pattern converter in the chain
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Get the next pattern converter in the chain
-    /// </para>
-    /// </remarks>
-    public virtual PatternConverter Next
-    {
-      get { return m_next; }
-    }
+    public virtual PatternConverter? Next => m_next;
 
     /// <summary>
     /// Gets or sets the formatting info for this converter
@@ -107,15 +90,7 @@
     /// Gets or sets the option value for this converter
     /// </para>
     /// </remarks>
-    public virtual string Option
-    {
-      get { return m_option; }
-      set { m_option = value; }
-    }
-
-    #endregion Public Instance Properties
-
-    #region Protected Abstract Methods
+    public virtual string? Option { get; set; }
 
     /// <summary>
     /// Evaluate this pattern converter and write the output to a writer.
@@ -130,10 +105,6 @@
     /// </remarks>
     public abstract void Convert(TextWriter writer, object state);
 
-    #endregion Protected Abstract Methods
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Set the next pattern converter in the chains
     /// </summary>
@@ -141,7 +112,7 @@
     /// <returns>the next converter</returns>
     /// <remarks>
     /// <para>
-    /// The PatternConverter can merge with its neighbor during this method (or a sub class).
+    /// The PatternConverter can merge with its neighbor during this method (or a subclass).
     /// Therefore the return value may or may not be the value of the argument passed in.
     /// </para>
     /// </remarks>
@@ -173,7 +144,7 @@
       }
       else
       {
-        string msg = null;
+        string? msg;
         int len;
         lock (m_formatWriter)
         {
@@ -214,9 +185,17 @@
       }
     }
 
-    private static readonly string[] SPACES = {  " ", "  ", "    ", "        ",      // 1,2,4,8 spaces
-                          "                ",            // 16 spaces
-                          "                                " }; // 32 spaces
+    private static readonly string[] SPACES =
+    {
+      // 1,2,4,8 spaces
+      " ", "  ", "    ", "        ",      
+      
+      // 16 spaces
+      "                ",
+
+      // 32 spaces
+      "                                "
+    };
 
     /// <summary>
     /// Fast space padding method.
@@ -245,25 +224,12 @@
       }
     }
 
-    #endregion Public Instance Methods
-
-    #region Private Instance Fields
-
-    private PatternConverter m_next;
+    private PatternConverter? m_next;
     private int m_min = -1;
     private int m_max = int.MaxValue;
-    private bool m_leftAlign = false;
+    private bool m_leftAlign;
 
-    /// <summary>
-    /// The option string to the converter
-    /// </summary>
-    private string m_option = null;
-
-    private ReusableStringWriter m_formatWriter = new ReusableStringWriter(System.Globalization.CultureInfo.InvariantCulture);
-
-    #endregion Private Instance Fields
-
-    #region Constants
+    private readonly ReusableStringWriter m_formatWriter = new(System.Globalization.CultureInfo.InvariantCulture);
 
     /// <summary>
     /// Initial buffer size
@@ -275,10 +241,6 @@
     /// </summary>
     private const int c_renderBufferMaxCapacity = 1024;
 
-    #endregion
-
-    #region Static Methods
-
     /// <summary>
     /// Write an dictionary to a <see cref="TextWriter"/>
     /// </summary>
@@ -304,7 +266,7 @@
     }
 
     /// <summary>
-    /// Write an dictionary to a <see cref="TextWriter"/>
+    /// Writes a dictionary to a <see cref="TextWriter"/>
     /// </summary>
     /// <param name="writer">the writer to write to</param>
     /// <param name="repository">a <see cref="ILoggerRepository"/> to use for object conversion</param>
@@ -360,16 +322,16 @@
     /// the object's ToString method is called.
     /// </para>
     /// </remarks>
-    protected static void WriteObject(TextWriter writer, ILoggerRepository repository, object value)
+    protected static void WriteObject(TextWriter writer, ILoggerRepository? repository, object? value)
     {
-      if (repository != null)
+      if (repository is not null)
       {
         repository.RendererMap.FindAndRender(value, writer);
       }
       else
       {
         // Don't have a repository to render with so just have to rely on ToString
-        if (value == null)
+        if (value is null)
         {
           writer.Write(SystemInfo.NullText);
         }
@@ -380,17 +342,9 @@
       }
     }
 
-    #endregion
-
-    private PropertiesDictionary properties;
-
     /// <summary>
     /// 
     /// </summary>
-    public PropertiesDictionary Properties
-    {
-      get { return properties; }
-      set { properties = value; }
-    }
+    public PropertiesDictionary? Properties { get; set; }
   }
 }
diff --git a/src/log4net/Util/PatternStringConverters/EnvironmentFolderPathPatternConverter.cs b/src/log4net/Util/PatternStringConverters/EnvironmentFolderPathPatternConverter.cs
index fb2ba93..b59452a 100644
--- a/src/log4net/Util/PatternStringConverters/EnvironmentFolderPathPatternConverter.cs
+++ b/src/log4net/Util/PatternStringConverters/EnvironmentFolderPathPatternConverter.cs
@@ -27,7 +27,6 @@
   /// </summary>
   /// <remarks>
   /// <para>
-  /// Write an special path environment folder path to the output writer.
   /// The value of the <see cref="log4net.Util.PatternConverter.Option"/> determines 
   /// the name of the variable to output. <see cref="log4net.Util.PatternConverter.Option"/>
   /// should be a value in the <see cref="System.Environment.SpecialFolder" /> enumeration.
@@ -37,7 +36,7 @@
   internal sealed class EnvironmentFolderPathPatternConverter : PatternConverter
   {
     /// <summary>
-    /// Write an special path environment folder path to the output
+    /// Writes a special path environment folder path to the output
     /// </summary>
     /// <param name="writer">the writer to write to</param>
     /// <param name="state">null, state is not set</param>
diff --git a/src/log4net/Util/PatternStringConverters/EnvironmentPatternConverter.cs b/src/log4net/Util/PatternStringConverters/EnvironmentPatternConverter.cs
index 44428e1..98d77f0 100644
--- a/src/log4net/Util/PatternStringConverters/EnvironmentPatternConverter.cs
+++ b/src/log4net/Util/PatternStringConverters/EnvironmentPatternConverter.cs
@@ -64,7 +64,9 @@
           envValue ??= Environment.GetEnvironmentVariable(Option, EnvironmentVariableTarget.Machine);
 
           if (envValue?.Length > 0)
+          {
             writer.Write(envValue);
+          }
         }
       }
       catch (System.Security.SecurityException secEx)
@@ -80,8 +82,6 @@
       }
     }
 
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the EnvironmentPatternConverter class.
     /// </summary>
@@ -90,7 +90,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(EnvironmentPatternConverter);
-
-    #endregion Private Static Fields
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Util/PropertiesDictionary.cs b/src/log4net/Util/PropertiesDictionary.cs
index 0bd500f..ced2c72 100644
--- a/src/log4net/Util/PropertiesDictionary.cs
+++ b/src/log4net/Util/PropertiesDictionary.cs
@@ -19,8 +19,11 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Runtime.Serialization;
 
+#nullable enable
+
 namespace log4net.Util
 {
   /// <summary>
@@ -28,7 +31,7 @@
   /// </summary>
   /// <remarks>
   /// <para>
-  /// While this collection is serializable only member 
+  /// While this collection is serializable, only member 
   /// objects that are serializable will
   /// be serialized along with this collection.
   /// </para>
@@ -38,8 +41,6 @@
   [Serializable]
   public sealed class PropertiesDictionary : ReadOnlyPropertiesDictionary, ISerializable, IDictionary
   {
-    #region Public Instance Constructors
-
     /// <summary>
     /// Constructor
     /// </summary>
@@ -65,10 +66,6 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Private Instance Constructors
-
     /// <summary>
     /// Initializes a new instance of the <see cref="PropertiesDictionary" /> class 
     /// with serialized data.
@@ -84,10 +81,6 @@
     {
     }
 
-    #endregion Protected Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets or sets the value of the  property with the specified key.
     /// </summary>
@@ -102,15 +95,19 @@
     /// a serialization operation is performed.
     /// </para>
     /// </remarks>
-    public override object this[string key]
+    public override object? this[string key]
     {
-      get { return InnerHashtable[key]; }
-      set { InnerHashtable[key] = value; }
+      get => base[key];
+      set => InnerHashtable[key] = value;
     }
 
-    #endregion Public Instance Properties
-
-    #region Public Instance Methods
+    /// <summary>
+    /// See <see cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>.
+    /// </summary>
+    public override void Add(string key, object? value)
+    {
+      InnerHashtable.Add(key, value);
+    }
 
     /// <summary>
     /// Remove the entry with the specified key from this dictionary
@@ -121,15 +118,11 @@
     /// Remove the entry with the specified key from this dictionary
     /// </para>
     /// </remarks>
-    public void Remove(string key)
+    public override bool Remove(string key)
     {
-      InnerHashtable.Remove(key);
+      return InnerHashtable.Remove(key);
     }
 
-    #endregion Public Instance Methods
-
-    #region Implementation of IDictionary
-
     /// <summary>
     /// See <see cref="IDictionary.GetEnumerator"/>
     /// </summary>
@@ -155,22 +148,12 @@
     /// </remarks>
     void IDictionary.Remove(object key)
     {
-      InnerHashtable.Remove(key);
-    }
+      if (key is not string k)
+      {
+        throw new ArgumentException("key must be a string");
+      }
 
-    /// <summary>
-    /// See <see cref="IDictionary.Contains"/>
-    /// </summary>
-    /// <param name="key">the key to lookup in the collection</param>
-    /// <returns><c>true</c> if the collection contains the specified key</returns>
-    /// <remarks>
-    /// <para>
-    /// Test if this collection contains a specified key.
-    /// </para>
-    /// </remarks>
-    bool IDictionary.Contains(object key)
-    {
-      return InnerHashtable.Contains(key);
+      InnerHashtable.Remove(k);
     }
 
     /// <summary>
@@ -199,11 +182,11 @@
     /// <exception cref="ArgumentException">Thrown if the <paramref name="key"/> is not a string</exception>
     void IDictionary.Add(object key, object value)
     {
-      if (!(key is string))
+      if (key is not string k)
       {
-        throw new ArgumentException("key must be a string", "key");
+        throw new ArgumentException("key must be a string", nameof(key));
       }
-      InnerHashtable.Add(key, value);
+      InnerHashtable.Add(k, value);
     }
 
     /// <summary>
@@ -218,10 +201,7 @@
     /// returns <c>false</c>.
     /// </para>
     /// </remarks>
-    bool IDictionary.IsReadOnly
-    {
-      get { return false; }
-    }
+    bool IDictionary.IsReadOnly => false;
 
     /// <summary>
     /// See <see cref="IDictionary.this"/>
@@ -235,93 +215,25 @@
     /// </para>
     /// </remarks>
     /// <exception cref="ArgumentException">Thrown if the <paramref name="key"/> is not a string</exception>
-    object IDictionary.this[object key]
+    object? IDictionary.this[object key]
     {
       get
       {
-        if (!(key is string))
+        if (key is not string k)
         {
-          throw new ArgumentException("key must be a string", "key");
+          throw new ArgumentException("key must be a string", nameof(key));
         }
-        return InnerHashtable[key];
+        InnerHashtable.TryGetValue(k, out object? val);
+        return val;
       }
       set
       {
-        if (!(key is string))
+        if (key is not string k)
         {
-          throw new ArgumentException("key must be a string", "key");
+          throw new ArgumentException("key must be a string", nameof(key));
         }
-        InnerHashtable[key] = value;
+        InnerHashtable[k] = value;
       }
     }
-
-    /// <summary>
-    /// See <see cref="IDictionary.Values"/>
-    /// </summary>
-    ICollection IDictionary.Values
-    {
-      get { return InnerHashtable.Values; }
-    }
-
-    /// <summary>
-    /// See <see cref="IDictionary.Keys"/>
-    /// </summary>
-    ICollection IDictionary.Keys
-    {
-      get { return InnerHashtable.Keys; }
-    }
-
-    /// <summary>
-    /// See <see cref="IDictionary.IsFixedSize"/>
-    /// </summary>
-    bool IDictionary.IsFixedSize
-    {
-      get { return false; }
-    }
-
-    #endregion
-
-    #region Implementation of ICollection
-
-    /// <summary>
-    /// See <see cref="ICollection.CopyTo"/>
-    /// </summary>
-    /// <param name="array"></param>
-    /// <param name="index"></param>
-    void ICollection.CopyTo(Array array, int index)
-    {
-      InnerHashtable.CopyTo(array, index);
-    }
-
-    /// <summary>
-    /// See <see cref="ICollection.IsSynchronized"/>
-    /// </summary>
-    bool ICollection.IsSynchronized
-    {
-      get { return InnerHashtable.IsSynchronized; }
-    }
-
-    /// <summary>
-    /// See <see cref="ICollection.SyncRoot"/>
-    /// </summary>
-    object ICollection.SyncRoot
-    {
-      get { return InnerHashtable.SyncRoot; }
-    }
-
-    #endregion
-
-    #region Implementation of IEnumerable
-
-    /// <summary>
-    /// See <see cref="IEnumerable.GetEnumerator"/>
-    /// </summary>
-    IEnumerator IEnumerable.GetEnumerator()
-    {
-      return ((IEnumerable)InnerHashtable).GetEnumerator();
-    }
-
-    #endregion
   }
 }
-
diff --git a/src/log4net/Util/PropertyEntry.cs b/src/log4net/Util/PropertyEntry.cs
index 379a285..bac35c8 100644
--- a/src/log4net/Util/PropertyEntry.cs
+++ b/src/log4net/Util/PropertyEntry.cs
@@ -24,49 +24,17 @@
   /// <summary>
   /// A class to hold the key and data for a property set in the config file
   /// </summary>
-  /// <remarks>
-  /// <para>
-  /// A class to hold the key and data for a property set in the config file
-  /// </para>
-  /// </remarks>
   public class PropertyEntry
   {
-    private string m_key = null;
-    private object m_value = null;
-
     /// <summary>
     /// Property Key
     /// </summary>
-    /// <value>
-    /// Property Key
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Property Key.
-    /// </para>
-    /// </remarks>
-    public string Key
-    {
-      get { return m_key; }
-      set { m_key = value; }
-    }
+    public string? Key { get; set; }
 
     /// <summary>
     /// Property Value
     /// </summary>
-    /// <value>
-    /// Property Value
-    /// </value>
-    /// <remarks>
-    /// <para>
-    /// Property Value.
-    /// </para>
-    /// </remarks>
-    public object Value
-    {
-      get { return m_value; }
-      set { m_value = value; }
-    }
+    public object? Value { get; set; }
 
     /// <summary>
     /// Override <c>Object.ToString</c> to return sensible debug info
@@ -74,7 +42,7 @@
     /// <returns>string info about this object</returns>
     public override string ToString()
     {
-      return "PropertyEntry(Key=" + m_key + ", Value=" + m_value + ")";
+      return $"PropertyEntry(Key={Key}, Value={Value})";
     }
   }
 }
diff --git a/src/log4net/Util/ReadOnlyPropertiesDictionary.cs b/src/log4net/Util/ReadOnlyPropertiesDictionary.cs
index 10d77b5..1da7256 100644
--- a/src/log4net/Util/ReadOnlyPropertiesDictionary.cs
+++ b/src/log4net/Util/ReadOnlyPropertiesDictionary.cs
@@ -19,9 +19,12 @@
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Runtime.Serialization;
 using System.Xml;
 
+#nullable enable
+
 namespace log4net.Util
 {
   /// <summary>
@@ -29,10 +32,10 @@
   /// </summary>
   /// <remarks>
   /// <para>
-  /// This collection is readonly and cannot be modified.
+  /// This collection is readonly and cannot be modified. It is not thread-safe.
   /// </para>
   /// <para>
-  /// While this collection is serializable only member 
+  /// While this collection is serializable, only member
   /// objects that are serializable will
   /// be serialized along with this collection.
   /// </para>
@@ -40,18 +43,9 @@
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
   [Serializable]
-  public class ReadOnlyPropertiesDictionary : ISerializable, IDictionary
+  public class ReadOnlyPropertiesDictionary : ISerializable, IDictionary, IDictionary<string, object?>
   {
-    #region Private Instance Fields
-
-    /// <summary>
-    /// The Hashtable used to store the properties data
-    /// </summary>
-    private readonly Hashtable m_hashtable = new Hashtable();
-
-    #endregion Private Instance Fields
-
-    #region Public Instance Constructors
+    private const string ReadOnlyMessage = "This is a read-only dictionary and cannot be modified";
 
     /// <summary>
     /// Constructor
@@ -76,16 +70,12 @@
     /// </remarks>
     public ReadOnlyPropertiesDictionary(ReadOnlyPropertiesDictionary propertiesDictionary)
     {
-      foreach (DictionaryEntry entry in propertiesDictionary)
+      foreach (KeyValuePair<string, object?> entry in propertiesDictionary)
       {
-        InnerHashtable.Add(entry.Key, entry.Value);
+        InnerHashtable[entry.Key] = entry.Value;
       }
     }
 
-    #endregion Public Instance Constructors
-
-    #region Private Instance Constructors
-
     /// <summary>
     /// Deserialization constructor
     /// </summary>
@@ -102,14 +92,10 @@
       foreach (var entry in info)
       {
         // The keys are stored as Xml encoded names
-        InnerHashtable[XmlConvert.DecodeName(entry.Name)] = entry.Value;
+        InnerHashtable[XmlConvert.DecodeName(entry.Name) ?? string.Empty] = entry.Value;
       }
     }
 
-    #endregion Protected Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets the key names.
     /// </summary>
@@ -127,10 +113,40 @@
     }
 
     /// <summary>
-    /// Gets or sets the value of the  property with the specified key.
+    /// See <see cref="IDictionary{TKey,TValue}.ContainsKey(TKey)"/>.
+    /// </summary>
+    public bool ContainsKey(string key) => InnerHashtable.ContainsKey(key);
+
+    /// <summary>
+    /// See <see cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>.
+    /// </summary>
+    public virtual void Add(string key, object? value)
+    {
+      throw new NotSupportedException(ReadOnlyMessage);
+    }
+
+    /// <summary>
+    /// See <see cref="IDictionary{TKey,TValue}.Remove(TKey)"/>.
+    /// </summary>
+    public virtual bool Remove(string key)
+    {
+      throw new NotSupportedException(ReadOnlyMessage);
+    }
+
+    /// <summary>
+    /// See <see cref="IDictionary{TKey,TValue}.TryGetValue(TKey,out TValue)"/>.
+    /// </summary>
+    public bool TryGetValue(string key, out object? value)
+    {
+      return InnerHashtable.TryGetValue(key, out value);
+    }
+
+    /// <summary>
+    /// Gets or sets the value of the property with the specified key.
     /// </summary>
     /// <value>
-    /// The value of the property with the specified key.
+    /// The value of the property with the specified key, or null if a property is not present in the dictionary.
+    /// Note this is the <see cref="IDictionary"/> semantic, not that of <see cref="IDictionary{TKey,TValue}"/>.
     /// </value>
     /// <param name="key">The key of the property to get or set.</param>
     /// <remarks>
@@ -140,16 +156,16 @@
     /// a serialization operation is performed.
     /// </para>
     /// </remarks>
-    public virtual object this[string key]
+    public virtual object? this[string key]
     {
-      get { return InnerHashtable[key]; }
-      set { throw new NotSupportedException("This is a Read Only Dictionary and can not be modified"); }
+      get
+      {
+        InnerHashtable.TryGetValue(key, out object? val);
+        return val;
+      }
+      set => throw new NotSupportedException(ReadOnlyMessage);
     }
 
-    #endregion Public Instance Properties
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Test if the dictionary contains a specified key
     /// </summary>
@@ -162,11 +178,9 @@
     /// </remarks>
     public bool Contains(string key)
     {
-      return InnerHashtable.Contains(key);
+      return InnerHashtable.ContainsKey(key);
     }
 
-    #endregion
-
     /// <summary>
     /// The hashtable used to store the properties
     /// </summary>
@@ -178,12 +192,7 @@
     /// The hashtable used to store the properties
     /// </para>
     /// </remarks>
-    protected Hashtable InnerHashtable
-    {
-      get { return m_hashtable; }
-    }
-
-    #region Implementation of ISerializable
+    protected Dictionary<string, object?> InnerHashtable { get; } = new(StringComparer.Ordinal);
 
     /// <summary>
     /// Serializes this object into the <see cref="SerializationInfo" /> provided.
@@ -199,39 +208,27 @@
     [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, SerializationFormatter = true)]
     public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
     {
-      foreach (DictionaryEntry entry in InnerHashtable.Clone() as IDictionary)
+      foreach (KeyValuePair<string, object?> entry in InnerHashtable)
       {
-        var entryKey = entry.Key as string;
-        if (entryKey is null)
-        {
-          continue;
-        }
-
-        var entryValue = entry.Value;
-
         // If value is serializable then we add it to the list
-        var isSerializable = entryValue?.GetType().IsSerializable ?? false;
+        var isSerializable = entry.Value?.GetType().IsSerializable ?? false;
         if (!isSerializable)
         {
           continue;
         }
 
-        // Store the keys as an Xml encoded local name as it may contain colons (':') 
+        // Store the keys as an XML encoded local name as it may contain colons (':')
         // which are NOT escaped by the Xml Serialization framework.
         // This must be a bug in the serialization framework as we cannot be expected
         // to know the implementation details of all the possible transport layers.
-        var localKeyName = XmlConvert.EncodeLocalName(entryKey);
+        var localKeyName = XmlConvert.EncodeLocalName(entry.Key);
         if (localKeyName is not null)
         {
-          info.AddValue(localKeyName, entryValue);
+          info.AddValue(localKeyName, entry.Value);
         }
       }
     }
 
-    #endregion Implementation of ISerializable
-
-    #region Implementation of IDictionary
-
     /// <summary>
     /// See <see cref="IDictionary.GetEnumerator"/>
     /// </summary>
@@ -241,131 +238,161 @@
     }
 
     /// <summary>
+    /// See <see cref="IEnumerable{T}.GetEnumerator"/>
+    /// </summary>
+    IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator()
+    {
+      return InnerHashtable.GetEnumerator();
+    }
+
+    /// <summary>
     /// See <see cref="IDictionary.Remove"/>
     /// </summary>
     /// <param name="key"></param>
     void IDictionary.Remove(object key)
     {
-      throw new NotSupportedException("This is a Read Only Dictionary and can not be modified");
+      throw new NotSupportedException(ReadOnlyMessage);
     }
 
     /// <summary>
     /// See <see cref="IDictionary.Contains"/>
     /// </summary>
-    /// <param name="key"></param>
-    /// <returns></returns>
     bool IDictionary.Contains(object key)
     {
-      return InnerHashtable.Contains(key);
+      if (key is not string k)
+      {
+        throw new ArgumentException("key must be a string");
+      }
+      return InnerHashtable.ContainsKey(k);
     }
 
     /// <summary>
-    /// Remove all properties from the properties collection
+    /// See <see cref="ICollection{T}.Add(T)"/>.
+    /// </summary>
+    public void Add(KeyValuePair<string, object?> item)
+    {
+      InnerHashtable.Add(item.Key, item.Value);
+    }
+
+    /// <summary>
+    /// Removes all properties from the properties collection
     /// </summary>
     public virtual void Clear()
     {
-      throw new NotSupportedException("This is a Read Only Dictionary and can not be modified");
+      throw new NotSupportedException(ReadOnlyMessage);
     }
 
     /// <summary>
-    /// See <see cref="IDictionary.Add"/>
+    /// See <see cref="ICollection{T}.Contains(T)"/>.
     /// </summary>
-    /// <param name="key"></param>
-    /// <param name="value"></param>
+    public bool Contains(KeyValuePair<string, object?> item)
+    {
+      return InnerHashtable.TryGetValue(item.Key, out object? v) && item.Value == v;
+    }
+
+    /// <summary>
+    /// See <see cref="ICollection{T}.CopyTo(T[],int)"/>.
+    /// </summary>
+    public void CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex)
+    {
+      int i = arrayIndex;
+      foreach (var kvp in InnerHashtable)
+      {
+        array[i] = kvp;
+        i++;
+      }
+    }
+
+    /// <summary>
+    /// See <see cref="ICollection{T}.Remove(T)"/>.
+    /// </summary>
+    public bool Remove(KeyValuePair<string, object?> item)
+    {
+      return InnerHashtable.Remove(item.Key);
+    }
+
+    /// <summary>
+    /// See <see cref="IDictionary.Add"/>.
+    /// </summary>
     void IDictionary.Add(object key, object value)
     {
-      throw new NotSupportedException("This is a Read Only Dictionary and can not be modified");
+      throw new NotSupportedException(ReadOnlyMessage);
     }
 
     /// <summary>
-    /// See <see cref="IDictionary.IsReadOnly"/>
+    /// See <see cref="IDictionary.IsReadOnly"/>.
     /// </summary>
-    bool IDictionary.IsReadOnly
-    {
-      get { return true; }
-    }
+    bool IDictionary.IsReadOnly => true;
 
     /// <summary>
     /// See <see cref="IDictionary.this[object]"/>
     /// </summary>
-    object IDictionary.this[object key]
+    object? IDictionary.this[object key]
     {
       get
       {
-        if (!(key is string)) throw new ArgumentException("key must be a string");
-        return InnerHashtable[key];
+        if (key is not string k)
+        {
+          throw new ArgumentException("key must be a string", nameof(key));
+        }
+        InnerHashtable.TryGetValue(k, out object? val);
+        return val;
       }
-      set
-      {
-        throw new NotSupportedException("This is a Read Only Dictionary and can not be modified");
-      }
+      set => throw new NotSupportedException(ReadOnlyMessage);
     }
 
     /// <summary>
+    /// See <see cref="IDictionary{TKey,TValue}.Keys"/>.
+    /// </summary>
+    public ICollection<string> Keys => InnerHashtable.Keys;
+
+    /// <summary>
+    /// See <see cref="IDictionary{TKey,TValue}.Values"/>.
+    /// </summary>
+    public ICollection<object?> Values => InnerHashtable.Values;
+
+    /// <summary>
     /// See <see cref="IDictionary.Values"/>
     /// </summary>
-    ICollection IDictionary.Values
-    {
-      get { return InnerHashtable.Values; }
-    }
+    ICollection IDictionary.Values => InnerHashtable.Values;
 
     /// <summary>
     /// See <see cref="IDictionary.Keys"/>
     /// </summary>
-    ICollection IDictionary.Keys
-    {
-      get { return InnerHashtable.Keys; }
-    }
+    ICollection IDictionary.Keys => InnerHashtable.Keys;
 
     /// <summary>
     /// See <see cref="IDictionary.IsFixedSize"/>
     /// </summary>
-    bool IDictionary.IsFixedSize
-    {
-      get { return InnerHashtable.IsFixedSize; }
-    }
-
-    #endregion
-
-    #region Implementation of ICollection
+    bool IDictionary.IsFixedSize => false;
 
     /// <summary>
     /// See <see cref="ICollection.CopyTo"/>
     /// </summary>
-    /// <param name="array"></param>
-    /// <param name="index"></param>
     void ICollection.CopyTo(Array array, int index)
     {
-      InnerHashtable.CopyTo(array, index);
+      ((ICollection)InnerHashtable).CopyTo(array, index);
     }
 
     /// <summary>
     /// See <see cref="ICollection.IsSynchronized"/>
     /// </summary>
-    bool ICollection.IsSynchronized
-    {
-      get { return InnerHashtable.IsSynchronized; }
-    }
+    bool ICollection.IsSynchronized => false;
 
     /// <summary>
     /// The number of properties in this collection
     /// </summary>
-    public int Count
-    {
-      get { return InnerHashtable.Count; }
-    }
+    public int Count => InnerHashtable.Count;
+
+    /// <summary>
+    /// See <see cref="IDictionary.IsReadOnly"/>.
+    /// </summary>
+    public bool IsReadOnly => true;
 
     /// <summary>
     /// See <see cref="ICollection.SyncRoot"/>
     /// </summary>
-    object ICollection.SyncRoot
-    {
-      get { return InnerHashtable.SyncRoot; }
-    }
-
-    #endregion
-
-    #region Implementation of IEnumerable
+    object ICollection.SyncRoot => InnerHashtable;
 
     /// <summary>
     /// See <see cref="IEnumerable.GetEnumerator"/>
@@ -374,8 +401,5 @@
     {
       return ((IEnumerable)InnerHashtable).GetEnumerator();
     }
-
-    #endregion
   }
 }
-
diff --git a/src/log4net/Util/SystemStringFormat.cs b/src/log4net/Util/SystemStringFormat.cs
index 2a94531..65d7bc6 100644
--- a/src/log4net/Util/SystemStringFormat.cs
+++ b/src/log4net/Util/SystemStringFormat.cs
@@ -19,23 +19,16 @@
 
 using System;
 using System.Text;
-using System.Xml;
-using System.Text.RegularExpressions;
 
 namespace log4net.Util
 {
   /// <summary>
   /// Utility class that represents a format string.
   /// </summary>
-  /// <remarks>
-  /// <para>
-  /// Utility class that represents a format string.
-  /// </para>
-  /// </remarks>
   /// <author>Nicko Cadell</author>
   public sealed class SystemStringFormat
   {
-    private readonly IFormatProvider m_provider;
+    private readonly IFormatProvider? m_provider;
 
     /// <summary>
     /// Format
@@ -47,34 +40,28 @@
     /// </summary>
     public object[] Args { get; set; }
 
-    #region Constructor
-
     /// <summary>
     /// Initialise the <see cref="SystemStringFormat"/>
     /// </summary>
     /// <param name="provider">An <see cref="System.IFormatProvider"/> that supplies culture-specific formatting information.</param>
     /// <param name="format">A <see cref="System.String"/> containing zero or more format items.</param>
     /// <param name="args">An <see cref="System.Object"/> array containing zero or more objects to format.</param>
-    public SystemStringFormat(IFormatProvider provider, string format, params object[] args)
+    public SystemStringFormat(IFormatProvider? provider, string format, params object[] args)
     {
       m_provider = provider;
       Format = format;
       Args = args;
     }
 
-    #endregion Constructor
-
     /// <summary>
     /// Format the string and arguments
     /// </summary>
     /// <returns>the formatted string</returns>
-    public override string ToString()
+    public override string? ToString()
     {
       return StringFormat(m_provider, Format, Args);
     }
 
-    #region StringFormat
-
     /// <summary>
     /// Replaces the format item in a specified <see cref="System.String"/> with the text equivalent 
     /// of the value of a corresponding <see cref="System.Object"/> instance in a specified array.
@@ -93,28 +80,28 @@
     /// exception and arguments are returned in the result string.
     /// </para>
     /// </remarks>
-    private static string StringFormat(IFormatProvider provider, string format, params object[] args)
+    private static string? StringFormat(IFormatProvider? provider, string? format, params object[]? args)
     {
       try
       {
         // The format is missing, log null value
-        if (format == null)
+        if (format is null)
         {
           return null;
         }
 
         // The args are missing - should not happen unless we are called explicitly with a null array
-        if (args == null)
+        if (args is null)
         {
           return format;
         }
 
         // Try to format the string
-        return String.Format(provider, format, args);
+        return string.Format(provider, format, args);
       }
       catch (Exception ex)
       {
-        log4net.Util.LogLog.Warn(declaringType, "Exception while rendering format [" + format + "]", ex);
+        LogLog.Warn(declaringType, $"Exception while rendering format [{format}]", ex);
         return StringFormatError(ex, format, args);
       }
     }
@@ -122,13 +109,13 @@
     /// <summary>
     /// Process an error during StringFormat
     /// </summary>
-    private static string StringFormatError(Exception formatException, string format, object[] args)
+    private static string StringFormatError(Exception? formatException, string? format, object[]? args)
     {
       try
       {
-        StringBuilder buf = new StringBuilder("<log4net.Error>");
+        var buf = new StringBuilder("<log4net.Error>", 100);
 
-        if (formatException != null)
+        if (formatException is not null)
         {
           buf.Append("Exception during StringFormat: ").Append(formatException.Message);
         }
@@ -146,7 +133,7 @@
       }
       catch (Exception ex)
       {
-        log4net.Util.LogLog.Error(declaringType, "INTERNAL ERROR during StringFormat error handling", ex);
+        LogLog.Error(declaringType, "INTERNAL ERROR during StringFormat error handling", ex);
         return "<log4net.Error>Exception during StringFormat. See Internal Log.</log4net.Error>";
       }
     }
@@ -154,9 +141,9 @@
     /// <summary>
     /// Dump the contents of an array into a string builder
     /// </summary>
-    private static void RenderArray(Array array, StringBuilder buffer)
+    private static void RenderArray(Array? array, StringBuilder buffer)
     {
-      if (array == null)
+      if (array is null)
       {
         buffer.Append(SystemInfo.NullText);
       }
@@ -164,7 +151,7 @@
       {
         if (array.Rank != 1)
         {
-          buffer.Append(array.ToString());
+          buffer.Append(array);
         }
         else
         {
@@ -188,9 +175,9 @@
     /// <summary>
     /// Dump an object to a string
     /// </summary>
-    private static void RenderObject(Object obj, StringBuilder buffer)
+    private static void RenderObject(object? obj, StringBuilder buffer)
     {
-      if (obj == null)
+      if (obj is null)
       {
         buffer.Append(SystemInfo.NullText);
       }
@@ -207,10 +194,6 @@
       }
     }
 
-    #endregion StringFormat
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the SystemStringFormat class.
     /// </summary>
@@ -219,7 +202,5 @@
     /// log message.
     /// </remarks>
     private static readonly Type declaringType = typeof(SystemStringFormat);
-
-    #endregion Private Static Fields
   }
 }
diff --git a/src/log4net/Util/ThreadContextProperties.cs b/src/log4net/Util/ThreadContextProperties.cs
index 39040a6..939f8f7 100644
--- a/src/log4net/Util/ThreadContextProperties.cs
+++ b/src/log4net/Util/ThreadContextProperties.cs
@@ -19,6 +19,8 @@
 
 using System;
 
+#nullable enable
+
 namespace log4net.Util
 {
   /// <summary>
@@ -33,17 +35,11 @@
   /// <author>Nicko Cadell</author>
   public sealed class ThreadContextProperties : ContextPropertiesBase
   {
-    #region Private Instance Fields
-
     /// <summary>
     /// Each thread will automatically have its instance.
     /// </summary>
     [ThreadStatic]
-    private static PropertiesDictionary _dictionary;
-
-    #endregion Private Instance Fields
-
-    #region Public Instance Constructors
+    private static PropertiesDictionary? _dictionary;
 
     /// <summary>
     /// Internal constructor
@@ -57,10 +53,6 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Instance Properties
-
     /// <summary>
     /// Gets or sets the value of a property
     /// </summary>
@@ -72,26 +64,12 @@
     /// Gets or sets the value of a property
     /// </para>
     /// </remarks>
-    public override object this[string key]
+    public override object? this[string key]
     {
-      get
-      {
-        if (_dictionary != null)
-        {
-          return _dictionary[key];
-        }
-        return null;
-      }
-      set
-      {
-        GetProperties(true)[key] = value;
-      }
+      get => _dictionary?[key];
+      set => GetProperties(true)![key] = value;
     }
 
-    #endregion Public Instance Properties
-
-    #region Public Instance Methods
-
     /// <summary>
     /// Remove a property
     /// </summary>
@@ -101,13 +79,7 @@
     /// Remove a property
     /// </para>
     /// </remarks>
-    public void Remove(string key)
-    {
-      if (_dictionary != null)
-      {
-        _dictionary.Remove(key);
-      }
-    }
+    public void Remove(string key) => _dictionary?.Remove(key);
 
     /// <summary>
     /// Get the keys stored in the properties.
@@ -116,14 +88,7 @@
     /// Gets the keys stored in the properties.
     /// </para>
     /// <returns>a set of the defined keys</returns>
-    public string[] GetKeys()
-    {
-      if (_dictionary != null)
-      {
-        return _dictionary.GetKeys();
-      }
-      return null;
-    }
+    public string[]? GetKeys() => _dictionary?.GetKeys();
 
     /// <summary>
     /// Clear all properties
@@ -133,22 +98,12 @@
     /// Clear all properties
     /// </para>
     /// </remarks>
-    public void Clear()
-    {
-      if (_dictionary != null)
-      {
-        _dictionary.Clear();
-      }
-    }
-
-    #endregion Public Instance Methods
-
-    #region Internal Instance Methods
+    public void Clear() => _dictionary?.Clear();
 
     /// <summary>
     /// Get the <c>PropertiesDictionary</c> for this thread.
     /// </summary>
-    /// <param name="create">create the dictionary if it does not exist, otherwise return null if does not exist</param>
+    /// <param name="create">create the dictionary if it does not exist, otherwise return null if it does not exist</param>
     /// <returns>the properties for this thread</returns>
     /// <remarks>
     /// <para>
@@ -157,15 +112,13 @@
     /// caller must clone the collection before doing so.
     /// </para>
     /// </remarks>
-    internal PropertiesDictionary GetProperties(bool create)
+    internal PropertiesDictionary? GetProperties(bool create)
     {
-      if (_dictionary == null && create)
+      if (_dictionary is null && create)
       {
         _dictionary = new PropertiesDictionary();
       }
       return _dictionary;
     }
-
-    #endregion Internal Instance Methods
   }
 }
\ No newline at end of file
diff --git a/src/log4net/Util/ThreadContextStack.cs b/src/log4net/Util/ThreadContextStack.cs
index 5beb1dd..ff72943 100644
--- a/src/log4net/Util/ThreadContextStack.cs
+++ b/src/log4net/Util/ThreadContextStack.cs
@@ -18,8 +18,8 @@
 #endregion
 
 using System;
-
 using System.Collections;
+using System.Collections.Generic;
 
 using log4net.Core;
 
@@ -36,16 +36,10 @@
   /// <author>Nicko Cadell</author>
   public sealed class ThreadContextStack : IFixingRequired
   {
-    #region Private Static Fields
-
     /// <summary>
     /// The stack store.
     /// </summary>
-    private Stack m_stack = new Stack();
-
-    #endregion Private Static Fields
-
-    #region Public Instance Constructors
+    private readonly Stack<StackFrame> m_stack = new();
 
     /// <summary>
     /// Internal constructor
@@ -59,10 +53,6 @@
     {
     }
 
-    #endregion Public Instance Constructors
-
-    #region Public Properties
-
     /// <summary>
     /// The number of messages in the stack
     /// </summary>
@@ -76,14 +66,7 @@
     /// minus the number of times <see cref="Pop"/> has been called.
     /// </para>
     /// </remarks>
-    public int Count
-    {
-      get { return m_stack.Count; }
-    }
-
-    #endregion // Public Properties
-
-    #region Public Methods
+    public int Count => m_stack.Count;
 
     /// <summary>
     /// Clears all the contextual information held in this stack.
@@ -100,10 +83,7 @@
     /// syntax.
     /// </para>
     /// </remarks>
-    public void Clear()
-    {
-      m_stack.Clear();
-    }
+    public void Clear() => m_stack.Clear();
 
     /// <summary>
     /// Removes the top context from this stack.
@@ -116,14 +96,14 @@
     /// empty string (not <see langword="null"/>) is returned.
     /// </para>
     /// </remarks>
-    public string Pop()
+    public string? Pop()
     {
-      Stack stack = m_stack;
+      Stack<StackFrame> stack = m_stack;
       if (stack.Count > 0)
       {
-        return ((StackFrame)(stack.Pop())).Message;
+        return stack.Pop().Message;
       }
-      return "";
+      return string.Empty;
     }
 
     /// <summary>
@@ -149,10 +129,10 @@
     ///  }
     /// </code>
     /// </example>
-    public IDisposable Push(string message)
+    public IDisposable Push(string? message)
     {
-      Stack stack = m_stack;
-      stack.Push(new StackFrame(message, (stack.Count > 0) ? (StackFrame)stack.Peek() : null));
+      Stack<StackFrame> stack = m_stack;
+      stack.Push(new StackFrame(message, (stack.Count > 0) ? stack.Peek() : null));
 
       return new AutoPopStackFrame(stack, stack.Count - 1);
     }
@@ -167,30 +147,26 @@
     /// empty string (not <see langword="null"/>) is returned.
     /// </para>
     /// </remarks>
-    public string Peek()
+    public string? Peek()
     {
-      Stack stack = m_stack;
+      Stack<StackFrame> stack = m_stack;
       if (stack.Count > 0)
       {
-        return ((StackFrame)stack.Peek()).Message;
+        return stack.Peek().Message;
       }
       return "";
     }
 
-    #endregion Public Methods
-
-    #region Internal Methods
-
     /// <summary>
     /// Gets the current context information for this stack.
     /// </summary>
     /// <returns>The current context information.</returns>
-    internal string GetFullMessage()
+    internal string? GetFullMessage()
     {
-      Stack stack = m_stack;
+      Stack<StackFrame> stack = m_stack;
       if (stack.Count > 0)
       {
-        return ((StackFrame)(stack.Peek())).FullMessage;
+        return stack.Peek().FullMessage;
       }
       return null;
     }
@@ -201,46 +177,34 @@
     /// <value>The internal storage stack</value>
     /// <remarks>
     /// <para>
-    /// This property is provided only to support backward compatability 
-    /// of the <see cref="NDC"/>. Tytpically the internal stack should not
+    /// This property is provided only to support backward compatibility 
+    /// of the <see cref="NDC"/>. Typically the internal stack should not
     /// be modified.
     /// </para>
     /// </remarks>
     internal Stack InternalStack
     {
-      get { return m_stack; }
-      set { m_stack = value; }
+        get => new Stack(new Stack(m_stack));
+        set
+        {
+          m_stack.Clear();
+          var frames = (StackFrame[])value.ToArray();
+          for (int i = frames.Length - 1; i >= 0; i--)
+          {
+             m_stack.Push(frames[i]);
+          }
+        }
     }
 
-    #endregion Internal Methods
-
     /// <summary>
     /// Gets the current context information for this stack.
     /// </summary>
-    /// <returns>Gets the current context information</returns>
-    /// <remarks>
-    /// <para>
-    /// Gets the current context information for this stack.
-    /// </para>
-    /// </remarks>
-    public override string ToString()
-    {
-      return GetFullMessage();
-    }
+    public override string? ToString() => GetFullMessage();
 
     /// <summary>
     /// Get a portable version of this object
     /// </summary>
-    /// <returns>the portable instance of this object</returns>
-    /// <remarks>
-    /// <para>
-    /// Get a cross thread portable version of this object
-    /// </para>
-    /// </remarks>
-    object IFixingRequired.GetFixedObject()
-    {
-      return GetFullMessage();
-    }
+    object? IFixingRequired.GetFixedObject() => GetFullMessage();
 
     /// <summary>
     /// Inner class used to represent a single context frame in the stack.
@@ -250,17 +214,10 @@
     /// Inner class used to represent a single context frame in the stack.
     /// </para>
     /// </remarks>
-    private sealed class StackFrame
+    internal sealed class StackFrame
     {
-      #region Private Instance Fields
-
-      private readonly string m_message;
-      private readonly StackFrame m_parent;
-      private string m_fullMessage = null;
-
-      #endregion
-
-      #region Internal Instance Constructors
+      private readonly StackFrame? m_parent;
+      private string? m_fullMessage;
 
       /// <summary>
       /// Constructor
@@ -273,21 +230,17 @@
       /// with the specified message and parent context.
       /// </para>
       /// </remarks>
-      internal StackFrame(string message, StackFrame parent)
+      internal StackFrame(string? message, StackFrame? parent)
       {
-        m_message = message;
+        Message = message;
         m_parent = parent;
 
-        if (parent == null)
+        if (parent is null)
         {
           m_fullMessage = message;
         }
       }
 
-      #endregion Internal Instance Constructors
-
-      #region Internal Instance Properties
-
       /// <summary>
       /// Get the message.
       /// </summary>
@@ -297,10 +250,7 @@
       /// Get the message.
       /// </para>
       /// </remarks>
-      internal string Message
-      {
-        get { return m_message; }
-      }
+      internal string? Message { get; }
 
       /// <summary>
       /// Gets the full text of the context down to the root level.
@@ -313,19 +263,17 @@
       /// Gets the full text of the context down to the root level.
       /// </para>
       /// </remarks>
-      internal string FullMessage
+      internal string? FullMessage
       {
         get
         {
-          if (m_fullMessage == null && m_parent != null)
+          if (m_fullMessage is null && m_parent is not null)
           {
-            m_fullMessage = string.Concat(m_parent.FullMessage, " ", m_message);
+            m_fullMessage = string.Concat(m_parent.FullMessage, " ", Message);
           }
           return m_fullMessage;
         }
       }
-
-      #endregion Internal Instance Properties
     }
 
     /// <summary>
@@ -337,56 +285,36 @@
     /// with the <see langword="using"/> pattern to remove the stack frame at the end of the scope.
     /// </para>
     /// </remarks>
-    private struct AutoPopStackFrame : IDisposable
+    private readonly struct AutoPopStackFrame : IDisposable
     {
-      #region Private Instance Fields
-
       /// <summary>
       /// The ThreadContextStack internal stack
       /// </summary>
-      private Stack m_frameStack;
+      private readonly Stack<StackFrame> m_frameStack;
 
       /// <summary>
       /// The depth to trim the stack to when this instance is disposed
       /// </summary>
-      private int m_frameDepth;
-
-      #endregion Private Instance Fields
-
-      #region Internal Instance Constructors
+      private readonly int m_frameDepth;
 
       /// <summary>
-      /// Constructor
+      /// Initializes a new instance of the <see cref="AutoPopStackFrame" /> class with
+      /// the specified stack and return depth.
       /// </summary>
       /// <param name="frameStack">The internal stack used by the ThreadContextStack.</param>
       /// <param name="frameDepth">The depth to return the stack to when this object is disposed.</param>
-      /// <remarks>
-      /// <para>
-      /// Initializes a new instance of the <see cref="AutoPopStackFrame" /> class with
-      /// the specified stack and return depth.
-      /// </para>
-      /// </remarks>
-      internal AutoPopStackFrame(Stack frameStack, int frameDepth)
+      internal AutoPopStackFrame(Stack<StackFrame> frameStack, int frameDepth)
       {
         m_frameStack = frameStack;
         m_frameDepth = frameDepth;
       }
 
-      #endregion Internal Instance Constructors
-
-      #region Implementation of IDisposable
-
       /// <summary>
       /// Returns the stack to the correct depth.
       /// </summary>
-      /// <remarks>
-      /// <para>
-      /// Returns the stack to the correct depth.
-      /// </para>
-      /// </remarks>
       public void Dispose()
       {
-        if (m_frameDepth >= 0 && m_frameStack != null)
+        if (m_frameDepth >= 0)
         {
           while (m_frameStack.Count > m_frameDepth)
           {
@@ -394,8 +322,6 @@
           }
         }
       }
-
-      #endregion Implementation of IDisposable
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/log4net/Util/TypeConverters/ConverterRegistry.cs b/src/log4net/Util/TypeConverters/ConverterRegistry.cs
index eaf29ad..fb0b4ca 100644
--- a/src/log4net/Util/TypeConverters/ConverterRegistry.cs
+++ b/src/log4net/Util/TypeConverters/ConverterRegistry.cs
@@ -18,7 +18,7 @@
 #endregion
 
 using System;
-using System.Collections;
+using System.Collections.Concurrent;
 
 namespace log4net.Util.TypeConverters
 {
@@ -41,24 +41,8 @@
   /// <seealso cref="IConvertTo"/>
   /// <author>Nicko Cadell</author>
   /// <author>Gert Driesen</author>
-  public sealed class ConverterRegistry
+  public static class ConverterRegistry
   {
-    #region Private Constructors
-
-    /// <summary>
-    /// Private constructor
-    /// </summary>
-    /// <remarks>
-    /// Initializes a new instance of the <see cref="ConverterRegistry" /> class.
-    /// </remarks>
-    private ConverterRegistry()
-    {
-    }
-
-    #endregion Private Constructors
-
-    #region Static Constructor
-
     /// <summary>
     /// Static constructor.
     /// </summary>
@@ -72,16 +56,12 @@
       // Add predefined converters here
       AddConverter(typeof(bool), typeof(BooleanConverter));
       AddConverter(typeof(System.Text.Encoding), typeof(EncodingConverter));
-      AddConverter(typeof(System.Type), typeof(TypeConverter));
-      AddConverter(typeof(log4net.Layout.PatternLayout), typeof(PatternLayoutConverter));
-      AddConverter(typeof(log4net.Util.PatternString), typeof(PatternStringConverter));
+      AddConverter(typeof(Type), typeof(TypeConverter));
+      AddConverter(typeof(Layout.PatternLayout), typeof(PatternLayoutConverter));
+      AddConverter(typeof(PatternString), typeof(PatternStringConverter));
       AddConverter(typeof(System.Net.IPAddress), typeof(IPAddressConverter));
     }
 
-    #endregion Static Constructor
-
-    #region Public Static Methods
-
     /// <summary>
     /// Adds a converter for a specific type.
     /// </summary>
@@ -92,13 +72,17 @@
     /// Adds a converter instance for a specific type.
     /// </para>
     /// </remarks>
-    public static void AddConverter(Type destinationType, object converter)
+    public static void AddConverter(Type? destinationType, object? converter)
     {
-      if (destinationType != null && converter != null)
+      if (destinationType is not null && converter is not null)
       {
-        lock (s_type2converter)
+        if (converter is IConvertTo convertTo)
         {
-          s_type2converter[destinationType] = converter;
+          s_type2ConvertTo[destinationType] = convertTo;
+        }
+        else if (converter is IConvertFrom convertFrom)
+        {
+          s_type2ConvertFrom[destinationType] = convertFrom;
         }
       }
     }
@@ -132,32 +116,26 @@
     /// Gets the type converter to use to convert values to the destination type.
     /// </para>
     /// </remarks>
-    public static IConvertTo GetConvertTo(Type sourceType, Type destinationType)
+    public static IConvertTo? GetConvertTo(Type sourceType, Type destinationType)
     {
       // TODO: Support inheriting type converters.
       // i.e. getting a type converter for a base of sourceType
 
       // TODO: Is destinationType required? We don't use it for anything.
 
-      lock (s_type2converter)
+      // Look up in the static registry
+      if (!s_type2ConvertTo.TryGetValue(sourceType, out IConvertTo? converter))
       {
-        // Lookup in the static registry
-        IConvertTo converter = s_type2converter[sourceType] as IConvertTo;
-
-        if (converter == null)
+        // Look up using attributes
+        converter = GetConverterFromAttribute(sourceType) as IConvertTo;
+        if (converter is not null)
         {
-          // Lookup using attributes
-          converter = GetConverterFromAttribute(sourceType) as IConvertTo;
-
-          if (converter != null)
-          {
-            // Store in registry
-            s_type2converter[sourceType] = converter;
-          }
+          // Store in registry
+          s_type2ConvertTo[sourceType] = converter;
         }
-
-        return converter;
       }
+
+      return converter;
     }
 
     /// <summary>
@@ -173,30 +151,24 @@
     /// Gets the type converter to use to convert values to the destination type.
     /// </para>
     /// </remarks>
-    public static IConvertFrom GetConvertFrom(Type destinationType)
+    public static IConvertFrom? GetConvertFrom(Type destinationType)
     {
       // TODO: Support inheriting type converters.
       // i.e. getting a type converter for a base of destinationType
 
-      lock (s_type2converter)
+      // Lookup in the static registry
+      if (!s_type2ConvertFrom.TryGetValue(destinationType, out IConvertFrom? converter))
       {
-        // Lookup in the static registry
-        IConvertFrom converter = s_type2converter[destinationType] as IConvertFrom;
-
-        if (converter == null)
+        // Look up using attributes
+        converter = GetConverterFromAttribute(destinationType) as IConvertFrom;
+        if (converter is not null)
         {
-          // Lookup using attributes
-          converter = GetConverterFromAttribute(destinationType) as IConvertFrom;
-
-          if (converter != null)
-          {
-            // Store in registry
-            s_type2converter[destinationType] = converter;
-          }
+          // Store in registry
+          s_type2ConvertFrom[destinationType] = converter;
         }
-
-        return converter;
       }
+
+      return converter;
     }
 
     /// <summary>
@@ -208,24 +180,18 @@
     /// The type converter instance to use for type conversions or <c>null</c> 
     /// if no type converter is found.
     /// </returns>
-    private static object GetConverterFromAttribute(Type destinationType)
+    private static object? GetConverterFromAttribute(Type destinationType)
     {
       // Look for an attribute on the destination type
-      var attributes = destinationType
-        .GetCustomAttributes(typeof(TypeConverterAttribute), true);
-      if (attributes is null)
-      {
-        // I assume the original null check is perhaps for CF or older .NET versions -- please leave in place
-        return null;
-      }
-
+      object[] attributes = destinationType.GetCustomAttributes(typeof(TypeConverterAttribute), true);
       foreach (var attribute in attributes)
       {
-        var tcAttr = attribute as TypeConverterAttribute;
-        if (tcAttr != null)
+        if (attribute is TypeConverterAttribute tcAttr)
         {
-          var converterType = SystemInfo.GetTypeFromString(destinationType, tcAttr.ConverterTypeName, false, true);
-          return CreateConverterInstance(converterType);
+          if (SystemInfo.GetTypeFromString(destinationType, tcAttr.ConverterTypeName, false, true) is Type converterType)
+          {
+            return CreateConverterInstance(converterType);
+          }
         }
       }
 
@@ -248,13 +214,8 @@
     /// and must have a public default (no argument) constructor.
     /// </para>
     /// </remarks>
-    private static object CreateConverterInstance(Type converterType)
+    private static object? CreateConverterInstance(Type converterType)
     {
-      if (converterType == null)
-      {
-        throw new ArgumentNullException("converterType", "CreateConverterInstance cannot create instance, converterType is null");
-      }
-
       // Check type is a converter
       if (typeof(IConvertFrom).IsAssignableFrom(converterType) || typeof(IConvertTo).IsAssignableFrom(converterType))
       {
@@ -265,20 +226,16 @@
         }
         catch (Exception ex)
         {
-          LogLog.Error(declaringType, "Cannot CreateConverterInstance of type [" + converterType.FullName + "], Exception in call to Activator.CreateInstance", ex);
+          LogLog.Error(declaringType, $"Cannot CreateConverterInstance of type [{converterType.FullName}], exception in call to Activator.CreateInstance", ex);
         }
       }
       else
       {
-        LogLog.Error(declaringType, "Cannot CreateConverterInstance of type [" + converterType.FullName + "], type does not implement IConvertFrom or IConvertTo");
+        LogLog.Error(declaringType, $"Cannot CreateConverterInstance of type [{converterType.FullName}], type does not implement IConvertFrom or IConvertTo");
       }
       return null;
     }
 
-    #endregion Public Static Methods
-
-    #region Private Static Fields
-
     /// <summary>
     /// The fully qualified type of the ConverterRegistry class.
     /// </summary>
@@ -288,11 +245,7 @@
     /// </remarks>
     private static readonly Type declaringType = typeof(ConverterRegistry);
 
-    /// <summary>
-    /// Mapping from <see cref="Type" /> to type converter.
-    /// </summary>
-    private static Hashtable s_type2converter = new Hashtable();
-
-    #endregion
+    private static readonly ConcurrentDictionary<Type, IConvertTo> s_type2ConvertTo = new();
+    private static readonly ConcurrentDictionary<Type, IConvertFrom> s_type2ConvertFrom = new();
   }
 }