Update to C# 10 and upgrade code style (#10105)

diff --git a/csharp/src/AddressBook/AddPerson.cs b/csharp/src/AddressBook/AddPerson.cs
index 889d1d0..eb0ecb3 100644
--- a/csharp/src/AddressBook/AddPerson.cs
+++ b/csharp/src/AddressBook/AddPerson.cs
@@ -107,10 +107,8 @@
 
             if (File.Exists(args[0]))
             {
-                using (Stream file = File.OpenRead(args[0]))
-                {
-                    addressBook = AddressBook.Parser.ParseFrom(file);
-                }
+                using Stream file = File.OpenRead(args[0]);
+                addressBook = AddressBook.Parser.ParseFrom(file);
             }
             else
             {
diff --git a/csharp/src/Directory.Build.props b/csharp/src/Directory.Build.props
new file mode 100644
index 0000000..dd69462
--- /dev/null
+++ b/csharp/src/Directory.Build.props
@@ -0,0 +1,7 @@
+<Project>
+
+  <PropertyGroup>
+    <LangVersion>10.0</LangVersion>
+  </PropertyGroup>
+
+</Project>
diff --git a/csharp/src/Google.Protobuf.Benchmarks/BenchmarkDatasetConfig.cs b/csharp/src/Google.Protobuf.Benchmarks/BenchmarkDatasetConfig.cs
index c075419..b148377 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/BenchmarkDatasetConfig.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/BenchmarkDatasetConfig.cs
@@ -72,16 +72,14 @@
 
         private static byte[] LoadData(string resource)
         {
-            using (var stream = typeof(GoogleMessageBenchmark).Assembly.GetManifestResourceStream($"Google.Protobuf.Benchmarks.{resource}"))
+            using var stream = typeof(GoogleMessageBenchmark).Assembly.GetManifestResourceStream($"Google.Protobuf.Benchmarks.{resource}");
+            if (stream == null)
             {
-                if (stream == null)
-                {
-                    throw new ArgumentException($"Unable to load embedded resource {resource}");
-                }
-                var copy = new MemoryStream();
-                stream.CopyTo(copy);
-                return copy.ToArray();
+                throw new ArgumentException($"Unable to load embedded resource {resource}");
             }
+            var copy = new MemoryStream();
+            stream.CopyTo(copy);
+            return copy.ToArray();
         }
 
         public override string ToString() => Name;
diff --git a/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs b/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs
index 8e6710b..b085234 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs
@@ -49,10 +49,10 @@
     {
         const int MaxMessages = 100;
 
-        SubTest manyWrapperFieldsTest = new SubTest(CreateManyWrapperFieldsMessage(), ManyWrapperFieldsMessage.Parser, () => new ManyWrapperFieldsMessage(), MaxMessages);
-        SubTest manyPrimitiveFieldsTest = new SubTest(CreateManyPrimitiveFieldsMessage(), ManyPrimitiveFieldsMessage.Parser, () => new ManyPrimitiveFieldsMessage(), MaxMessages);
-        SubTest repeatedFieldTest = new SubTest(CreateRepeatedFieldMessage(), GoogleMessage1.Parser, () => new GoogleMessage1(), MaxMessages);
-        SubTest emptyMessageTest = new SubTest(new Empty(), Empty.Parser, () => new Empty(), MaxMessages);
+        private readonly SubTest manyWrapperFieldsTest = new(CreateManyWrapperFieldsMessage(), ManyWrapperFieldsMessage.Parser, () => new ManyWrapperFieldsMessage(), MaxMessages);
+        private readonly SubTest manyPrimitiveFieldsTest = new(CreateManyPrimitiveFieldsMessage(), ManyPrimitiveFieldsMessage.Parser, () => new ManyPrimitiveFieldsMessage(), MaxMessages);
+        private readonly SubTest repeatedFieldTest = new(CreateRepeatedFieldMessage(), GoogleMessage1.Parser, () => new GoogleMessage1(), MaxMessages);
+        private readonly SubTest emptyMessageTest = new(new Empty(), Empty.Parser, () => new Empty(), MaxMessages);
 
         public IEnumerable<int> MessageCountValues => new[] { 10, 100 };
 
@@ -204,8 +204,8 @@
             private readonly byte[] data;
             private readonly byte[] multipleMessagesData;
 
-            private ReadOnlySequence<byte> dataSequence;
-            private ReadOnlySequence<byte> multipleMessagesDataSequence;
+            private readonly ReadOnlySequence<byte> dataSequence;
+            private readonly ReadOnlySequence<byte> multipleMessagesDataSequence;
 
             public SubTest(IMessage message, MessageParser parser, Func<IMessage> factory, int maxMessageCount)
             {
diff --git a/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs b/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
index 6df1c87..64624a4 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
@@ -418,7 +418,7 @@
         private static byte[] CreateBufferWithRandomData(Random random, int valueCount, int encodedSize, int paddingValueCount)
         {
             int bufferSize = (valueCount + paddingValueCount) * encodedSize;
-            byte[] buffer = new byte[bufferSize];
+            var buffer = new byte[bufferSize];
             random.NextBytes(buffer);
             return buffer;
         }
diff --git a/csharp/src/Google.Protobuf.Benchmarks/Program.cs b/csharp/src/Google.Protobuf.Benchmarks/Program.cs
index 037752f..d597ff7 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/Program.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/Program.cs
@@ -44,6 +44,4 @@
             BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
         }
     }
-
-    
 }
diff --git a/csharp/src/Google.Protobuf.Benchmarks/WriteRawPrimitivesBenchmark.cs b/csharp/src/Google.Protobuf.Benchmarks/WriteRawPrimitivesBenchmark.cs
index 66b6b4a..bd0f0ac 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/WriteRawPrimitivesBenchmark.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/WriteRawPrimitivesBenchmark.cs
@@ -32,10 +32,7 @@
 
 using BenchmarkDotNet.Attributes;
 using System;
-using System.Buffers.Binary;
 using System.Collections.Generic;
-using System.IO;
-using System.Buffers;
 using System.Text;
 
 namespace Google.Protobuf.Benchmarks
diff --git a/csharp/src/Google.Protobuf.Conformance/Program.cs b/csharp/src/Google.Protobuf.Conformance/Program.cs
index d721ecf..1c2e9ae 100644
--- a/csharp/src/Google.Protobuf.Conformance/Program.cs
+++ b/csharp/src/Google.Protobuf.Conformance/Program.cs
@@ -43,7 +43,7 @@
     /// </summary>
     class Program
     {
-        private static void Main(string[] args)
+        private static void Main()
         {
             // This way we get the binary streams instead of readers/writers.
             var input = new BinaryReader(Console.OpenStandardInput());
@@ -100,32 +100,22 @@
                             return new ConformanceResponse { Skipped = "CSharp doesn't support skipping unknown fields in json parsing." };
                         }
                         var parser = new JsonParser(new JsonParser.Settings(20, typeRegistry));
-                        switch (request.MessageType)
+                        message = request.MessageType switch
                         {
-                            case "protobuf_test_messages.proto3.TestAllTypesProto3":
-                                message = parser.Parse<ProtobufTestMessages.Proto3.TestAllTypesProto3>(request.JsonPayload);
-                                break;
-                            case "protobuf_test_messages.proto2.TestAllTypesProto2":
-                                message = parser.Parse<ProtobufTestMessages.Proto2.TestAllTypesProto2>(request.JsonPayload);
-                                break;
-                            default:
-                                throw new Exception($" Protobuf request doesn't have specific payload type ({request.MessageType})");
-                        }
+                            "protobuf_test_messages.proto3.TestAllTypesProto3" => parser.Parse<ProtobufTestMessages.Proto3.TestAllTypesProto3>(request.JsonPayload),
+                            "protobuf_test_messages.proto2.TestAllTypesProto2" => parser.Parse<ProtobufTestMessages.Proto2.TestAllTypesProto2>(request.JsonPayload),
+                            _ => throw new Exception($" Protobuf request doesn't have specific payload type ({request.MessageType})"),
+                        };
                         break;
                     case ConformanceRequest.PayloadOneofCase.ProtobufPayload:
-                        switch (request.MessageType)
+                        message = request.MessageType switch
                         {
-                            case "protobuf_test_messages.proto3.TestAllTypesProto3":
-                                message = ProtobufTestMessages.Proto3.TestAllTypesProto3.Parser.ParseFrom(request.ProtobufPayload);
-                                break;
-                            case "protobuf_test_messages.proto2.TestAllTypesProto2":
-                                message = ProtobufTestMessages.Proto2.TestAllTypesProto2.Parser
-                                    .WithExtensionRegistry(proto2ExtensionRegistry)
-                                    .ParseFrom(request.ProtobufPayload);
-                                break;
-                            default:
-                                throw new Exception($" Protobuf request doesn't have specific payload type ({request.MessageType})");
-                        }
+                            "protobuf_test_messages.proto3.TestAllTypesProto3" => ProtobufTestMessages.Proto3.TestAllTypesProto3.Parser.ParseFrom(request.ProtobufPayload),
+                            "protobuf_test_messages.proto2.TestAllTypesProto2" => ProtobufTestMessages.Proto2.TestAllTypesProto2.Parser
+                                                                .WithExtensionRegistry(proto2ExtensionRegistry)
+                                                                .ParseFrom(request.ProtobufPayload),
+                            _ => throw new Exception($" Protobuf request doesn't have specific payload type ({request.MessageType})"),
+                        };
                         break;
 					case ConformanceRequest.PayloadOneofCase.TextPayload:
 						return new ConformanceResponse { Skipped = "CSharp doesn't support text format" };
diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj b/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj
index 8d9d64b..3d0ba71 100644
--- a/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj
+++ b/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj
@@ -7,7 +7,7 @@
   -->
   <PropertyGroup>
     <TargetFrameworks>net462;netstandard1.1;netstandard2.0</TargetFrameworks>
-    <LangVersion>3.0</LangVersion>
+    <LangVersion>10.0</LangVersion>
     <AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>
     <IsPackable>False</IsPackable>
diff --git a/csharp/src/Google.Protobuf.Test/ByteStringTest.cs b/csharp/src/Google.Protobuf.Test/ByteStringTest.cs
index 3810565..b757287 100644
--- a/csharp/src/Google.Protobuf.Test/ByteStringTest.cs
+++ b/csharp/src/Google.Protobuf.Test/ByteStringTest.cs
@@ -70,7 +70,7 @@
             Assert.IsFalse(b1 != b1);
             Assert.IsFalse(b1 != b2);
             Assert.IsTrue(ByteString.Empty == ByteString.Empty);
-#pragma warning disable 1718
+#pragma warning restore 1718
             Assert.IsTrue(b1 != b3);
             Assert.IsTrue(b1 != b4);
             Assert.IsTrue(b1 != null);
@@ -276,12 +276,9 @@
             Span<byte> s = stackalloc byte[data.Length];
             data.CopyTo(s);
 
-            using (UnmanagedMemoryManager<byte> manager = new UnmanagedMemoryManager<byte>(s))
-            {
-                ByteString bs = ByteString.AttachBytes(manager.Memory);
-
-                Assert.AreEqual("Hello world", bs.ToString(Encoding.UTF8));
-            }
+            using var manager = new UnmanagedMemoryManager<byte>(s);
+            ByteString bs = ByteString.AttachBytes(manager.Memory);
+            Assert.AreEqual("Hello world", bs.ToString(Encoding.UTF8));
         }
 
         [Test]
@@ -315,12 +312,9 @@
             Span<byte> s = stackalloc byte[data.Length];
             data.CopyTo(s);
 
-            using (UnmanagedMemoryManager<byte> manager = new UnmanagedMemoryManager<byte>(s))
-            {
-                ByteString bs = ByteString.AttachBytes(manager.Memory);
-
-                Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64());
-            }
+            using var manager = new UnmanagedMemoryManager<byte>(s);
+            ByteString bs = ByteString.AttachBytes(manager.Memory);
+            Assert.AreEqual("SGVsbG8gd29ybGQ=", bs.ToBase64());
         }
 
         [Test]
diff --git a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs
index b84a1b7..a48f3b1 100644
--- a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs
+++ b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs
@@ -563,8 +563,8 @@
             int groupFieldNumber = Proto2.TestAllTypes.OptionalGroupFieldNumber;
 
             // write Proto2.TestAllTypes with "optional_group" set, but use wrong EndGroup closing tag
-            MemoryStream ms = new MemoryStream();
-            CodedOutputStream output = new CodedOutputStream(ms);
+            var ms = new MemoryStream();
+            var output = new CodedOutputStream(ms);
             output.WriteTag(WireFormat.MakeTag(groupFieldNumber, WireFormat.WireType.StartGroup));
             output.WriteGroup(new Proto2.TestAllTypes.Types.OptionalGroup { A = 12345 });
             // end group with different field number
@@ -578,8 +578,8 @@
         [Test]
         public void ReadGroup_UnknownFields_WrongEndGroupTag()
         {
-            MemoryStream ms = new MemoryStream();
-            CodedOutputStream output = new CodedOutputStream(ms);
+            var ms = new MemoryStream();
+            var output = new CodedOutputStream(ms);
             output.WriteTag(WireFormat.MakeTag(14, WireFormat.WireType.StartGroup));
             // end group with different field number
             output.WriteTag(WireFormat.MakeTag(15, WireFormat.WireType.EndGroup));
@@ -654,7 +654,7 @@
             output.Flush();
             ms.Position = 0;
 
-            CodedInputStream input = new CodedInputStream(ms);
+            var input = new CodedInputStream(ms);
 
             Assert.AreEqual(tag, input.ReadTag());
             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadBytes());
@@ -694,26 +694,24 @@
         [Test]
         public void TestSlowPathAvoidance()
         {
-            using (var ms = new MemoryStream())
-            {
-                CodedOutputStream output = new CodedOutputStream(ms);
-                output.WriteTag(1, WireFormat.WireType.LengthDelimited);
-                output.WriteBytes(ByteString.CopyFrom(new byte[100]));
-                output.WriteTag(2, WireFormat.WireType.LengthDelimited);
-                output.WriteBytes(ByteString.CopyFrom(new byte[100]));
-                output.Flush();
+            using var ms = new MemoryStream();
+            var output = new CodedOutputStream(ms);
+            output.WriteTag(1, WireFormat.WireType.LengthDelimited);
+            output.WriteBytes(ByteString.CopyFrom(new byte[100]));
+            output.WriteTag(2, WireFormat.WireType.LengthDelimited);
+            output.WriteBytes(ByteString.CopyFrom(new byte[100]));
+            output.Flush();
 
-                ms.Position = 0;
-                CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2], 0, 0, false);
+            ms.Position = 0;
+            CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2], 0, 0, false);
 
-                uint tag = input.ReadTag();
-                Assert.AreEqual(1, WireFormat.GetTagFieldNumber(tag));
-                Assert.AreEqual(100, input.ReadBytes().Length);
+            uint tag = input.ReadTag();
+            Assert.AreEqual(1, WireFormat.GetTagFieldNumber(tag));
+            Assert.AreEqual(100, input.ReadBytes().Length);
 
-                tag = input.ReadTag();
-                Assert.AreEqual(2, WireFormat.GetTagFieldNumber(tag));
-                Assert.AreEqual(100, input.ReadBytes().Length);
-            }
+            tag = input.ReadTag();
+            Assert.AreEqual(2, WireFormat.GetTagFieldNumber(tag));
+            Assert.AreEqual(100, input.ReadBytes().Length);
         }
 
         [Test]
@@ -927,13 +925,11 @@
         {
             byte[] serializedMessage = GenerateBigSerializedMessage();
             // How many of these big messages do we need to take us near our 2GB limit?
-            int count = Int32.MaxValue / serializedMessage.Length;
+            int count = int.MaxValue / serializedMessage.Length;
             // Now make a MemoryStream that will fake a near-2GB stream of messages by returning
             // our big serialized message 'count' times.
-            using (RepeatingMemoryStream stream = new RepeatingMemoryStream(serializedMessage, count))
-            {
-                Assert.DoesNotThrow(()=>TestAllTypes.Parser.ParseFrom(stream));
-            }
+            using var stream = new RepeatingMemoryStream(serializedMessage, count);
+            Assert.DoesNotThrow(() => TestAllTypes.Parser.ParseFrom(stream));
         }
 
         [Test]
@@ -941,17 +937,15 @@
         {
             byte[] serializedMessage = GenerateBigSerializedMessage();
             // How many of these big messages do we need to take us near our 2GB limit?
-            int count = Int32.MaxValue / serializedMessage.Length;
+            int count = int.MaxValue / serializedMessage.Length;
             // Now add one to take us over the 2GB limit
             count++;
             // Now make a MemoryStream that will fake a near-2GB stream of messages by returning
             // our big serialized message 'count' times.
-            using (RepeatingMemoryStream stream = new RepeatingMemoryStream(serializedMessage, count))
-            {
-                Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(stream),
-                    "Protocol message was too large.  May be malicious.  " +
-                    "Use CodedInputStream.SetSizeLimit() to increase the size limit.");
-            }
+            using var stream = new RepeatingMemoryStream(serializedMessage, count);
+            Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(stream),
+                "Protocol message was too large.  May be malicious.  " +
+                "Use CodedInputStream.SetSizeLimit() to increase the size limit.");
         }
 
         /// <returns>A serialized big message</returns>
diff --git a/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs
index 13f83a2..31090c4 100644
--- a/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs
+++ b/csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs
@@ -94,14 +94,12 @@
                 if ((value >> 32) == 0)
                 {
                     MemoryStream rawOutput = new MemoryStream();
-                    CodedOutputStream output =
-                        new CodedOutputStream(rawOutput, bufferSize);
+                    CodedOutputStream output = new CodedOutputStream(rawOutput, bufferSize);
                     output.WriteRawVarint32((uint) value);
                     output.Flush();
                     Assert.AreEqual(data, rawOutput.ToArray());
 
-                    var bufferWriter = new TestArrayBufferWriter<byte>();
-                    bufferWriter.MaxGrowBy = bufferSize;
+                    var bufferWriter = new TestArrayBufferWriter<byte> { MaxGrowBy = bufferSize };
                     WriteContext.Initialize(bufferWriter, out WriteContext ctx);
                     ctx.WriteUInt32((uint) value);
                     ctx.Flush();
@@ -115,8 +113,7 @@
                     output.Flush();
                     Assert.AreEqual(data, rawOutput.ToArray());
 
-                    var bufferWriter = new TestArrayBufferWriter<byte>();
-                    bufferWriter.MaxGrowBy = bufferSize;
+                    var bufferWriter = new TestArrayBufferWriter<byte> { MaxGrowBy = bufferSize };
                     WriteContext.Initialize(bufferWriter, out WriteContext ctx);
                     ctx.WriteUInt64(value);
                     ctx.Flush();
@@ -190,8 +187,7 @@
                 output.Flush();
                 Assert.AreEqual(data, rawOutput.ToArray());
 
-                var bufferWriter = new TestArrayBufferWriter<byte>();
-                bufferWriter.MaxGrowBy = bufferSize;
+                var bufferWriter = new TestArrayBufferWriter<byte> { MaxGrowBy = bufferSize };
                 WriteContext.Initialize(bufferWriter, out WriteContext ctx);
                 ctx.WriteFixed32(value);
                 ctx.Flush();
@@ -228,8 +224,7 @@
                 output.Flush();
                 Assert.AreEqual(data, rawOutput.ToArray());
 
-                var bufferWriter = new TestArrayBufferWriter<byte>();
-                bufferWriter.MaxGrowBy = blockSize;
+                var bufferWriter = new TestArrayBufferWriter<byte> { MaxGrowBy = blockSize };
                 WriteContext.Initialize(bufferWriter, out WriteContext ctx);
                 ctx.WriteFixed64(value);
                 ctx.Flush();
@@ -270,8 +265,7 @@
                 output.Flush();
                 Assert.AreEqual(rawBytes, rawOutput.ToArray());
 
-                var bufferWriter = new TestArrayBufferWriter<byte>();
-                bufferWriter.MaxGrowBy = blockSize;
+                var bufferWriter = new TestArrayBufferWriter<byte> { MaxGrowBy = blockSize };
                 message.WriteTo(bufferWriter);
                 Assert.AreEqual(rawBytes, bufferWriter.WrittenSpan.ToArray()); 
             }
@@ -383,7 +377,9 @@
         {
             byte[] content = new byte[110];
             for (int i = 0; i < content.Length; i++)
+            {
                 content[i] = (byte)i;
+            }
 
             byte[] child = new byte[120];
             {
diff --git a/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs b/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
index abd19a2..401b70a 100644
--- a/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
@@ -91,10 +91,12 @@
         [Test]
         public void AddPreservesInsertionOrder()
         {
-            var map = new MapField<string, string>();
-            map.Add("a", "v1");
-            map.Add("b", "v2");
-            map.Add("c", "v3");
+            var map = new MapField<string, string>
+            {
+                { "a", "v1" },
+                { "b", "v2" },
+                { "c", "v3" }
+            };
             map.Remove("b");
             map.Add("d", "v4");
             CollectionAssert.AreEqual(new[] { "a", "c", "d" }, map.Keys);
@@ -104,13 +106,17 @@
         [Test]
         public void EqualityIsOrderInsensitive()
         {
-            var map1 = new MapField<string, string>();
-            map1.Add("a", "v1");
-            map1.Add("b", "v2");
+            var map1 = new MapField<string, string>
+            {
+                { "a", "v1" },
+                { "b", "v2" }
+            };
 
-            var map2 = new MapField<string, string>();
-            map2.Add("b", "v2");
-            map2.Add("a", "v1");
+            var map2 = new MapField<string, string>
+            {
+                { "b", "v2" },
+                { "a", "v1" }
+            };
 
             EqualityTester.AssertEquality(map1, map2);
         }
@@ -118,13 +124,17 @@
         [Test]
         public void EqualityIsKeySensitive()
         {
-            var map1 = new MapField<string, string>();
-            map1.Add("first key", "v1");
-            map1.Add("second key", "v2");
+            var map1 = new MapField<string, string>
+            {
+                { "first key", "v1" },
+                { "second key", "v2" }
+            };
 
-            var map2 = new MapField<string, string>();
-            map2.Add("third key", "v1");
-            map2.Add("fourth key", "v2");
+            var map2 = new MapField<string, string>
+            {
+                { "third key", "v1" },
+                { "fourth key", "v2" }
+            };
 
             EqualityTester.AssertInequality(map1, map2);
         }
@@ -143,13 +153,17 @@
         {
             // Note: Without some care, it's a little easier than one might
             // hope to see hash collisions, but only in some environments...
-            var map1 = new MapField<string, string>();
-            map1.Add("a", "first value");
-            map1.Add("b", "second value");
+            var map1 = new MapField<string, string>
+            {
+                { "a", "first value" },
+                { "b", "second value" }
+            };
 
-            var map2 = new MapField<string, string>();
-            map2.Add("a", "third value");
-            map2.Add("b", "fourth value");
+            var map2 = new MapField<string, string>
+            {
+                { "a", "third value" },
+                { "b", "fourth value" }
+            };
 
             EqualityTester.AssertInequality(map1, map2);
         }
@@ -183,8 +197,7 @@
         [Test]
         public void Add_KeyAlreadyExists()
         {
-            var map = new MapField<string, string>();
-            map.Add("foo", "bar");
+            var map = new MapField<string, string> { { "foo", "bar" } };
             Assert.Throws<ArgumentException>(() => map.Add("foo", "baz"));
         }
 
@@ -211,8 +224,7 @@
         [Test]
         public void Remove_Key()
         {
-            var map = new MapField<string, string>();
-            map.Add("foo", "bar");
+            var map = new MapField<string, string> { { "foo", "bar" } };
             Assert.AreEqual(1, map.Count);
             Assert.IsFalse(map.Remove("missing"));
             Assert.AreEqual(1, map.Count);
@@ -224,8 +236,7 @@
         [Test]
         public void Remove_Pair()
         {
-            var map = new MapField<string, string>();
-            map.Add("foo", "bar");
+            var map = new MapField<string, string> { { "foo", "bar" } };
             ICollection<KeyValuePair<string, string>> collection = map;
             Assert.AreEqual(1, map.Count);
             Assert.IsFalse(collection.Remove(NewKeyValuePair("wrong key", "bar")));
@@ -240,8 +251,7 @@
         [Test]
         public void CopyTo_Pair()
         {
-            var map = new MapField<string, string>();
-            map.Add("foo", "bar");
+            var map = new MapField<string, string> { { "foo", "bar" } };
             ICollection<KeyValuePair<string, string>> collection = map;
             KeyValuePair<string, string>[] array = new KeyValuePair<string, string>[3];
             collection.CopyTo(array, 1);
@@ -270,8 +280,7 @@
         [Test]
         public void Indexer_Set()
         {
-            var map = new MapField<string, string>();
-            map["x"] = "y";
+            var map = new MapField<string, string> { ["x"] = "y" };
             Assert.AreEqual("y", map["x"]);
             map["x"] = "z"; // This won't throw, unlike Add.
             Assert.AreEqual("z", map["x"]);
@@ -357,12 +366,10 @@
             IDictionary dictionary = map;
             var array = new DictionaryEntry[3];
             dictionary.CopyTo(array, 1);
-            CollectionAssert.AreEqual(new[] { default(DictionaryEntry), new DictionaryEntry("x", "y"), default(DictionaryEntry) },
-                array);
+            CollectionAssert.AreEqual(new[] { default, new DictionaryEntry("x", "y"), default }, array);
             var objectArray = new object[3];
             dictionary.CopyTo(objectArray, 1);
-            CollectionAssert.AreEqual(new object[] { null, new DictionaryEntry("x", "y"), null },
-                objectArray);
+            CollectionAssert.AreEqual(new object[] { null, new DictionaryEntry("x", "y"), null }, objectArray);
         }
 
         [Test]
@@ -580,8 +587,7 @@
             };
             Assert.AreEqual("x", map[SampleNaNs.Regular]);
             Assert.AreEqual("y", map[SampleNaNs.SignallingFlipped]);
-            string ignored;
-            Assert.False(map.TryGetValue(SampleNaNs.PayloadFlipped, out ignored));
+            Assert.False(map.TryGetValue(SampleNaNs.PayloadFlipped, out _));
         }
 
         [Test]
diff --git a/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs b/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
index ff8f5cc..766b4cb 100644
--- a/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
@@ -33,7 +33,6 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 using System.IO;
 using System.Linq;
 using System.Text;
@@ -59,8 +58,7 @@
         [Test]
         public void Add_SingleItem()
         {
-            var list = new RepeatedField<string>();
-            list.Add("foo");
+            var list = new RepeatedField<string> { "foo" };
             Assert.AreEqual(1, list.Count);
             Assert.AreEqual("foo", list[0]);
         }
@@ -68,8 +66,7 @@
         [Test]
         public void Add_Sequence()
         {
-            var list = new RepeatedField<string>();
-            list.Add(new[] { "foo", "bar" });
+            var list = new RepeatedField<string> { new[] { "foo", "bar" } };
             Assert.AreEqual(2, list.Count);
             Assert.AreEqual("foo", list[0]);
             Assert.AreEqual("bar", list[1]);
@@ -293,15 +290,13 @@
         public void Enumerator()
         {
             var list = new RepeatedField<string> { "first", "second" };
-            using (var enumerator = list.GetEnumerator())
-            {
-                Assert.IsTrue(enumerator.MoveNext());
-                Assert.AreEqual("first", enumerator.Current);
-                Assert.IsTrue(enumerator.MoveNext());
-                Assert.AreEqual("second", enumerator.Current);
-                Assert.IsFalse(enumerator.MoveNext());
-                Assert.IsFalse(enumerator.MoveNext());
-            }
+            using var enumerator = list.GetEnumerator();
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual("first", enumerator.Current);
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual("second", enumerator.Current);
+            Assert.IsFalse(enumerator.MoveNext());
+            Assert.IsFalse(enumerator.MoveNext());
         }
 
         [Test]
diff --git a/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs b/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs
index 1d69fcb..040a2f1 100644
--- a/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Compatibility/TypeExtensionsTest.cs
@@ -29,6 +29,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
+
 using NUnit.Framework;
 using System;
 using System.Collections.Generic;
diff --git a/csharp/src/Google.Protobuf.Test/DeprecatedMemberTest.cs b/csharp/src/Google.Protobuf.Test/DeprecatedMemberTest.cs
index fd041e0..f291b1e 100644
--- a/csharp/src/Google.Protobuf.Test/DeprecatedMemberTest.cs
+++ b/csharp/src/Google.Protobuf.Test/DeprecatedMemberTest.cs
@@ -50,6 +50,5 @@
         {
             AssertIsDeprecated(typeof(TestDeprecatedFields).GetProperty("DeprecatedInt32"));
         }
-
     }
 }
diff --git a/csharp/src/Google.Protobuf.Test/ExtensionSetTest.cs b/csharp/src/Google.Protobuf.Test/ExtensionSetTest.cs
index b2c24dd..23237d4 100644
--- a/csharp/src/Google.Protobuf.Test/ExtensionSetTest.cs
+++ b/csharp/src/Google.Protobuf.Test/ExtensionSetTest.cs
@@ -1,3 +1,35 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2015 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
 using System;
 using System.Collections;
 using Google.Protobuf.TestProtos.Proto2;
@@ -72,7 +104,7 @@
             message.SetExtension(OptionalStringExtension, "abcd");
 
             var input = new CodedInputStream(message.ToByteArray());
-            input.ExtensionRegistry = new ExtensionRegistry() { OptionalStringExtension };
+            input.ExtensionRegistry = new ExtensionRegistry { OptionalStringExtension };
             input.ReadTag(); // TryMergeFieldFrom expects that a tag was just read and will inspect the LastTag value
 
             ExtensionSet<TestAllExtensions> extensionSet = null;
diff --git a/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs b/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs
index c7fbd13..4c206e6 100644
--- a/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs
+++ b/csharp/src/Google.Protobuf.Test/FieldMaskTreeTest.cs
@@ -30,7 +30,6 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using System.Collections.Generic;
 using Google.Protobuf.Collections;
 using Google.Protobuf.TestProtos;
 using NUnit.Framework;
diff --git a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.Proto2.cs b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.Proto2.cs
index fa5f927..9d03656 100644
--- a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.Proto2.cs
+++ b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.Proto2.cs
@@ -1,3 +1,35 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2015 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
 using Google.Protobuf.TestProtos.Proto2;
 using Proto2 = Google.Protobuf.TestProtos.Proto2;
 using NUnit.Framework;
@@ -387,11 +419,9 @@
             var message = new TestAllExtensions();
             message.SetExtension(UnittestExtensions.OptionalBoolExtension, true);
             byte[] bytes = message.ToByteArray();
-            using (CodedInputStream input = new CodedInputStream(bytes))
-            {
-                var parsed = TestAllExtensions.Parser.WithExtensionRegistry(new ExtensionRegistry() { UnittestExtensions.OptionalBoolExtension }).ParseFrom(input);
-                Assert.AreEqual(message, parsed);
-            }
+            using CodedInputStream input = new CodedInputStream(bytes);
+            var parsed = TestAllExtensions.Parser.WithExtensionRegistry(new ExtensionRegistry() { UnittestExtensions.OptionalBoolExtension }).ParseFrom(input);
+            Assert.AreEqual(message, parsed);
         }
     }
 }
diff --git a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
index 41a0b91..8387291 100644
--- a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
+++ b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
@@ -33,10 +33,7 @@
 using System;
 using System.IO;
 using Google.Protobuf.TestProtos;
-using Proto2 = Google.Protobuf.TestProtos.Proto2;
 using NUnit.Framework;
-using System.Collections;
-using System.Collections.Generic;
 using System.Linq;
 using Google.Protobuf.WellKnownTypes;
 
@@ -658,9 +655,11 @@
         [Test]
         public void OneofSerialization_NonDefaultValue()
         {
-            var message = new TestAllTypes();
-            message.OneofString = "this would take a bit of space";
-            message.OneofUint32 = 10;
+            var message = new TestAllTypes
+            {
+                OneofString = "this would take a bit of space",
+                OneofUint32 = 10
+            };
             var bytes = message.ToByteArray();
             Assert.AreEqual(3, bytes.Length); // 2 bytes for the tag + 1 for the value - no string!
 
@@ -675,9 +674,11 @@
         [Test]
         public void OneofSerialization_DefaultValue()
         {
-            var message = new TestAllTypes();
-            message.OneofString = "this would take a bit of space";
-            message.OneofUint32 = 0; // This is the default value for UInt32; normally wouldn't be serialized
+            var message = new TestAllTypes
+            {
+                OneofString = "this would take a bit of space",
+                OneofUint32 = 0 // This is the default value for UInt32; normally wouldn't be serialized
+            };
             var bytes = message.ToByteArray();
             Assert.AreEqual(3, bytes.Length); // 2 bytes for the tag + 1 for the value - it's still serialized
 
@@ -746,7 +747,6 @@
         [Test]
         public void ExtraEndGroupThrows()
         {
-            var message = SampleMessages.CreateFullTestAllTypes();
             var stream = new MemoryStream();
             var output = new CodedOutputStream(stream);
 
diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
index 3a77990..714c78c 100644
--- a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
+++ b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
@@ -168,8 +168,7 @@
         [Test]
         public void WithFormatDefaultValues_DoesNotAffectProto3OptionalFields()
         {
-            var message = new TestProto3Optional();
-            message.OptionalInt32 = 0;
+            var message = new TestProto3Optional { OptionalInt32 = 0 };
             var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true));
             var json = formatter.Format(message);
             // The non-optional proto3 fields are formatted, as is the optional-but-specified field.
@@ -179,8 +178,7 @@
         [Test]
         public void WithFormatDefaultValues_DoesNotAffectProto2Fields()
         {
-            var message = new TestProtos.Proto2.ForeignMessage();
-            message.C = 0;
+            var message = new TestProtos.Proto2.ForeignMessage { C = 0 };
             var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true));
             var json = formatter.Format(message);
             // The specified field is formatted, but the non-specified field (d) is not.
diff --git a/csharp/src/Google.Protobuf.Test/JsonParserTest.cs b/csharp/src/Google.Protobuf.Test/JsonParserTest.cs
index eb8996e..b5d6c0b 100644
--- a/csharp/src/Google.Protobuf.Test/JsonParserTest.cs
+++ b/csharp/src/Google.Protobuf.Test/JsonParserTest.cs
@@ -641,7 +641,7 @@
         [TestCase("9999-12-31T23:59:59.999999999Z", null)]
         public void Timestamp_Valid(string jsonValue, string expectedFormatted)
         {
-            expectedFormatted = expectedFormatted ?? jsonValue;
+            expectedFormatted ??= jsonValue;
             string json = WrapInQuotes(jsonValue);
             var parsed = Timestamp.Parser.ParseJson(json);
             Assert.AreEqual(WrapInQuotes(expectedFormatted), parsed.ToString());
@@ -758,7 +758,7 @@
         [TestCase("-315576000000s", null)]
         public void Duration_Valid(string jsonValue, string expectedFormatted)
         {
-            expectedFormatted = expectedFormatted ?? jsonValue;
+            expectedFormatted ??= jsonValue;
             string json = WrapInQuotes(jsonValue);
             var parsed = Duration.Parser.ParseJson(json);
             Assert.AreEqual(WrapInQuotes(expectedFormatted), parsed.ToString());
@@ -1066,25 +1066,26 @@
   ""mapStringNestedMessage"": null
 }";
 
-            TestAllTypesProto3 message = new TestAllTypesProto3();
-
-            message.OptionalInt32 = 1;
-            message.OptionalInt64 = 1;
-            message.OptionalUint32 = 1;
-            message.OptionalUint64 = 1;
-            message.OptionalSint32 = 1;
-            message.OptionalSint64 = 1;
-            message.OptionalFixed32 = 1;
-            message.OptionalFixed64 = 1;
-            message.OptionalSfixed32 = 1;
-            message.OptionalSfixed64 = 1;
-            message.OptionalFloat = 1;
-            message.OptionalDouble = 1;
-            message.OptionalBool = true;
-            message.OptionalString = "1";
-            message.OptionalBytes = ByteString.CopyFrom(new byte[] { 1 });
-            message.OptionalNestedEnum = TestAllTypesProto3.Types.NestedEnum.Bar;
-            message.OptionalNestedMessage = new TestAllTypesProto3.Types.NestedMessage();
+            var message = new TestAllTypesProto3
+            {
+                OptionalInt32 = 1,
+                OptionalInt64 = 1,
+                OptionalUint32 = 1,
+                OptionalUint64 = 1,
+                OptionalSint32 = 1,
+                OptionalSint64 = 1,
+                OptionalFixed32 = 1,
+                OptionalFixed64 = 1,
+                OptionalSfixed32 = 1,
+                OptionalSfixed64 = 1,
+                OptionalFloat = 1,
+                OptionalDouble = 1,
+                OptionalBool = true,
+                OptionalString = "1",
+                OptionalBytes = ByteString.CopyFrom(new byte[] { 1 }),
+                OptionalNestedEnum = TestAllTypesProto3.Types.NestedEnum.Bar,
+                OptionalNestedMessage = new TestAllTypesProto3.Types.NestedMessage()
+            };
             message.RepeatedInt32.Add(1);
             message.RepeatedInt64.Add(1);
             message.RepeatedUint32.Add(1);
diff --git a/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs b/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs
index df43eff..06521dd 100644
--- a/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs
+++ b/csharp/src/Google.Protobuf.Test/JsonTokenizerTest.cs
@@ -29,6 +29,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
+
 using NUnit.Framework;
 using System;
 using System.IO;
@@ -126,7 +127,7 @@
             tokenizer.PushBack(token);
             Assert.AreEqual(0, tokenizer.ObjectDepth);
             // Read the same token again, and get back to depth 1
-            token = tokenizer.Next();
+            _ = tokenizer.Next();
             Assert.AreEqual(1, tokenizer.ObjectDepth);
 
             // Now the same in reverse, with EndObject
diff --git a/csharp/src/Google.Protobuf.Test/LegacyGeneratedCodeTest.cs b/csharp/src/Google.Protobuf.Test/LegacyGeneratedCodeTest.cs
index 22adcaa..01fca3e 100644
--- a/csharp/src/Google.Protobuf.Test/LegacyGeneratedCodeTest.cs
+++ b/csharp/src/Google.Protobuf.Test/LegacyGeneratedCodeTest.cs
@@ -29,14 +29,12 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
-using Google.Protobuf;
-using Google.Protobuf.Reflection;
+
 using System.Buffers;
-using pb = global::Google.Protobuf;
-using pbr = global::Google.Protobuf.Reflection;
+using pb = Google.Protobuf;
+using pbr = Google.Protobuf.Reflection;
 using NUnit.Framework;
 using System.IO;
-using System;
 using Google.Protobuf.Buffers;
 
 namespace Google.Protobuf
diff --git a/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs b/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs
index 05f1e36..f2eb762 100644
--- a/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs
+++ b/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs
@@ -136,8 +136,7 @@
             // test for different IBufferWriter.GetSpan() segment sizes
             for (int blockSize = 1; blockSize < 256; blockSize *= 2)
             {
-                var segmentedBufferWriter = new TestArrayBufferWriter<byte>();
-                segmentedBufferWriter.MaxGrowBy = blockSize;
+                var segmentedBufferWriter = new TestArrayBufferWriter<byte> { MaxGrowBy = blockSize };
                 message.WriteTo(segmentedBufferWriter);
                 Assert.AreEqual(bytes, segmentedBufferWriter.WrittenSpan.ToArray());
             }
diff --git a/csharp/src/Google.Protobuf.Test/Proto3OptionalTest.cs b/csharp/src/Google.Protobuf.Test/Proto3OptionalTest.cs
index 46a8c57..b5b0b72 100644
--- a/csharp/src/Google.Protobuf.Test/Proto3OptionalTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Proto3OptionalTest.cs
@@ -38,7 +38,7 @@
 
 namespace Google.Protobuf.Test
 {
-    class Proto3OptionalTest
+    public class Proto3OptionalTest
     {
         [Test]
         public void OptionalInt32FieldLifecycle()
diff --git a/csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs b/csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs
index f0248ac..d4dfd18 100644
--- a/csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs
+++ b/csharp/src/Google.Protobuf.Test/ReadOnlySequenceFactory.cs
@@ -33,9 +33,6 @@
 using System;
 using System.Buffers;
 using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Google.Protobuf
 {
@@ -50,7 +47,7 @@
 
             if (addEmptySegmentDelimiters)
             {
-                segments.Add(new byte[0]);
+                segments.Add(Array.Empty<byte>());
             }
 
             var currentIndex = 0;
@@ -65,7 +62,7 @@
 
                 if (addEmptySegmentDelimiters)
                 {
-                    segments.Add(new byte[0]);
+                    segments.Add(Array.Empty<byte>());
                 }
             }
 
diff --git a/csharp/src/Google.Protobuf.Test/RefStructCompatibilityTest.cs b/csharp/src/Google.Protobuf.Test/RefStructCompatibilityTest.cs
index 9dca501..f3651df 100644
--- a/csharp/src/Google.Protobuf.Test/RefStructCompatibilityTest.cs
+++ b/csharp/src/Google.Protobuf.Test/RefStructCompatibilityTest.cs
@@ -77,43 +77,42 @@
         /// <param name="args"></param>
         /// <param name="workingDirectory"></param>
         private void RunOldCsharpCompilerAndCheckSuccess(string args, string workingDirectory)
-        {  
-            using (var process = new Process())
+        {
+            using var process = new Process();
+
+            // Get the path to the old C# 5 compiler from .NET framework. This approach is not 100% reliable, but works on most machines.
+            // Alternative way of getting the framework path is System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()
+            // but it only works with the net45 target.
+            var oldCsharpCompilerPath = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "Microsoft.NET", "Framework", "v4.0.30319", "csc.exe");
+            process.StartInfo.FileName = oldCsharpCompilerPath;
+            process.StartInfo.RedirectStandardOutput = true;
+            process.StartInfo.RedirectStandardError = true;
+            process.StartInfo.UseShellExecute = false;
+            process.StartInfo.Arguments = args;
+            process.StartInfo.WorkingDirectory = workingDirectory;
+
+            process.OutputDataReceived += (sender, e) =>
             {
-                // Get the path to the old C# 5 compiler from .NET framework. This approach is not 100% reliable, but works on most machines.
-                // Alternative way of getting the framework path is System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()
-                // but it only works with the net45 target.
-                var oldCsharpCompilerPath = Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "Microsoft.NET", "Framework", "v4.0.30319", "csc.exe");
-                process.StartInfo.FileName = oldCsharpCompilerPath;
-                process.StartInfo.RedirectStandardOutput = true;
-                process.StartInfo.RedirectStandardError = true;
-                process.StartInfo.UseShellExecute = false;
-                process.StartInfo.Arguments = args;
-                process.StartInfo.WorkingDirectory = workingDirectory;
-
-                process.OutputDataReceived += (sender, e) =>
+                if (e.Data != null)
                 {
-                    if (e.Data != null)
-                    {
-                        Console.WriteLine(e.Data);
-                    }
-                };
-                process.ErrorDataReceived += (sender, e) =>
+                    Console.WriteLine(e.Data);
+                }
+            };
+            process.ErrorDataReceived += (sender, e) =>
+            {
+                if (e.Data != null)
                 {
-                    if (e.Data != null)
-                    {
-                        Console.WriteLine(e.Data);
-                    }
-                };
+                    Console.WriteLine(e.Data);
+                }
+            };
 
-                process.Start();
+            process.Start();
 
-                process.BeginErrorReadLine();
-                process.BeginOutputReadLine();
+            process.BeginErrorReadLine();
+            process.BeginOutputReadLine();
 
-                process.WaitForExit();
-                Assert.AreEqual(0, process.ExitCode);
-            }
+            process.WaitForExit();
+            Assert.AreEqual(0, process.ExitCode);
         }
     }
 }
\ No newline at end of file
diff --git a/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
index 68b9bd3..589c35c 100644
--- a/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
@@ -31,13 +31,10 @@
 #endregion
 
 using Google.Protobuf.Reflection;
-using Google.Protobuf.WellKnownTypes;
 using NUnit.Framework;
 using System;
-using System.IO;
 using System.Linq;
 using UnitTest.Issues.TestProtos;
-using static Google.Protobuf.WireFormat;
 using static UnitTest.Issues.TestProtos.ComplexOptionType2.Types;
 using static UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Extensions;
 using static UnitTest.Issues.TestProtos.DummyMessageContainingEnum.Types;
@@ -65,7 +62,7 @@
                 }
                 else
                 {
-                    v = default(E);
+                    v = default;
                     return false;
                 }
             };
diff --git a/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs
index 65c8b82..03722d4 100644
--- a/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Reflection/DescriptorsTest.cs
@@ -35,7 +35,6 @@
 using ProtobufUnittest;
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Linq;
 using UnitTest.Issues.TestProtos;
 
diff --git a/csharp/src/Google.Protobuf.Test/UnknownFieldSetTest.cs b/csharp/src/Google.Protobuf.Test/UnknownFieldSetTest.cs
index f3e5af2..eeb9f89 100644
--- a/csharp/src/Google.Protobuf.Test/UnknownFieldSetTest.cs
+++ b/csharp/src/Google.Protobuf.Test/UnknownFieldSetTest.cs
@@ -30,10 +30,8 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using System;
 using System.IO;
 using Google.Protobuf.TestProtos;
-using Proto2 = Google.Protobuf.TestProtos.Proto2;
 using NUnit.Framework;
 
 namespace Google.Protobuf
@@ -127,8 +125,7 @@
         public void TestClone(IMessage message)
         {
             var emptyMessage = new TestEmptyMessage();
-            var otherEmptyMessage = new TestEmptyMessage();
-            otherEmptyMessage = emptyMessage.Clone();
+            TestEmptyMessage otherEmptyMessage = emptyMessage.Clone();
             Assert.AreEqual(emptyMessage.CalculateSize(), otherEmptyMessage.CalculateSize());
             Assert.AreEqual(emptyMessage.ToByteArray(), otherEmptyMessage.ToByteArray());
 
@@ -169,13 +166,13 @@
             byte[] data = message.ToByteArray();
             int fullSize = message.CalculateSize();
 
-            Action<IMessage> assertEmpty = msg =>
+            void AssertEmpty(IMessage msg)
             {
                 Assert.AreEqual(0, msg.CalculateSize());
                 Assert.AreEqual(goldenEmptyMessage, msg);
-            };
+            }
 
-            Action<IMessage> assertFull = msg => Assert.AreEqual(fullSize, msg.CalculateSize());
+            void AssertFull(IMessage msg) => Assert.AreEqual(fullSize, msg.CalculateSize());
 
             // Test the behavior of the parsers with and without discarding, both generic and non-generic.
             MessageParser<TestEmptyMessage> retainingParser1 = TestEmptyMessage.Parser;
@@ -184,28 +181,28 @@
             MessageParser discardingParser2 = retainingParser2.WithDiscardUnknownFields(true);
 
             // Test parse from byte[]
-            MessageParsingHelpers.AssertReadingMessage(retainingParser1, data, m => assertFull(m));
-            MessageParsingHelpers.AssertReadingMessage(retainingParser2, data, m => assertFull(m));
-            MessageParsingHelpers.AssertReadingMessage(discardingParser1, data, m => assertEmpty(m));
-            MessageParsingHelpers.AssertReadingMessage(discardingParser2, data, m => assertEmpty(m));
+            MessageParsingHelpers.AssertReadingMessage(retainingParser1, data, m => AssertFull(m));
+            MessageParsingHelpers.AssertReadingMessage(retainingParser2, data, m => AssertFull(m));
+            MessageParsingHelpers.AssertReadingMessage(discardingParser1, data, m => AssertEmpty(m));
+            MessageParsingHelpers.AssertReadingMessage(discardingParser2, data, m => AssertEmpty(m));
 
             // Test parse from byte[] with offset
-            assertFull(retainingParser1.ParseFrom(data, 0, data.Length));
-            assertFull(retainingParser2.ParseFrom(data, 0, data.Length));
-            assertEmpty(discardingParser1.ParseFrom(data, 0, data.Length));
-            assertEmpty(discardingParser2.ParseFrom(data, 0, data.Length));
+            AssertFull(retainingParser1.ParseFrom(data, 0, data.Length));
+            AssertFull(retainingParser2.ParseFrom(data, 0, data.Length));
+            AssertEmpty(discardingParser1.ParseFrom(data, 0, data.Length));
+            AssertEmpty(discardingParser2.ParseFrom(data, 0, data.Length));
 
             // Test parse from CodedInputStream
-            assertFull(retainingParser1.ParseFrom(new CodedInputStream(data)));
-            assertFull(retainingParser2.ParseFrom(new CodedInputStream(data)));
-            assertEmpty(discardingParser1.ParseFrom(new CodedInputStream(data)));
-            assertEmpty(discardingParser2.ParseFrom(new CodedInputStream(data)));
+            AssertFull(retainingParser1.ParseFrom(new CodedInputStream(data)));
+            AssertFull(retainingParser2.ParseFrom(new CodedInputStream(data)));
+            AssertEmpty(discardingParser1.ParseFrom(new CodedInputStream(data)));
+            AssertEmpty(discardingParser2.ParseFrom(new CodedInputStream(data)));
 
             // Test parse from Stream
-            assertFull(retainingParser1.ParseFrom(new MemoryStream(data)));
-            assertFull(retainingParser2.ParseFrom(new MemoryStream(data)));
-            assertEmpty(discardingParser1.ParseFrom(new MemoryStream(data)));
-            assertEmpty(discardingParser2.ParseFrom(new MemoryStream(data)));
+            AssertFull(retainingParser1.ParseFrom(new MemoryStream(data)));
+            AssertFull(retainingParser2.ParseFrom(new MemoryStream(data)));
+            AssertEmpty(discardingParser1.ParseFrom(new MemoryStream(data)));
+            AssertEmpty(discardingParser2.ParseFrom(new MemoryStream(data)));
         }
 
         [Test]
diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
index 771f1f4..187e06a 100644
--- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
+++ b/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
@@ -126,7 +126,6 @@
         [TestCase("foobar", "")]
         public void GetTypeName(string typeUrl, string expectedTypeName)
         {
-            var any = new Any { TypeUrl = typeUrl };
             Assert.AreEqual(expectedTypeName, Any.GetTypeName(typeUrl));
         }
 
diff --git a/csharp/src/Google.Protobuf/ByteString.cs b/csharp/src/Google.Protobuf/ByteString.cs
index c615616..b13b611 100644
--- a/csharp/src/Google.Protobuf/ByteString.cs
+++ b/csharp/src/Google.Protobuf/ByteString.cs
@@ -189,7 +189,7 @@
         /// <param name="stream">The stream to copy into a ByteString.</param>
         /// <param name="cancellationToken">The cancellation token to use when reading from the stream, if any.</param>
         /// <returns>A ByteString with content read from the given stream.</returns>
-        public static Task<ByteString> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
+        public static Task<ByteString> FromStreamAsync(Stream stream, CancellationToken cancellationToken = default)
         {
             ProtoPreconditions.CheckNotNull(stream, nameof(stream));
             return ByteStringAsync.FromStreamAsyncCore(stream, cancellationToken);
@@ -340,7 +340,7 @@
             {
                 return true;
             }
-            if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
+            if (lhs is null || rhs is null)
             {
                 return false;
             }
diff --git a/csharp/src/Google.Protobuf/CodedInputStream.cs b/csharp/src/Google.Protobuf/CodedInputStream.cs
index 96ae84c..aea546a 100644
--- a/csharp/src/Google.Protobuf/CodedInputStream.cs
+++ b/csharp/src/Google.Protobuf/CodedInputStream.cs
@@ -32,10 +32,7 @@
 
 using Google.Protobuf.Collections;
 using System;
-using System.Collections.Generic;
 using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 using System.Security;
 
 namespace Google.Protobuf
@@ -650,21 +647,6 @@
         }
 
         /// <summary>
-        /// Called when buffer is empty to read more bytes from the
-        /// input.  If <paramref name="mustSucceed"/> is true, RefillBuffer() guarantees that
-        /// either there will be at least one byte in the buffer when it returns
-        /// or it will throw an exception.  If <paramref name="mustSucceed"/> is false,
-        /// RefillBuffer() returns false if no more bytes were available.
-        /// </summary>
-        /// <param name="mustSucceed"></param>
-        /// <returns></returns>
-        private bool RefillBuffer(bool mustSucceed)
-        {
-            var span = new ReadOnlySpan<byte>(buffer);
-            return state.segmentedBufferHelper.RefillBuffer(ref span, ref state, mustSucceed);
-        }
-
-        /// <summary>
         /// Reads a fixed size of bytes from the input.
         /// </summary>
         /// <exception cref="InvalidProtocolBufferException">
diff --git a/csharp/src/Google.Protobuf/CodedOutputStream.cs b/csharp/src/Google.Protobuf/CodedOutputStream.cs
index 5b8cca1..2a162c6 100644
--- a/csharp/src/Google.Protobuf/CodedOutputStream.cs
+++ b/csharp/src/Google.Protobuf/CodedOutputStream.cs
@@ -30,11 +30,9 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using Google.Protobuf.Collections;
 using System;
 using System.IO;
 using System.Security;
-using System.Text;
 
 namespace Google.Protobuf
 {
diff --git a/csharp/src/Google.Protobuf/Collections/Lists.cs b/csharp/src/Google.Protobuf/Collections/Lists.cs
index 860795c..9007709 100644
--- a/csharp/src/Google.Protobuf/Collections/Lists.cs
+++ b/csharp/src/Google.Protobuf/Collections/Lists.cs
@@ -31,7 +31,6 @@
 #endregion
 
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 
 namespace Google.Protobuf.Collections
 {
diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs
index 0f02d59..f0124ee 100644
--- a/csharp/src/Google.Protobuf/Collections/MapField.cs
+++ b/csharp/src/Google.Protobuf/Collections/MapField.cs
@@ -31,9 +31,7 @@
 #endregion
 
 using Google.Protobuf.Compatibility;
-using Google.Protobuf.Reflection;
 using System;
-using System.Buffers;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO;
@@ -74,9 +72,8 @@
         private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>();
 
         // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
-        private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
-            new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer);
-        private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
+        private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map = new(KeyEqualityComparer);
+        private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new();
 
         /// <summary>
         /// Creates a deep clone of this object.
@@ -144,8 +141,7 @@
         public bool Remove(TKey key)
         {
             ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
-            LinkedListNode<KeyValuePair<TKey, TValue>> node;
-            if (map.TryGetValue(key, out node))
+            if (map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> node))
             {
                 map.Remove(key);
                 node.List.Remove(node);
@@ -167,15 +163,14 @@
         /// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns>
         public bool TryGetValue(TKey key, out TValue value)
         {
-            LinkedListNode<KeyValuePair<TKey, TValue>> node;
-            if (map.TryGetValue(key, out node))
+            if (map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> node))
             {
                 value = node.Value.Value;
                 return true;
             }
             else
             {
-                value = default(TValue);
+                value = default;
                 return false;
             }
         }
@@ -192,8 +187,7 @@
             get
             {
                 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
-                TValue value;
-                if (TryGetValue(key, out value))
+                if (TryGetValue(key, out TValue value))
                 {
                     return value;
                 }
@@ -207,9 +201,8 @@
                 {
                     ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
                 }
-                LinkedListNode<KeyValuePair<TKey, TValue>> node;
                 var pair = new KeyValuePair<TKey, TValue>(key, value);
-                if (map.TryGetValue(key, out node))
+                if (map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> node))
                 {
                     node.Value = pair;
                 }
@@ -224,12 +217,12 @@
         /// <summary>
         /// Gets a collection containing the keys in the map.
         /// </summary>
-        public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } }
+        public ICollection<TKey> Keys => new MapView<TKey>(this, pair => pair.Key, ContainsKey);
 
         /// <summary>
         /// Gets a collection containing the values in the map.
         /// </summary>
-        public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
+        public ICollection<TValue> Values => new MapView<TValue>(this, pair => pair.Value, ContainsValue);
 
         /// <summary>
         /// Adds the specified entries to the map. The keys and values are not automatically cloned.
@@ -250,10 +243,7 @@
         /// <returns>
         /// An enumerator that can be used to iterate through the collection.
         /// </returns>
-        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
-        {
-            return list.GetEnumerator();
-        }
+        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => list.GetEnumerator();
 
         /// <summary>
         /// Returns an enumerator that iterates through a collection.
@@ -261,19 +251,13 @@
         /// <returns>
         /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
         /// </returns>
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return GetEnumerator();
-        }
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
         /// <summary>
         /// Adds the specified item to the map.
         /// </summary>
         /// <param name="item">The item to add to the map.</param>
-        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
-        {
-            Add(item.Key, item.Value);
-        }
+        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
 
         /// <summary>
         /// Removes all items from the map.
@@ -289,21 +273,16 @@
         /// </summary>
         /// <param name="item">The key/value pair to find.</param>
         /// <returns></returns>
-        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
-        {
-            TValue value;
-            return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
-        }
+        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) =>
+            TryGetValue(item.Key, out TValue value) && ValueEqualityComparer.Equals(item.Value, value);
 
         /// <summary>
         /// Copies the key/value pairs in this map to an array.
         /// </summary>
         /// <param name="array">The array to copy the entries into.</param>
         /// <param name="arrayIndex">The index of the array at which to start copying values.</param>
-        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
-        {
+        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) =>
             list.CopyTo(array, arrayIndex);
-        }
 
         /// <summary>
         /// Removes the specified key/value pair from the map.
@@ -317,8 +296,7 @@
             {
                 throw new ArgumentException("Key is null", nameof(item));
             }
-            LinkedListNode<KeyValuePair<TKey, TValue>> node;
-            if (map.TryGetValue(item.Key, out node) &&
+            if (map.TryGetValue(item.Key, out LinkedListNode<KeyValuePair<TKey, TValue>> node) &&
                 EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value))
             {
                 map.Remove(item.Key);
@@ -334,12 +312,12 @@
         /// <summary>
         /// Gets the number of elements contained in the map.
         /// </summary>
-        public int Count { get { return list.Count; } }
+        public int Count => list.Count;
 
         /// <summary>
         /// Gets a value indicating whether the map is read-only.
         /// </summary>
-        public bool IsReadOnly { get { return false; } }
+        public bool IsReadOnly => false;
 
         /// <summary>
         /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
@@ -348,10 +326,7 @@
         /// <returns>
         ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
         /// </returns>
-        public override bool Equals(object other)
-        {
-            return Equals(other as MapField<TKey, TValue>);
-        }
+        public override bool Equals(object other) => Equals(other as MapField<TKey, TValue>);
 
         /// <summary>
         /// Returns a hash code for this instance.
@@ -396,8 +371,7 @@
             var valueComparer = ValueEqualityComparer;
             foreach (var pair in this)
             {
-                TValue value;
-                if (!other.TryGetValue(pair.Key, out value))
+                if (!other.TryGetValue(pair.Key, out TValue value))
                 {
                     return false;
                 }
@@ -529,33 +503,20 @@
         }
 
         #region IDictionary explicit interface implementation
-        void IDictionary.Add(object key, object value)
-        {
-            Add((TKey)key, (TValue)value);
-        }
 
-        bool IDictionary.Contains(object key)
-        {
-            if (!(key is TKey))
-            {
-                return false;
-            }
-            return ContainsKey((TKey)key);
-        }
+        void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value);
 
-        IDictionaryEnumerator IDictionary.GetEnumerator()
-        {
-            return new DictionaryEnumerator(GetEnumerator());
-        }
+        bool IDictionary.Contains(object key) => key is TKey k && ContainsKey(k);
+
+        IDictionaryEnumerator IDictionary.GetEnumerator() => new DictionaryEnumerator(GetEnumerator());
 
         void IDictionary.Remove(object key)
         {
             ProtoPreconditions.CheckNotNull(key, nameof(key));
-            if (!(key is TKey))
+            if (key is TKey k)
             {
-                return;
+                Remove(k);
             }
-            Remove((TKey)key);
         }
 
         void ICollection.CopyTo(Array array, int index)
@@ -565,28 +526,27 @@
             temp.CopyTo(array, index);
         }
 
-        bool IDictionary.IsFixedSize { get { return false; } }
+        bool IDictionary.IsFixedSize => false;
 
-        ICollection IDictionary.Keys { get { return (ICollection)Keys; } }
+        ICollection IDictionary.Keys => (ICollection)Keys;
 
-        ICollection IDictionary.Values { get { return (ICollection)Values; } }
+        ICollection IDictionary.Values => (ICollection)Values;
 
-        bool ICollection.IsSynchronized { get { return false; } }
+        bool ICollection.IsSynchronized => false;
 
-        object ICollection.SyncRoot { get { return this; } }
+        object ICollection.SyncRoot => this;
 
         object IDictionary.this[object key]
         {
             get
             {
                 ProtoPreconditions.CheckNotNull(key, nameof(key));
-                if (!(key is TKey))
+                if (key is TKey k)
                 {
-                    return null;
+                    TryGetValue(k, out TValue value);
+                    return value;
                 }
-                TValue value;
-                TryGetValue((TKey)key, out value);
-                return value;
+                return null;
             }
 
             set
@@ -610,20 +570,14 @@
                 this.enumerator = enumerator;
             }
 
-            public bool MoveNext()
-            {
-                return enumerator.MoveNext();
-            }
+            public bool MoveNext() => enumerator.MoveNext();
 
-            public void Reset()
-            {
-                enumerator.Reset();
-            }
+            public void Reset() => enumerator.Reset();
 
-            public object Current { get { return Entry; } }
-            public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } }
-            public object Key { get { return enumerator.Current.Key; } }
-            public object Value { get { return enumerator.Current.Value; } }
+            public object Current => Entry;
+            public DictionaryEntry Entry => new DictionaryEntry(Key, Value);
+            public object Key => enumerator.Current.Key;
+            public object Value => enumerator.Current.Value;
         }
 
         /// <summary>
@@ -682,28 +636,19 @@
                 this.containsCheck = containsCheck;
             }
 
-            public int Count { get { return parent.Count; } }
+            public int Count => parent.Count; 
 
-            public bool IsReadOnly { get { return true; } }
+            public bool IsReadOnly => true;
 
-            public bool IsSynchronized { get { return false; } }
+            public bool IsSynchronized => false;
 
-            public object SyncRoot { get { return parent; } }
+            public object SyncRoot => parent;
 
-            public void Add(T item)
-            {
-                throw new NotSupportedException();
-            }
+            public void Add(T item) => throw new NotSupportedException();
 
-            public void Clear()
-            {
-                throw new NotSupportedException();
-            }
+            public void Clear() => throw new NotSupportedException();
 
-            public bool Contains(T item)
-            {
-                return containsCheck(item);
-            }
+            public bool Contains(T item) => containsCheck(item);
 
             public void CopyTo(T[] array, int arrayIndex)
             {
@@ -726,15 +671,9 @@
                 return parent.list.Select(projection).GetEnumerator();
             }
 
-            public bool Remove(T item)
-            {
-                throw new NotSupportedException();
-            }
+            public bool Remove(T item) => throw new NotSupportedException();
 
-            IEnumerator IEnumerable.GetEnumerator()
-            {
-                return GetEnumerator();
-            }
+            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
             public void CopyTo(Array array, int index)
             {
diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
index cd6f5eb..832e166 100644
--- a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
+++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
@@ -35,7 +35,6 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Security;
-using System.Threading;
 
 namespace Google.Protobuf.Collections
 {
@@ -74,8 +73,7 @@
             if (array != EmptyArray)
             {
                 clone.array = (T[])array.Clone();
-                IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[];
-                if (cloneableArray != null)
+                if (clone.array is IDeepCloneable<T>[] cloneableArray)
                 {
                     for (int i = 0; i < count; i++)
                     {
@@ -349,10 +347,7 @@
         /// </summary>
         /// <param name="item">The item to find.</param>
         /// <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
-        public bool Contains(T item)
-        {
-            return IndexOf(item) != -1;
-        }
+        public bool Contains(T item) => IndexOf(item) != -1;
 
         /// <summary>
         /// Copies this collection to the given array.
@@ -378,7 +373,7 @@
             }            
             Array.Copy(array, index + 1, array, index, count - index - 1);
             count--;
-            array[count] = default(T);
+            array[count] = default;
             return true;
         }
 
@@ -402,8 +397,7 @@
 
             // Optimization 1: If the collection we're adding is already a RepeatedField<T>,
             // we know the values are valid.
-            var otherRepeatedField = values as RepeatedField<T>;
-            if (otherRepeatedField != null)
+            if (values is RepeatedField<T> otherRepeatedField)
             {
                 EnsureSize(count + otherRepeatedField.count);
                 Array.Copy(otherRepeatedField.array, 0, array, count, otherRepeatedField.count);
@@ -413,8 +407,7 @@
 
             // Optimization 2: The collection is an ICollection, so we can expand
             // just once and ask the collection to copy itself into the array.
-            var collection = values as ICollection;
-            if (collection != null)
+            if (values is ICollection collection)
             {
                 var extraCount = collection.Count;
                 // For reference types and nullable value types, we need to check that there are no nulls
@@ -484,21 +477,15 @@
         /// <returns>
         ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
         /// </returns>
-        public override bool Equals(object obj)
-        {
-            return Equals(obj as RepeatedField<T>);
-        }
-
+        public override bool Equals(object obj) => Equals(obj as RepeatedField<T>);
+        
         /// <summary>
         /// Returns an enumerator that iterates through a collection.
         /// </summary>
         /// <returns>
         /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
         /// </returns>
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return GetEnumerator();
-        }
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
         /// <summary>
         /// Returns a hash code for this instance.
@@ -523,7 +510,7 @@
         /// <returns><c>true</c> if <paramref name="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
         public bool Equals(RepeatedField<T> other)
         {
-            if (ReferenceEquals(other, null))
+            if (other is null)
             {
                 return false;
             }
@@ -596,7 +583,7 @@
             }
             Array.Copy(array, index + 1, array, index, count - index - 1);
             count--;
-            array[count] = default(T);
+            array[count] = default;
         }
 
         /// <summary>
@@ -642,10 +629,7 @@
         #region Explicit interface implementation for IList and ICollection.
         bool IList.IsFixedSize => false;
 
-        void ICollection.CopyTo(Array array, int index)
-        {
-            Array.Copy(this.array, 0, array, index, count);
-        }
+        void ICollection.CopyTo(Array array, int index) => Array.Copy(this.array, 0, array, index, count);
 
         bool ICollection.IsSynchronized => false;
 
@@ -653,8 +637,8 @@
 
         object IList.this[int index]
         {
-            get { return this[index]; }
-            set { this[index] = (T)value; }
+            get => this[index];
+            set => this[index] = (T)value;
         }
 
         int IList.Add(object value)
@@ -663,32 +647,18 @@
             return count - 1;
         }
 
-        bool IList.Contains(object value)
-        {
-            return (value is T && Contains((T)value));
-        }
+        bool IList.Contains(object value) => (value is T t && Contains(t));
 
-        int IList.IndexOf(object value)
-        {
-            if (!(value is T))
-            {
-                return -1;
-            }
-            return IndexOf((T)value);
-        }
+        int IList.IndexOf(object value) => (value is T t) ? IndexOf(t) : -1;
 
-        void IList.Insert(int index, object value)
-        {
-            Insert(index, (T) value);
-        }
+        void IList.Insert(int index, object value) => Insert(index, (T) value);
 
         void IList.Remove(object value)
         {
-            if (!(value is T))
+            if (value is T t)
             {
-                return;
+                Remove(t);
             }
-            Remove((T)value);
         }
         #endregion        
     }
diff --git a/csharp/src/Google.Protobuf/Extension.cs b/csharp/src/Google.Protobuf/Extension.cs
index d10a668..2807260 100644
--- a/csharp/src/Google.Protobuf/Extension.cs
+++ b/csharp/src/Google.Protobuf/Extension.cs
@@ -77,7 +77,7 @@
             this.codec = codec;
         }
 
-        internal TValue DefaultValue => codec != null ? codec.DefaultValue : default(TValue);
+        internal TValue DefaultValue => codec != null ? codec.DefaultValue : default;
 
         internal override Type TargetType => typeof(TTarget);
 
diff --git a/csharp/src/Google.Protobuf/ExtensionRegistry.cs b/csharp/src/Google.Protobuf/ExtensionRegistry.cs
index e72314b..be381f0 100644
--- a/csharp/src/Google.Protobuf/ExtensionRegistry.cs
+++ b/csharp/src/Google.Protobuf/ExtensionRegistry.cs
@@ -55,7 +55,7 @@
 
             internal static ExtensionComparer Instance = new ExtensionComparer();
         }
-        private IDictionary<ObjectIntPair<Type>, Extension> extensions;
+        private readonly IDictionary<ObjectIntPair<Type>, Extension> extensions;
 
         /// <summary>
         /// Creates a new empty extension registry
diff --git a/csharp/src/Google.Protobuf/ExtensionSet.cs b/csharp/src/Google.Protobuf/ExtensionSet.cs
index 306e45e..aa1acbd 100644
--- a/csharp/src/Google.Protobuf/ExtensionSet.cs
+++ b/csharp/src/Google.Protobuf/ExtensionSet.cs
@@ -61,8 +61,7 @@
         /// </summary>
         public static TValue Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
         {
-            IExtensionValue value;
-            if (TryGetValue(ref set, extension, out value))
+            if (TryGetValue(ref set, extension, out IExtensionValue value))
             {
                 // The stored ExtensionValue can be a different type to what is being requested.
                 // This happens when the same extension proto is compiled in different assemblies.
@@ -98,7 +97,7 @@
                     }
                 }
             }
-            else 
+            else
             {
                 return extension.DefaultValue;
             }
@@ -109,8 +108,7 @@
         /// </summary>
         public static RepeatedField<TValue> Get<TTarget, TValue>(ref ExtensionSet<TTarget> set, RepeatedExtension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
         {
-            IExtensionValue value;
-            if (TryGetValue(ref set, extension, out value))
+            if (TryGetValue(ref set, extension, out IExtensionValue value))
             {
                 if (value is RepeatedExtensionValue<TValue> extensionValue)
                 {
@@ -132,7 +130,7 @@
                     }
                 }
             }
-            else 
+            else
             {
                 return null;
             }
@@ -193,8 +191,7 @@
         /// </summary>
         public static bool Has<TTarget, TValue>(ref ExtensionSet<TTarget> set, Extension<TTarget, TValue> extension) where TTarget : IExtendableMessage<TTarget>
         {
-            IExtensionValue value;
-            return TryGetValue(ref set, extension, out value);
+            return TryGetValue(ref set, extension, out IExtensionValue _);
         }
 
         /// <summary>
@@ -252,20 +249,18 @@
         /// </summary>
         public static bool TryMergeFieldFrom<TTarget>(ref ExtensionSet<TTarget> set, ref ParseContext ctx) where TTarget : IExtendableMessage<TTarget>
         {
-            Extension extension;
             int lastFieldNumber = WireFormat.GetTagFieldNumber(ctx.LastTag);
 
-            IExtensionValue extensionValue;
-            if (set != null && set.ValuesByNumber.TryGetValue(lastFieldNumber, out extensionValue))
+            if (set != null && set.ValuesByNumber.TryGetValue(lastFieldNumber, out IExtensionValue extensionValue))
             {
                 extensionValue.MergeFrom(ref ctx);
                 return true;
             }
-            else if (ctx.ExtensionRegistry != null && ctx.ExtensionRegistry.ContainsInputField(ctx.LastTag, typeof(TTarget), out extension))
+            else if (ctx.ExtensionRegistry != null && ctx.ExtensionRegistry.ContainsInputField(ctx.LastTag, typeof(TTarget), out Extension extension))
             {
                 IExtensionValue value = extension.CreateValue();
                 value.MergeFrom(ref ctx);
-                set = (set ?? new ExtensionSet<TTarget>());
+                set ??= new ExtensionSet<TTarget>();
                 set.ValuesByNumber.Add(extension.FieldNumber, value);
                 return true;
             }
@@ -290,8 +285,7 @@
             }
             foreach (var pair in second.ValuesByNumber)
             {
-                IExtensionValue value;
-                if (first.ValuesByNumber.TryGetValue(pair.Key, out value))
+                if (first.ValuesByNumber.TryGetValue(pair.Key, out IExtensionValue value))
                 {
                     value.MergeFrom(pair.Value);
                 }
@@ -365,8 +359,7 @@
             }
             foreach (var pair in ValuesByNumber)
             {
-                IExtensionValue secondValue;
-                if (!otherSet.ValuesByNumber.TryGetValue(pair.Key, out secondValue))
+                if (!otherSet.ValuesByNumber.TryGetValue(pair.Key, out IExtensionValue secondValue))
                 {
                     return false;
                 }
diff --git a/csharp/src/Google.Protobuf/ExtensionValue.cs b/csharp/src/Google.Protobuf/ExtensionValue.cs
index 1329b2f..9637f1b 100644
--- a/csharp/src/Google.Protobuf/ExtensionValue.cs
+++ b/csharp/src/Google.Protobuf/ExtensionValue.cs
@@ -32,7 +32,6 @@
 
 using Google.Protobuf.Collections;
 using System;
-using System.Linq;
 
 namespace Google.Protobuf
 {
@@ -50,7 +49,7 @@
     internal sealed class ExtensionValue<T> : IExtensionValue
     {
         private T field;
-        private FieldCodec<T> codec;
+        private readonly FieldCodec<T> codec;
 
         internal ExtensionValue(FieldCodec<T> codec)
         {
diff --git a/csharp/src/Google.Protobuf/FieldCodec.cs b/csharp/src/Google.Protobuf/FieldCodec.cs
index ee6bd6a..d3b0b71 100644
--- a/csharp/src/Google.Protobuf/FieldCodec.cs
+++ b/csharp/src/Google.Protobuf/FieldCodec.cs
@@ -31,7 +31,6 @@
 #endregion
 
 using Google.Protobuf.Collections;
-using Google.Protobuf.Compatibility;
 using Google.Protobuf.WellKnownTypes;
 using System;
 using System.Collections.Generic;
@@ -51,150 +50,105 @@
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<string> ForString(uint tag)
-        {
-            return FieldCodec.ForString(tag, "");
-        }
+        public static FieldCodec<string> ForString(uint tag) => ForString(tag, "");
 
         /// <summary>
         /// Retrieves a codec suitable for a bytes field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<ByteString> ForBytes(uint tag)
-        {
-            return FieldCodec.ForBytes(tag, ByteString.Empty);
-        }
+        public static FieldCodec<ByteString> ForBytes(uint tag) => ForBytes(tag, ByteString.Empty);
 
         /// <summary>
         /// Retrieves a codec suitable for a bool field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<bool> ForBool(uint tag)
-        {
-            return FieldCodec.ForBool(tag, false);
-        }
+        public static FieldCodec<bool> ForBool(uint tag) => ForBool(tag, false);
 
         /// <summary>
         /// Retrieves a codec suitable for an int32 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<int> ForInt32(uint tag)
-        {
-            return FieldCodec.ForInt32(tag, 0);
-        }
+        public static FieldCodec<int> ForInt32(uint tag) => ForInt32(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for an sint32 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<int> ForSInt32(uint tag)
-        {
-            return FieldCodec.ForSInt32(tag, 0);
-        }
+        public static FieldCodec<int> ForSInt32(uint tag) => ForSInt32(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for a fixed32 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<uint> ForFixed32(uint tag)
-        {
-            return FieldCodec.ForFixed32(tag, 0);
-        }
+        public static FieldCodec<uint> ForFixed32(uint tag) => ForFixed32(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for an sfixed32 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<int> ForSFixed32(uint tag)
-        {
-            return FieldCodec.ForSFixed32(tag, 0);
-        }
+        public static FieldCodec<int> ForSFixed32(uint tag) => ForSFixed32(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for a uint32 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<uint> ForUInt32(uint tag)
-        {
-            return FieldCodec.ForUInt32(tag, 0);
-        }
+        public static FieldCodec<uint> ForUInt32(uint tag) => ForUInt32(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for an int64 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<long> ForInt64(uint tag)
-        {
-            return FieldCodec.ForInt64(tag, 0);
-        }
+        public static FieldCodec<long> ForInt64(uint tag) => ForInt64(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for an sint64 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<long> ForSInt64(uint tag)
-        {
-            return FieldCodec.ForSInt64(tag, 0);
-        }
+        public static FieldCodec<long> ForSInt64(uint tag) => ForSInt64(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for a fixed64 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<ulong> ForFixed64(uint tag)
-        {
-            return FieldCodec.ForFixed64(tag, 0);
-        }
+        public static FieldCodec<ulong> ForFixed64(uint tag) => ForFixed64(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for an sfixed64 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<long> ForSFixed64(uint tag)
-        {
-            return FieldCodec.ForSFixed64(tag, 0);
-        }
+        public static FieldCodec<long> ForSFixed64(uint tag) => ForSFixed64(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for a uint64 field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<ulong> ForUInt64(uint tag)
-        {
-            return FieldCodec.ForUInt64(tag, 0);
-        }
+        public static FieldCodec<ulong> ForUInt64(uint tag) => ForUInt64(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for a float field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<float> ForFloat(uint tag)
-        {
-            return FieldCodec.ForFloat(tag, 0);
-        }
+        public static FieldCodec<float> ForFloat(uint tag) => ForFloat(tag, 0);
 
         /// <summary>
         /// Retrieves a codec suitable for a double field with the given tag.
         /// </summary>
         /// <param name="tag">The tag.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<double> ForDouble(uint tag)
-        {
-            return FieldCodec.ForDouble(tag, 0);
-        }
+        public static FieldCodec<double> ForDouble(uint tag) => ForDouble(tag, 0);
 
         // Enums are tricky. We can probably use expression trees to build these delegates automatically,
         // but it's easy to generate the code for it.
@@ -206,10 +160,8 @@
         /// <param name="toInt32">A conversion function from <see cref="Int32"/> to the enum type.</param>
         /// <param name="fromInt32">A conversion function from the enum type to <see cref="Int32"/>.</param>
         /// <returns>A codec for the given tag.</returns>
-        public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
-        {
-            return FieldCodec.ForEnum(tag, toInt32, fromInt32, default(T));
-        }
+        public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32) =>
+            ForEnum(tag, toInt32, fromInt32, default);
 
         /// <summary>
         /// Retrieves a codec suitable for a string field with the given tag.
@@ -565,8 +517,7 @@
             /// </summary>
             internal static FieldCodec<T> GetCodec<T>()
             {
-                object value;
-                if (!Codecs.TryGetValue(typeof(T), out value))
+                if (!Codecs.TryGetValue(typeof(T), out object value))
                 {
                     throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T));
                 }
@@ -575,8 +526,7 @@
 
             internal static ValueReader<T?> GetReader<T>() where T : struct
             {
-                object value;
-                if (!Readers.TryGetValue(typeof(T), out value))
+                if (!Readers.TryGetValue(typeof(T), out object value))
                 {
                     throw new InvalidOperationException("Invalid type argument requested for wrapper reader: " + typeof(T));
                 }
diff --git a/csharp/src/Google.Protobuf/FieldMaskTree.cs b/csharp/src/Google.Protobuf/FieldMaskTree.cs
index 63eb5f6..084374f 100644
--- a/csharp/src/Google.Protobuf/FieldMaskTree.cs
+++ b/csharp/src/Google.Protobuf/FieldMaskTree.cs
@@ -120,8 +120,7 @@
                     return this;
                 }
 
-                Node childNode;
-                if (!node.Children.TryGetValue(part, out childNode))
+                if (!node.Children.TryGetValue(part, out Node childNode))
                 {
                     createNewBranch = true;
                     childNode = new Node();
diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
index d3c18db..03de483 100644
--- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj
+++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
@@ -5,8 +5,7 @@
     <Copyright>Copyright 2015, Google Inc.</Copyright>
     <AssemblyTitle>Google Protocol Buffers</AssemblyTitle>
     <VersionPrefix>3.21.1</VersionPrefix>
-    <!-- C# 7.2 is required for Span/BufferWriter/ReadOnlySequence -->
-    <LangVersion>7.2</LangVersion>
+    <LangVersion>10.0</LangVersion>
     <Authors>Google Inc.</Authors>
     <TargetFrameworks>netstandard1.1;netstandard2.0;net45;net50</TargetFrameworks>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
diff --git a/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs b/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs
index 6a3dbd6..333838b 100644
--- a/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs
+++ b/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs
@@ -136,5 +136,5 @@
         {
             return new InvalidProtocolBufferException("Message was missing required fields");
         }
-}
+    }
 }
\ No newline at end of file
diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs
index 16f7c5a..2ef10ee 100644
--- a/csharp/src/Google.Protobuf/JsonFormatter.cs
+++ b/csharp/src/Google.Protobuf/JsonFormatter.cs
@@ -62,7 +62,6 @@
         internal const string AnyTypeUrlField = "@type";
         internal const string AnyDiagnosticValueField = "@value";
         internal const string AnyWellKnownTypeValueField = "value";
-        private const string TypeUrlPrefix = "type.googleapis.com";
         private const string NameValueSeparator = ": ";
         private const string PropertySeparator = ", ";
 
@@ -202,8 +201,7 @@
             }
             if (DiagnosticOnly)
             {
-                ICustomDiagnosticMessage customDiagnosticMessage = message as ICustomDiagnosticMessage;
-                if (customDiagnosticMessage != null)
+                if (message is ICustomDiagnosticMessage customDiagnosticMessage)
                 {
                     writer.Write(customDiagnosticMessage.ToDiagnosticString());
                     return;
@@ -320,39 +318,20 @@
                 IList list = (IList) value;
                 return list.Count == 0;
             }
-            switch (descriptor.FieldType)
+            return descriptor.FieldType switch
             {
-                case FieldType.Bool:
-                    return (bool) value == false;
-                case FieldType.Bytes:
-                    return (ByteString) value == ByteString.Empty;
-                case FieldType.String:
-                    return (string) value == "";
-                case FieldType.Double:
-                    return (double) value == 0.0;
-                case FieldType.SInt32:
-                case FieldType.Int32:
-                case FieldType.SFixed32:
-                case FieldType.Enum:
-                    return (int) value == 0;
-                case FieldType.Fixed32:
-                case FieldType.UInt32:
-                    return (uint) value == 0;
-                case FieldType.Fixed64:
-                case FieldType.UInt64:
-                    return (ulong) value == 0;
-                case FieldType.SFixed64:
-                case FieldType.Int64:
-                case FieldType.SInt64:
-                    return (long) value == 0;
-                case FieldType.Float:
-                    return (float) value == 0f;
-                case FieldType.Message:
-                case FieldType.Group: // Never expect to get this, but...
-                    return value == null;
-                default:
-                    throw new ArgumentException("Invalid field type");
-            }
+                FieldType.Bool => (bool) value == false,
+                FieldType.Bytes => (ByteString) value == ByteString.Empty,
+                FieldType.String => (string) value == "",
+                FieldType.Double => (double) value == 0.0,
+                FieldType.SInt32 or FieldType.Int32 or FieldType.SFixed32 or FieldType.Enum => (int) value == 0,
+                FieldType.Fixed32 or FieldType.UInt32 => (uint) value == 0,
+                FieldType.Fixed64 or FieldType.UInt64 => (ulong) value == 0,
+                FieldType.SFixed64 or FieldType.Int64 or FieldType.SInt64 => (long) value == 0,
+                FieldType.Float => (float) value == 0f,
+                FieldType.Message or FieldType.Group => value == null,
+                _ => throw new ArgumentException("Invalid field type"),
+            };
         }
 
         /// <summary>
@@ -369,28 +348,28 @@
             {
                 WriteNull(writer);
             }
-            else if (value is bool)
+            else if (value is bool b)
             {
-                writer.Write((bool)value ? "true" : "false");
+                writer.Write(b ? "true" : "false");
             }
-            else if (value is ByteString)
+            else if (value is ByteString byteString)
             {
                 // Nothing in Base64 needs escaping
                 writer.Write('"');
-                writer.Write(((ByteString)value).ToBase64());
+                writer.Write(byteString.ToBase64());
                 writer.Write('"');
             }
-            else if (value is string)
+            else if (value is string str)
             {
-                WriteString(writer, (string)value);
+                WriteString(writer, str);
             }
-            else if (value is IDictionary)
+            else if (value is IDictionary dictionary)
             {
-                WriteDictionary(writer, (IDictionary)value);
+                WriteDictionary(writer, dictionary);
             }
-            else if (value is IList)
+            else if (value is IList list)
             {
-                WriteList(writer, (IList)value);
+                WriteList(writer, list);
             }
             else if (value is int || value is uint)
             {
@@ -437,9 +416,9 @@
                     writer.Write(text);
                 }
             }
-            else if (value is IMessage)
+            else if (value is IMessage message)
             {
-                Format((IMessage)value, writer);
+                Format(message, writer);
             }
             else
             {
@@ -469,9 +448,8 @@
             // WriteValue will do the right thing.)
             if (descriptor.IsWrapperType)
             {
-                if (value is IMessage)
+                if (value is IMessage message)
                 {
-                    var message = (IMessage) value;
                     value = message.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.GetValue(message);
                 }
                 WriteValue(writer, value);
@@ -679,15 +657,15 @@
                     writer.Write(PropertySeparator);
                 }
                 string keyText;
-                if (pair.Key is string)
+                if (pair.Key is string s)
                 {
-                    keyText = (string) pair.Key;
+                    keyText = s;
                 }
-                else if (pair.Key is bool)
+                else if (pair.Key is bool b)
                 {
-                    keyText = (bool) pair.Key ? "true" : "false";
+                    keyText = b ? "true" : "false";
                 }
-                else if (pair.Key is int || pair.Key is uint | pair.Key is long || pair.Key is ulong)
+                else if (pair.Key is int || pair.Key is uint || pair.Key is long || pair.Key is ulong)
                 {
                     keyText = ((IFormattable) pair.Key).ToString("d", CultureInfo.InvariantCulture);
                 }
@@ -916,9 +894,8 @@
                     }
                 }
 
-                string originalName;
                 // If this returns false, originalName will be null, which is what we want.
-                nameMapping.TryGetValue(value, out originalName);
+                nameMapping.TryGetValue(value, out string originalName);
                 return originalName;
             }
 
diff --git a/csharp/src/Google.Protobuf/JsonParser.cs b/csharp/src/Google.Protobuf/JsonParser.cs
index cb5f5a8..3575e25 100644
--- a/csharp/src/Google.Protobuf/JsonParser.cs
+++ b/csharp/src/Google.Protobuf/JsonParser.cs
@@ -70,8 +70,7 @@
 
         // TODO: Consider introducing a class containing parse state of the parser, tokenizer and depth. That would simplify these handlers
         // and the signatures of various methods.
-        private static readonly Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
-            WellKnownTypeHandlers = new Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>>
+        private static readonly Dictionary<string, Action<JsonParser, IMessage, JsonTokenizer>> WellKnownTypeHandlers = new()
         {
             { Timestamp.Descriptor.FullName, (parser, message, tokenizer) => MergeTimestamp(message, tokenizer.Next()) },
             { Duration.Descriptor.FullName, (parser, message, tokenizer) => MergeDuration(message, tokenizer.Next()) },
@@ -156,8 +155,7 @@
             }
             if (message.Descriptor.IsWellKnownType)
             {
-                Action<JsonParser, IMessage, JsonTokenizer> handler;
-                if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullName, out handler))
+                if (WellKnownTypeHandlers.TryGetValue(message.Descriptor.FullName, out Action<JsonParser, IMessage, JsonTokenizer> handler))
                 {
                     handler(this, message, tokenizer);
                     return;
@@ -187,8 +185,7 @@
                     throw new InvalidOperationException("Unexpected token type " + token.Type);
                 }
                 string name = token.StringValue;
-                FieldDescriptor field;
-                if (jsonFieldMap.TryGetValue(name, out field))
+                if (jsonFieldMap.TryGetValue(name, out FieldDescriptor field))
                 {
                     if (field.ContainingOneof != null)
                     {
@@ -303,11 +300,7 @@
                 }
                 object key = ParseMapKey(keyField, token.StringValue);
                 object value = ParseSingleValue(valueField, tokenizer);
-                if (value == null)
-                {
-                    throw new InvalidProtocolBufferException("Map values must not be null");
-                }
-                dictionary[key] = value;
+                dictionary[key] = value ?? throw new InvalidProtocolBufferException("Map values must not be null");
             }
         }
 
@@ -853,7 +846,7 @@
                 if (secondsToAdd < 0 && nanosToAdd > 0)
                 {
                     secondsToAdd++;
-                    nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond;
+                    nanosToAdd -= Duration.NanosecondsPerSecond;
                 }
                 if (secondsToAdd != 0 || nanosToAdd != 0)
                 {
@@ -1049,23 +1042,20 @@
             /// when unknown fields are encountered.
             /// </summary>
             /// <param name="ignoreUnknownFields"><c>true</c> if unknown fields should be ignored when parsing; <c>false</c> to throw an exception.</param>
-            public Settings WithIgnoreUnknownFields(bool ignoreUnknownFields) =>
-                new Settings(RecursionLimit, TypeRegistry, ignoreUnknownFields);
+            public Settings WithIgnoreUnknownFields(bool ignoreUnknownFields) => new(RecursionLimit, TypeRegistry, ignoreUnknownFields);
 
             /// <summary>
             /// Creates a new <see cref="Settings"/> object based on this one, but with the specified recursion limit.
             /// </summary>
             /// <param name="recursionLimit">The new recursion limit.</param>
-            public Settings WithRecursionLimit(int recursionLimit) =>
-                new Settings(recursionLimit, TypeRegistry, IgnoreUnknownFields);
+            public Settings WithRecursionLimit(int recursionLimit) => new(recursionLimit, TypeRegistry, IgnoreUnknownFields);
 
             /// <summary>
             /// Creates a new <see cref="Settings"/> object based on this one, but with the specified type registry.
             /// </summary>
             /// <param name="typeRegistry">The new type registry. Must not be null.</param>
             public Settings WithTypeRegistry(TypeRegistry typeRegistry) =>
-                new Settings(
-                    RecursionLimit,
+                new(RecursionLimit,
                     ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry)),
                     IgnoreUnknownFields);
         }
diff --git a/csharp/src/Google.Protobuf/JsonToken.cs b/csharp/src/Google.Protobuf/JsonToken.cs
index 6c0138c..c23a25c 100644
--- a/csharp/src/Google.Protobuf/JsonToken.cs
+++ b/csharp/src/Google.Protobuf/JsonToken.cs
@@ -36,24 +36,14 @@
 {
     internal sealed class JsonToken : IEquatable<JsonToken>
     {
-        // Tokens with no value can be reused.
-        private static readonly JsonToken _true = new JsonToken(TokenType.True);
-        private static readonly JsonToken _false = new JsonToken(TokenType.False);
-        private static readonly JsonToken _null = new JsonToken(TokenType.Null);
-        private static readonly JsonToken startObject = new JsonToken(TokenType.StartObject);
-        private static readonly JsonToken endObject = new JsonToken(TokenType.EndObject);
-        private static readonly JsonToken startArray = new JsonToken(TokenType.StartArray);
-        private static readonly JsonToken endArray = new JsonToken(TokenType.EndArray);
-        private static readonly JsonToken endDocument = new JsonToken(TokenType.EndDocument);
-
-        internal static JsonToken Null { get { return _null; } }
-        internal static JsonToken False { get { return _false; } }
-        internal static JsonToken True { get { return _true; } }
-        internal static JsonToken StartObject{ get { return startObject; } }
-        internal static JsonToken EndObject { get { return endObject; } }
-        internal static JsonToken StartArray { get { return startArray; } }
-        internal static JsonToken EndArray { get { return endArray; } }
-        internal static JsonToken EndDocument { get { return endDocument; } }
+        internal static JsonToken Null { get; } = new JsonToken(TokenType.Null);
+        internal static JsonToken False { get; } = new JsonToken(TokenType.False);
+        internal static JsonToken True { get; } = new JsonToken(TokenType.True);
+        internal static JsonToken StartObject { get; } = new JsonToken(TokenType.StartObject);
+        internal static JsonToken EndObject { get; } = new JsonToken(TokenType.EndObject);
+        internal static JsonToken StartArray { get; } = new JsonToken(TokenType.StartArray);
+        internal static JsonToken EndArray { get; } = new JsonToken(TokenType.EndArray);
+        internal static JsonToken EndDocument { get; } = new JsonToken(TokenType.EndDocument);
 
         internal static JsonToken Name(string name)
         {
@@ -94,9 +84,9 @@
         private readonly string stringValue;
         private readonly double numberValue;
 
-        internal TokenType Type { get { return type; } }
-        internal string StringValue { get { return stringValue; } }
-        internal double NumberValue { get { return numberValue; } }
+        internal TokenType Type => type;
+        internal string StringValue => stringValue;
+        internal double NumberValue => numberValue;
 
         private JsonToken(TokenType type, string stringValue = null, double numberValue = 0)
         {
@@ -105,10 +95,7 @@
             this.numberValue = numberValue;
         }
 
-        public override bool Equals(object obj)
-        {
-            return Equals(obj as JsonToken);
-        }
+        public override bool Equals(object obj) => Equals(obj as JsonToken);
 
         public override int GetHashCode()
         {
@@ -124,38 +111,26 @@
 
         public override string ToString()
         {
-            switch (type)
+            return type switch
             {
-                case TokenType.Null:
-                    return "null";
-                case TokenType.True:
-                    return "true";
-                case TokenType.False:
-                    return "false";
-                case TokenType.Name:
-                    return "name (" + stringValue + ")";
-                case TokenType.StringValue:
-                    return "value (" + stringValue + ")";
-                case TokenType.Number:
-                    return "number (" + numberValue + ")";
-                case TokenType.StartObject:
-                    return "start-object";
-                case TokenType.EndObject:
-                    return "end-object";
-                case TokenType.StartArray:
-                    return "start-array";
-                case TokenType.EndArray:
-                    return "end-array";
-                case TokenType.EndDocument:
-                    return "end-document";
-                default:
-                    throw new InvalidOperationException("Token is of unknown type " + type);
-            }
+                TokenType.Null => "null",
+                TokenType.True => "true",
+                TokenType.False => "false",
+                TokenType.Name => $"name ({stringValue})",
+                TokenType.StringValue => $"value ({stringValue})",
+                TokenType.Number => $"number ({numberValue})",
+                TokenType.StartObject => "start-object",
+                TokenType.EndObject => "end-object",
+                TokenType.StartArray => "start-array",
+                TokenType.EndArray => "end-array",
+                TokenType.EndDocument => "end-document",
+                _ => throw new InvalidOperationException($"Token is of unknown type {type}"),
+            };
         }
 
         public bool Equals(JsonToken other)
         {
-            if (ReferenceEquals(other, null))
+            if (other is null)
             {
                 return false;
             }
diff --git a/csharp/src/Google.Protobuf/JsonTokenizer.cs b/csharp/src/Google.Protobuf/JsonTokenizer.cs
index 13a12c0..ed54734 100644
--- a/csharp/src/Google.Protobuf/JsonTokenizer.cs
+++ b/csharp/src/Google.Protobuf/JsonTokenizer.cs
@@ -29,6 +29,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
+
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -362,29 +363,19 @@
             private char ReadEscapedCharacter()
             {
                 char c = reader.ReadOrFail("Unexpected end of text while reading character escape sequence");
-                switch (c)
+                return c switch
                 {
-                    case 'n':
-                        return '\n';
-                    case '\\':
-                        return '\\';
-                    case 'b':
-                        return '\b';
-                    case 'f':
-                        return '\f';
-                    case 'r':
-                        return '\r';
-                    case 't':
-                        return '\t';
-                    case '"':
-                        return '"';
-                    case '/':
-                        return '/';
-                    case 'u':
-                        return ReadUnicodeEscape();
-                    default:
-                        throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int) c));
-                }
+                    'n' => '\n',
+                    '\\' => '\\',
+                    'b' => '\b',
+                    'f' => '\f',
+                    'r' => '\r',
+                    't' => '\t',
+                    '"' => '"',
+                    '/' => '/',
+                    'u' => ReadUnicodeEscape(),
+                    _ => throw reader.CreateException(string.Format(CultureInfo.InvariantCulture, "Invalid character in character escape sequence: U+{0:x4}", (int)c)),
+                };
             }
 
             /// <summary>
@@ -498,8 +489,7 @@
                     throw reader.CreateException("Invalid numeric literal");
                 }
                 builder.Append(first);
-                int digitCount;
-                char? next = ConsumeDigits(builder, out digitCount);
+                char? next = ConsumeDigits(builder, out int digitCount);
                 if (first == '0' && digitCount != 0)
                 {
                     throw reader.CreateException("Invalid numeric literal: leading 0 for non-zero value.");
@@ -510,8 +500,7 @@
             private char? ReadFrac(StringBuilder builder)
             {
                 builder.Append('.'); // Already consumed this
-                int digitCount;
-                char? next = ConsumeDigits(builder, out digitCount);
+                char? next = ConsumeDigits(builder, out int digitCount);
                 if (digitCount == 0)
                 {
                     throw reader.CreateException("Invalid numeric literal: fraction with no trailing digits");
@@ -535,8 +524,7 @@
                 {
                     reader.PushBack(next.Value);
                 }
-                int digitCount;
-                next = ConsumeDigits(builder, out digitCount);
+                next = ConsumeDigits(builder, out int digitCount);
                 if (digitCount == 0)
                 {
                     throw reader.CreateException("Invalid numeric literal: exponent without value");
@@ -591,20 +579,13 @@
             {
                 containerStack.Pop();
                 var parent = containerStack.Peek();
-                switch (parent)
+                state = parent switch
                 {
-                    case ContainerType.Object:
-                        state = State.ObjectAfterProperty;
-                        break;
-                    case ContainerType.Array:
-                        state = State.ArrayAfterValue;
-                        break;
-                    case ContainerType.Document:
-                        state = State.ExpectedEndOfDocument;
-                        break;
-                    default:
-                        throw new InvalidOperationException("Unexpected container type: " + parent);
-                }
+                    ContainerType.Object => State.ObjectAfterProperty,
+                    ContainerType.Array => State.ArrayAfterValue,
+                    ContainerType.Document => State.ExpectedEndOfDocument,
+                    _ => throw new InvalidOperationException("Unexpected container type: " + parent),
+                };
             }
 
             private enum ContainerType
diff --git a/csharp/src/Google.Protobuf/LimitedInputStream.cs b/csharp/src/Google.Protobuf/LimitedInputStream.cs
index 50ead9c..46d5295 100644
--- a/csharp/src/Google.Protobuf/LimitedInputStream.cs
+++ b/csharp/src/Google.Protobuf/LimitedInputStream.cs
@@ -51,34 +51,20 @@
             bytesLeft = size;
         }
 
-        public override bool CanRead
-        {
-            get { return true; }
-        }
-
-        public override bool CanSeek
-        {
-            get { return false; }
-        }
-
-        public override bool CanWrite
-        {
-            get { return false; }
-        }
+        public override bool CanRead => true;
+        public override bool CanSeek => false;
+        public override bool CanWrite => false;
 
         public override void Flush()
         {
         }
 
-        public override long Length
-        {
-            get { throw new NotSupportedException(); }
-        }
+        public override long Length => throw new NotSupportedException();
 
         public override long Position
         {
-            get { throw new NotSupportedException(); }
-            set { throw new NotSupportedException(); }
+            get => throw new NotSupportedException();
+            set => throw new NotSupportedException();
         }
 
         public override int Read(byte[] buffer, int offset, int count)
@@ -92,19 +78,10 @@
             return 0;
         }
 
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            throw new NotSupportedException();
-        }
+        public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
 
-        public override void SetLength(long value)
-        {
-            throw new NotSupportedException();
-        }
+        public override void SetLength(long value) => throw new NotSupportedException();
 
-        public override void Write(byte[] buffer, int offset, int count)
-        {
-            throw new NotSupportedException();
-        }
+        public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
     }
 }
diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs
index c4b3f82..22d3a2f 100644
--- a/csharp/src/Google.Protobuf/MessageExtensions.cs
+++ b/csharp/src/Google.Protobuf/MessageExtensions.cs
@@ -107,7 +107,7 @@
         /// <returns>The message data as a byte array.</returns>
         public static byte[] ToByteArray(this IMessage message)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
             byte[] result = new byte[message.CalculateSize()];
             CodedOutputStream output = new CodedOutputStream(result);
             message.WriteTo(output);
@@ -122,8 +122,8 @@
         /// <param name="output">The stream to write to.</param>
         public static void WriteTo(this IMessage message, Stream output)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
-            ProtoPreconditions.CheckNotNull(output, "output");
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
+            ProtoPreconditions.CheckNotNull(output, nameof(output));
             CodedOutputStream codedOutput = new CodedOutputStream(output);
             message.WriteTo(codedOutput);
             codedOutput.Flush();
@@ -136,8 +136,8 @@
         /// <param name="output">The output stream to write to.</param>
         public static void WriteDelimitedTo(this IMessage message, Stream output)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
-            ProtoPreconditions.CheckNotNull(output, "output");
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
+            ProtoPreconditions.CheckNotNull(output, nameof(output));
             CodedOutputStream codedOutput = new CodedOutputStream(output);
             codedOutput.WriteLength(message.CalculateSize());
             message.WriteTo(codedOutput);
@@ -151,7 +151,7 @@
         /// <returns>The message data as a byte string.</returns>
         public static ByteString ToByteString(this IMessage message)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
             return ByteString.AttachBytes(message.ToByteArray());
         }
 
@@ -251,30 +251,34 @@
         // Implementations allowing unknown fields to be discarded.
         internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields, ExtensionRegistry registry)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
-            ProtoPreconditions.CheckNotNull(data, "data");
-            CodedInputStream input = new CodedInputStream(data);
-            input.DiscardUnknownFields = discardUnknownFields;
-            input.ExtensionRegistry = registry;
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
+            ProtoPreconditions.CheckNotNull(data, nameof(data));
+            CodedInputStream input = new CodedInputStream(data)
+            {
+                DiscardUnknownFields = discardUnknownFields,
+                ExtensionRegistry = registry
+            };
             message.MergeFrom(input);
             input.CheckReadEndOfStreamTag();
         }
 
         internal static void MergeFrom(this IMessage message, byte[] data, int offset, int length, bool discardUnknownFields, ExtensionRegistry registry)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
-            ProtoPreconditions.CheckNotNull(data, "data");
-            CodedInputStream input = new CodedInputStream(data, offset, length);
-            input.DiscardUnknownFields = discardUnknownFields;
-            input.ExtensionRegistry = registry;
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
+            ProtoPreconditions.CheckNotNull(data, nameof(data));
+            CodedInputStream input = new CodedInputStream(data, offset, length)
+            {
+                DiscardUnknownFields = discardUnknownFields,
+                ExtensionRegistry = registry
+            };
             message.MergeFrom(input);
             input.CheckReadEndOfStreamTag();
         }
 
         internal static void MergeFrom(this IMessage message, ByteString data, bool discardUnknownFields, ExtensionRegistry registry)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
-            ProtoPreconditions.CheckNotNull(data, "data");
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
+            ProtoPreconditions.CheckNotNull(data, nameof(data));
             CodedInputStream input = data.CreateCodedInput();
             input.DiscardUnknownFields = discardUnknownFields;
             input.ExtensionRegistry = registry;
@@ -284,11 +288,13 @@
 
         internal static void MergeFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
-            ProtoPreconditions.CheckNotNull(input, "input");
-            CodedInputStream codedInput = new CodedInputStream(input);
-            codedInput.DiscardUnknownFields = discardUnknownFields;
-            codedInput.ExtensionRegistry = registry;
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
+            ProtoPreconditions.CheckNotNull(input, nameof(input));
+            CodedInputStream codedInput = new CodedInputStream(input)
+            {
+                DiscardUnknownFields = discardUnknownFields,
+                ExtensionRegistry = registry
+            };
             message.MergeFrom(codedInput);
             codedInput.CheckReadEndOfStreamTag();
         }
@@ -315,8 +321,8 @@
 
         internal static void MergeDelimitedFrom(this IMessage message, Stream input, bool discardUnknownFields, ExtensionRegistry registry)
         {
-            ProtoPreconditions.CheckNotNull(message, "message");
-            ProtoPreconditions.CheckNotNull(input, "input");
+            ProtoPreconditions.CheckNotNull(message, nameof(message));
+            ProtoPreconditions.CheckNotNull(input, nameof(input));
             int size = (int) CodedInputStream.ReadRawVarint32(input);
             Stream limitedStream = new LimitedInputStream(input, size);
             MergeFrom(message, limitedStream, discardUnknownFields, registry);
diff --git a/csharp/src/Google.Protobuf/MessageParser.cs b/csharp/src/Google.Protobuf/MessageParser.cs
index a10c908..66907d4 100644
--- a/csharp/src/Google.Protobuf/MessageParser.cs
+++ b/csharp/src/Google.Protobuf/MessageParser.cs
@@ -43,9 +43,8 @@
     /// </summary>
     public class MessageParser
     {
-        private Func<IMessage> factory;
-        // TODO: When we use a C# 7.1 compiler, make this private protected.
-        internal bool DiscardUnknownFields { get; }
+        private readonly Func<IMessage> factory;
+        private protected bool DiscardUnknownFields { get; }
 
         internal ExtensionRegistry Extensions { get; }
 
diff --git a/csharp/src/Google.Protobuf/ObjectIntPair.cs b/csharp/src/Google.Protobuf/ObjectIntPair.cs
index b61fc68..3270b49 100644
--- a/csharp/src/Google.Protobuf/ObjectIntPair.cs
+++ b/csharp/src/Google.Protobuf/ObjectIntPair.cs
@@ -23,14 +23,7 @@
                    && number == other.number;
         }
 
-        public override bool Equals(object obj)
-        {
-            if (obj is ObjectIntPair<T>)
-            {
-                return Equals((ObjectIntPair<T>)obj);
-            }
-            return false;
-        }
+        public override bool Equals(object obj) => obj is ObjectIntPair<T> pair && Equals(pair);
 
         public override int GetHashCode()
         {
diff --git a/csharp/src/Google.Protobuf/ParseContext.cs b/csharp/src/Google.Protobuf/ParseContext.cs
index 7b278b5..85ea567 100644
--- a/csharp/src/Google.Protobuf/ParseContext.cs
+++ b/csharp/src/Google.Protobuf/ParseContext.cs
@@ -32,14 +32,8 @@
 
 using System;
 using System.Buffers;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.IO;
 using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 using System.Security;
-using System.Text;
-using Google.Protobuf.Collections;
 
 namespace Google.Protobuf
 {
@@ -53,7 +47,7 @@
     public ref struct ParseContext
     {
         internal const int DefaultRecursionLimit = 100;
-        internal const int DefaultSizeLimit = Int32.MaxValue;
+        internal const int DefaultSizeLimit = int.MaxValue;
 
         internal ReadOnlySpan<byte> buffer;
         internal ParserInternalState state;
@@ -127,14 +121,15 @@
         /// Returns the last tag read, or 0 if no tags have been read or we've read beyond
         /// the end of the input.
         /// </summary>
-        internal uint LastTag { get { return state.lastTag; } }
+        internal uint LastTag => state.lastTag;
 
         /// <summary>
         /// Internal-only property; when set to true, unknown fields will be discarded while parsing.
         /// </summary>
-        internal bool DiscardUnknownFields {
-            get { return state.DiscardUnknownFields; }
-            set { state.DiscardUnknownFields = value; }
+        internal bool DiscardUnknownFields
+        {
+            get => state.DiscardUnknownFields;
+            set => state.DiscardUnknownFields = value;
         }
 
         /// <summary>
@@ -142,8 +137,8 @@
         /// </summary>
         internal ExtensionRegistry ExtensionRegistry
         {
-            get { return state.ExtensionRegistry; }
-            set { state.ExtensionRegistry = value; }
+            get => state.ExtensionRegistry;
+            set => state.ExtensionRegistry = value;
         }
 
         /// <summary>
@@ -156,125 +151,85 @@
         /// </remarks>
         /// <returns>The next field tag, or 0 for end of input. (0 is never a valid tag.)</returns>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public uint ReadTag()
-        {
-            return ParsingPrimitives.ParseTag(ref buffer, ref state);
-        }
+        public uint ReadTag() => ParsingPrimitives.ParseTag(ref buffer, ref state);
 
         /// <summary>
         /// Reads a double field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public double ReadDouble()
-        {
-            return ParsingPrimitives.ParseDouble(ref buffer, ref state);
-        }
+        public double ReadDouble() => ParsingPrimitives.ParseDouble(ref buffer, ref state);
 
         /// <summary>
         /// Reads a float field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public float ReadFloat()
-        {
-            return ParsingPrimitives.ParseFloat(ref buffer, ref state);
-        }
+        public float ReadFloat() => ParsingPrimitives.ParseFloat(ref buffer, ref state);
 
         /// <summary>
         /// Reads a uint64 field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public ulong ReadUInt64()
-        {
-            return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
-        }
+        public ulong ReadUInt64() => ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
 
         /// <summary>
         /// Reads an int64 field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public long ReadInt64()
-        {
-            return (long)ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
-        }
+        public long ReadInt64() => (long)ParsingPrimitives.ParseRawVarint64(ref buffer, ref state);
 
         /// <summary>
         /// Reads an int32 field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public int ReadInt32()
-        {
-            return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
-        }
+        public int ReadInt32() => (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
 
         /// <summary>
         /// Reads a fixed64 field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public ulong ReadFixed64()
-        {
-            return ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
-        }
+        public ulong ReadFixed64() => ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
 
         /// <summary>
         /// Reads a fixed32 field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public uint ReadFixed32()
-        {
-            return ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
-        }
+        public uint ReadFixed32() => ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
 
         /// <summary>
         /// Reads a bool field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public bool ReadBool()
-        {
-            return ParsingPrimitives.ParseRawVarint64(ref buffer, ref state) != 0;
-        }
+        public bool ReadBool() => ParsingPrimitives.ParseRawVarint64(ref buffer, ref state) != 0;
+
         /// <summary>
         /// Reads a string field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public string ReadString()
-        {
-            return ParsingPrimitives.ReadString(ref buffer, ref state);
-        }
+        public string ReadString() => ParsingPrimitives.ReadString(ref buffer, ref state);
 
         /// <summary>
         /// Reads an embedded message field value from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public void ReadMessage(IMessage message)
-        {
-            ParsingPrimitivesMessages.ReadMessage(ref this, message);
-        }
+        public void ReadMessage(IMessage message) => ParsingPrimitivesMessages.ReadMessage(ref this, message);
 
         /// <summary>
         /// Reads an embedded group field from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public void ReadGroup(IMessage message)
-        {
-            ParsingPrimitivesMessages.ReadGroup(ref this, message);
-        }
+        public void ReadGroup(IMessage message) => ParsingPrimitivesMessages.ReadGroup(ref this, message);
 
         /// <summary>
         /// Reads a bytes field value from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public ByteString ReadBytes()
-        {
-            return ParsingPrimitives.ReadBytes(ref buffer, ref state);
-        }
+        public ByteString ReadBytes() => ParsingPrimitives.ReadBytes(ref buffer, ref state);
+
         /// <summary>
         /// Reads a uint32 field value from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public uint ReadUInt32()
-        {
-            return ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
-        }
+        public uint ReadUInt32() => ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
 
         /// <summary>
         /// Reads an enum field value from the input.
@@ -290,37 +245,25 @@
         /// Reads an sfixed32 field value from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public int ReadSFixed32()
-        {
-            return (int)ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
-        }
+        public int ReadSFixed32() => (int)ParsingPrimitives.ParseRawLittleEndian32(ref buffer, ref state);
 
         /// <summary>
         /// Reads an sfixed64 field value from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public long ReadSFixed64()
-        {
-            return (long)ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
-        }
+        public long ReadSFixed64() => (long)ParsingPrimitives.ParseRawLittleEndian64(ref buffer, ref state);
 
         /// <summary>
         /// Reads an sint32 field value from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public int ReadSInt32()
-        {
-            return ParsingPrimitives.DecodeZigZag32(ParsingPrimitives.ParseRawVarint32(ref buffer, ref state));
-        }
+        public int ReadSInt32() => ParsingPrimitives.DecodeZigZag32(ParsingPrimitives.ParseRawVarint32(ref buffer, ref state));
 
         /// <summary>
         /// Reads an sint64 field value from the input.
         /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public long ReadSInt64()
-        {
-            return ParsingPrimitives.DecodeZigZag64(ParsingPrimitives.ParseRawVarint64(ref buffer, ref state));
-        }
+        public long ReadSInt64() => ParsingPrimitives.DecodeZigZag64(ParsingPrimitives.ParseRawVarint64(ref buffer, ref state));
 
         /// <summary>
         /// Reads a length for length-delimited data.
@@ -330,10 +273,7 @@
         /// to make the calling code clearer.
         /// </remarks>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public int ReadLength()
-        {
-            return (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
-        }
+        public int ReadLength() => (int)ParsingPrimitives.ParseRawVarint32(ref buffer, ref state);
 
         internal void CopyStateTo(CodedInputStream input)
         {
diff --git a/csharp/src/Google.Protobuf/ParserInternalState.cs b/csharp/src/Google.Protobuf/ParserInternalState.cs
index cb4f471..af7dd78 100644
--- a/csharp/src/Google.Protobuf/ParserInternalState.cs
+++ b/csharp/src/Google.Protobuf/ParserInternalState.cs
@@ -30,20 +30,8 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using System;
-using System.Buffers;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Text;
-using Google.Protobuf.Collections;
-
 namespace Google.Protobuf
 {
-
     // warning: this is a mutable struct, so it needs to be only passed as a ref!
     internal struct ParserInternalState
     {
diff --git a/csharp/src/Google.Protobuf/ParsingPrimitives.cs b/csharp/src/Google.Protobuf/ParsingPrimitives.cs
index e270ed8..4321d26 100644
--- a/csharp/src/Google.Protobuf/ParsingPrimitives.cs
+++ b/csharp/src/Google.Protobuf/ParsingPrimitives.cs
@@ -34,13 +34,10 @@
 using System.Buffers;
 using System.Buffers.Binary;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Security;
-using System.Text;
-using Google.Protobuf.Collections;
 
 namespace Google.Protobuf
 {
diff --git a/csharp/src/Google.Protobuf/ParsingPrimitivesMessages.cs b/csharp/src/Google.Protobuf/ParsingPrimitivesMessages.cs
index eabaf96..5e1babf 100644
--- a/csharp/src/Google.Protobuf/ParsingPrimitivesMessages.cs
+++ b/csharp/src/Google.Protobuf/ParsingPrimitivesMessages.cs
@@ -33,8 +33,6 @@
 using System;
 using System.Buffers;
 using System.Collections.Generic;
-using System.IO;
-using System.Runtime.CompilerServices;
 using System.Security;
 using Google.Protobuf.Collections;
 
diff --git a/csharp/src/Google.Protobuf/ParsingPrimitivesWrappers.cs b/csharp/src/Google.Protobuf/ParsingPrimitivesWrappers.cs
index 629ec32..e874935 100644
--- a/csharp/src/Google.Protobuf/ParsingPrimitivesWrappers.cs
+++ b/csharp/src/Google.Protobuf/ParsingPrimitivesWrappers.cs
@@ -31,15 +31,7 @@
 #endregion
 
 using System;
-using System.Buffers;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 using System.Security;
-using System.Text;
-using Google.Protobuf.Collections;
 
 namespace Google.Protobuf
 {
diff --git a/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs b/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs
index f6fa152..2c5d2c8 100644
--- a/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs
+++ b/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs
@@ -31,7 +31,6 @@
 #endregion
 
 using Google.Protobuf.Collections;
-using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
@@ -226,24 +225,21 @@
         {
             if (values == null)
             {
-                value = default(T);
+                value = default;
                 return false;
             }
 
-            IExtensionValue extensionValue;
-            if (values.TryGetValue(field, out extensionValue))
+            if (values.TryGetValue(field, out IExtensionValue extensionValue))
             {
-                if (extensionValue is ExtensionValue<T>)
+                if (extensionValue is ExtensionValue<T> single)
                 {
-                    ExtensionValue<T> single = extensionValue as ExtensionValue<T>;
                     ByteString bytes = single.GetValue().ToByteString();
                     value = new T();
                     value.MergeFrom(bytes);
                     return true;
                 }
-                else if (extensionValue is RepeatedExtensionValue<T>)
+                else if (extensionValue is RepeatedExtensionValue<T> repeated)
                 {
-                    RepeatedExtensionValue<T> repeated = extensionValue as RepeatedExtensionValue<T>;
                     value = repeated.GetValue()
                         .Select(v => v.ToByteString())
                         .Aggregate(new T(), (t, b) =>
@@ -264,22 +260,19 @@
         {
             if (values == null)
             {
-                value = default(T);
+                value = default;
                 return false;
             }
 
-            IExtensionValue extensionValue;
-            if (values.TryGetValue(field, out extensionValue))
+            if (values.TryGetValue(field, out IExtensionValue extensionValue))
             {
-                if (extensionValue is ExtensionValue<T>)
+                if (extensionValue is ExtensionValue<T> single)
                 {
-                    ExtensionValue<T> single = extensionValue as ExtensionValue<T>;
                     value = single.GetValue();
                     return true;
                 }
-                else if (extensionValue is RepeatedExtensionValue<T>)
+                else if (extensionValue is RepeatedExtensionValue<T> repeated)
                 {
-                    RepeatedExtensionValue<T> repeated = extensionValue as RepeatedExtensionValue<T>;
                     if (repeated.GetValue().Count != 0)
                     {
                         RepeatedField<T> repeatedField = repeated.GetValue();
@@ -317,7 +310,7 @@
                 }
             }
 
-            value = default(T);
+            value = default;
             return false;
         }
     }
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
index b22048f..3c7fac2 100644
--- a/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorDeclaration.cs
@@ -30,11 +30,9 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
-using System.Text;
 using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
 
 namespace Google.Protobuf.Reflection
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs
index 93f2fa9..5128ad9 100644
--- a/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorPool.cs
@@ -51,12 +51,11 @@
         private readonly IDictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor> enumValuesByNumber =
             new Dictionary<ObjectIntPair<IDescriptor>, EnumValueDescriptor>();
 
-        private readonly HashSet<FileDescriptor> dependencies;
+        private readonly HashSet<FileDescriptor> dependencies = new HashSet<FileDescriptor>();
 
         internal DescriptorPool(IEnumerable<FileDescriptor> dependencyFiles)
         {
-            dependencies = new HashSet<FileDescriptor>();
-            foreach (var dependencyFile in dependencyFiles)
+            foreach (FileDescriptor dependencyFile in dependencyFiles)
             {
                 dependencies.Add(dependencyFile);
                 ImportPublicDependencies(dependencyFile);
@@ -88,10 +87,8 @@
         /// or null if the symbol doesn't exist or has the wrong type</returns>
         internal T FindSymbol<T>(string fullName) where T : class
         {
-            IDescriptor result;
-            descriptorsByName.TryGetValue(fullName, out result);
-            T descriptor = result as T;
-            if (descriptor != null)
+            descriptorsByName.TryGetValue(fullName, out IDescriptor result);
+            if (result is T descriptor)
             {
                 return descriptor;
             }
@@ -131,10 +128,9 @@
                 name = fullName;
             }
 
-            IDescriptor old;
-            if (descriptorsByName.TryGetValue(fullName, out old))
+            if (descriptorsByName.TryGetValue(fullName, out IDescriptor old))
             {
-                if (!(old is PackageDescriptor))
+                if (old is not PackageDescriptor)
                 {
                     throw new DescriptorValidationException(file,
                                                             "\"" + name +
@@ -153,10 +149,9 @@
         internal void AddSymbol(IDescriptor descriptor)
         {
             ValidateSymbolName(descriptor);
-            String fullName = descriptor.FullName;
+            string fullName = descriptor.FullName;
 
-            IDescriptor old;
-            if (descriptorsByName.TryGetValue(fullName, out old))
+            if (descriptorsByName.TryGetValue(fullName, out IDescriptor old))
             {
                 int dotPos = fullName.LastIndexOf('.');
                 string message;
@@ -181,8 +176,7 @@
             descriptorsByName[fullName] = descriptor;
         }
 
-        private static readonly Regex ValidationRegex = new Regex("^[_A-Za-z][_A-Za-z0-9]*$",
-                                                                  FrameworkPortability.CompiledRegexWhereAvailable);
+        private static readonly Regex ValidationRegex = new Regex("^[_A-Za-z][_A-Za-z0-9]*$", FrameworkPortability.CompiledRegexWhereAvailable);
 
         /// <summary>
         /// Verifies that the descriptor's name is valid (i.e. it contains
@@ -191,7 +185,7 @@
         /// <param name="descriptor"></param>
         private static void ValidateSymbolName(IDescriptor descriptor)
         {
-            if (descriptor.Name == "")
+            if (descriptor.Name.Length == 0)
             {
                 throw new DescriptorValidationException(descriptor, "Missing name.");
             }
@@ -208,15 +202,13 @@
         /// </summary>
         internal FieldDescriptor FindFieldByNumber(MessageDescriptor messageDescriptor, int number)
         {
-            FieldDescriptor ret;
-            fieldsByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(messageDescriptor, number), out ret);
+            fieldsByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(messageDescriptor, number), out FieldDescriptor ret);
             return ret;
         }
 
         internal EnumValueDescriptor FindEnumValueByNumber(EnumDescriptor enumDescriptor, int number)
         {
-            EnumValueDescriptor ret;
-            enumValuesByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(enumDescriptor, number), out ret);
+            enumValuesByNumber.TryGetValue(new ObjectIntPair<IDescriptor>(enumDescriptor, number), out EnumValueDescriptor ret);
             return ret;
         }
 
@@ -229,8 +221,7 @@
         {
             // for extensions, we use the extended type, otherwise we use the containing type
             ObjectIntPair<IDescriptor> key = new ObjectIntPair<IDescriptor>(field.Proto.HasExtendee ? field.ExtendeeType : field.ContainingType, field.FieldNumber);
-            FieldDescriptor old;
-            if (fieldsByNumber.TryGetValue(key, out old))
+            if (fieldsByNumber.TryGetValue(key, out FieldDescriptor old))
             {
                 throw new DescriptorValidationException(field, "Field number " + field.FieldNumber +
                                                                "has already been used in \"" +
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorValidationException.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorValidationException.cs
index 143671d..f48e8e1 100644
--- a/csharp/src/Google.Protobuf/Reflection/DescriptorValidationException.cs
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorValidationException.cs
@@ -40,25 +40,16 @@
     /// </summary>
     public sealed class DescriptorValidationException : Exception
     {
-        private readonly String name;
-        private readonly string description;
-
         /// <value>
         /// The full name of the descriptor where the error occurred.
         /// </value>
-        public String ProblemSymbolName
-        {
-            get { return name; }
-        }
+        public string ProblemSymbolName { get; }
 
         /// <value>
         /// A human-readable description of the error. (The Message property
         /// is made up of the descriptor's name and this description.)
         /// </value>
-        public string Description
-        {
-            get { return description; }
-        }
+        public string Description { get; }
 
         internal DescriptorValidationException(IDescriptor problemDescriptor, string description) :
             base(problemDescriptor.FullName + ": " + description)
@@ -66,15 +57,15 @@
             // Note that problemDescriptor may be partially uninitialized, so we
             // don't want to expose it directly to the user.  So, we only provide
             // the name and the original proto.
-            name = problemDescriptor.FullName;
-            this.description = description;
+            ProblemSymbolName = problemDescriptor.FullName;
+            Description = description;
         }
 
         internal DescriptorValidationException(IDescriptor problemDescriptor, string description, Exception cause) :
             base(problemDescriptor.FullName + ": " + description, cause)
         {
-            name = problemDescriptor.FullName;
-            this.description = description;
+            ProblemSymbolName = problemDescriptor.FullName;
+            Description = description;
         }
     }
 }
\ No newline at end of file
diff --git a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
index 3f2e1c4..a14cc1d 100644
--- a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
@@ -41,17 +41,12 @@
     /// </summary>
     public sealed class EnumDescriptor : DescriptorBase
     {
-        private readonly EnumDescriptorProto proto;
-        private readonly MessageDescriptor containingType;
-        private readonly IList<EnumValueDescriptor> values;
-        private readonly Type clrType;
-
         internal EnumDescriptor(EnumDescriptorProto proto, FileDescriptor file, MessageDescriptor parent, int index, Type clrType)
             : base(file, file.ComputeFullName(parent, proto.Name), index)
         {
-            this.proto = proto;
-            this.clrType = clrType;
-            containingType = parent;
+            Proto = proto;
+            ClrType = clrType;
+            ContainingType = parent;
 
             if (proto.Value.Count == 0)
             {
@@ -60,13 +55,13 @@
                 throw new DescriptorValidationException(this, "Enums must contain at least one value.");
             }
 
-            values = DescriptorUtil.ConvertAndMakeReadOnly(proto.Value,
+            Values = DescriptorUtil.ConvertAndMakeReadOnly(proto.Value,
                                                            (value, i) => new EnumValueDescriptor(value, file, this, i));
 
             File.DescriptorPool.AddSymbol(this);
         }
 
-        internal EnumDescriptorProto Proto { get { return proto; } }
+        internal EnumDescriptorProto Proto { get; }
 
         /// <summary>
         /// Returns a clone of the underlying <see cref="EnumDescriptorProto"/> describing this enum.
@@ -79,39 +74,29 @@
         /// <summary>
         /// The brief name of the descriptor's target.
         /// </summary>
-        public override string Name { get { return proto.Name; } }
+        public override string Name => Proto.Name;
 
-        internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
-        {
-            switch (fieldNumber)
+        internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) =>
+            fieldNumber switch
             {
-                case EnumDescriptorProto.ValueFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) Values;
-                default:
-                    return null;
-            }
-        }
+                EnumDescriptorProto.ValueFieldNumber => (IReadOnlyList<DescriptorBase>)Values,
+                _ => null,
+            };
 
         /// <summary>
         /// The CLR type for this enum. For generated code, this will be a CLR enum type.
         /// </summary>
-        public Type ClrType { get { return clrType; } }
+        public Type ClrType { get; }
 
         /// <value>
         /// If this is a nested type, get the outer descriptor, otherwise null.
         /// </value>
-        public MessageDescriptor ContainingType
-        {
-            get { return containingType; }
-        }
+        public MessageDescriptor ContainingType { get; }
 
         /// <value>
         /// An unmodifiable list of defined value descriptors for this enum.
         /// </value>
-        public IList<EnumValueDescriptor> Values
-        {
-            get { return values; }
-        }
+        public IList<EnumValueDescriptor> Values { get; }
 
         /// <summary>
         /// Finds an enum value by number. If multiple enum values have the
diff --git a/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs
index 50b26a4..7a0cce0 100644
--- a/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs
@@ -40,20 +40,17 @@
     /// </summary>
     public sealed class EnumValueDescriptor : DescriptorBase
     {
-        private readonly EnumDescriptor enumDescriptor;
-        private readonly EnumValueDescriptorProto proto;
-
         internal EnumValueDescriptor(EnumValueDescriptorProto proto, FileDescriptor file,
                                      EnumDescriptor parent, int index)
             : base(file, parent.FullName + "." + proto.Name, index)
         {
-            this.proto = proto;
-            enumDescriptor = parent;
+            Proto = proto;
+            EnumDescriptor = parent;
             file.DescriptorPool.AddSymbol(this);
             file.DescriptorPool.AddEnumValueByNumber(this);
         }
 
-        internal EnumValueDescriptorProto Proto { get { return proto; } }
+        internal EnumValueDescriptorProto Proto { get; }
 
         /// <summary>
         /// Returns a clone of the underlying <see cref="EnumValueDescriptorProto"/> describing this enum value.
@@ -66,17 +63,17 @@
         /// <summary>
         /// Returns the name of the enum value described by this object.
         /// </summary>
-        public override string Name { get { return proto.Name; } }
+        public override string Name => Proto.Name;
 
         /// <summary>
         /// Returns the number associated with this enum value.
         /// </summary>
-        public int Number { get { return Proto.Number; } }
+        public int Number => Proto.Number;
 
         /// <summary>
         /// Returns the enum descriptor that this value is part of.
         /// </summary>
-        public EnumDescriptor EnumDescriptor { get { return enumDescriptor; } }
+        public EnumDescriptor EnumDescriptor { get; }
 
         /// <summary>
         /// The (possibly empty) set of custom options for this enum value.
diff --git a/csharp/src/Google.Protobuf/Reflection/ExtensionCollection.cs b/csharp/src/Google.Protobuf/Reflection/ExtensionCollection.cs
index ca874f9..1e10060 100644
--- a/csharp/src/Google.Protobuf/Reflection/ExtensionCollection.cs
+++ b/csharp/src/Google.Protobuf/Reflection/ExtensionCollection.cs
@@ -107,8 +107,7 @@
             {
                 descriptor.CrossLink();
 
-                IList<FieldDescriptor> list;
-                if (!declarationOrder.TryGetValue(descriptor.ExtendeeType, out list))
+                if (!declarationOrder.TryGetValue(descriptor.ExtendeeType, out IList<FieldDescriptor> list))
                 {
                     list = new List<FieldDescriptor>();
                     declarationOrder.Add(descriptor.ExtendeeType, list);
diff --git a/csharp/src/Google.Protobuf/Reflection/FieldAccessorBase.cs b/csharp/src/Google.Protobuf/Reflection/FieldAccessorBase.cs
index 85b7d39..156025d 100644
--- a/csharp/src/Google.Protobuf/Reflection/FieldAccessorBase.cs
+++ b/csharp/src/Google.Protobuf/Reflection/FieldAccessorBase.cs
@@ -42,15 +42,14 @@
     internal abstract class FieldAccessorBase : IFieldAccessor
     {
         private readonly Func<IMessage, object> getValueDelegate;
-        private readonly FieldDescriptor descriptor;
 
         internal FieldAccessorBase(PropertyInfo property, FieldDescriptor descriptor)
         {
-            this.descriptor = descriptor;
+            Descriptor = descriptor;
             getValueDelegate = ReflectionUtil.CreateFuncIMessageObject(property.GetGetMethod());
         }
 
-        public FieldDescriptor Descriptor { get { return descriptor; } }
+        public FieldDescriptor Descriptor { get; }
 
         public object GetValue(IMessage message)
         {
diff --git a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs
index 84ad49d..e4fd7d2 100644
--- a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs
@@ -176,47 +176,28 @@
         /// </summary>
         private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Type type)
         {
-            switch (type)
+            return type switch
             {
-                case FieldDescriptorProto.Types.Type.Double:
-                    return FieldType.Double;
-                case FieldDescriptorProto.Types.Type.Float:
-                    return FieldType.Float;
-                case FieldDescriptorProto.Types.Type.Int64:
-                    return FieldType.Int64;
-                case FieldDescriptorProto.Types.Type.Uint64:
-                    return FieldType.UInt64;
-                case FieldDescriptorProto.Types.Type.Int32:
-                    return FieldType.Int32;
-                case FieldDescriptorProto.Types.Type.Fixed64:
-                    return FieldType.Fixed64;
-                case FieldDescriptorProto.Types.Type.Fixed32:
-                    return FieldType.Fixed32;
-                case FieldDescriptorProto.Types.Type.Bool:
-                    return FieldType.Bool;
-                case FieldDescriptorProto.Types.Type.String:
-                    return FieldType.String;
-                case FieldDescriptorProto.Types.Type.Group:
-                    return FieldType.Group;
-                case FieldDescriptorProto.Types.Type.Message:
-                    return FieldType.Message;
-                case FieldDescriptorProto.Types.Type.Bytes:
-                    return FieldType.Bytes;
-                case FieldDescriptorProto.Types.Type.Uint32:
-                    return FieldType.UInt32;
-                case FieldDescriptorProto.Types.Type.Enum:
-                    return FieldType.Enum;
-                case FieldDescriptorProto.Types.Type.Sfixed32:
-                    return FieldType.SFixed32;
-                case FieldDescriptorProto.Types.Type.Sfixed64:
-                    return FieldType.SFixed64;
-                case FieldDescriptorProto.Types.Type.Sint32:
-                    return FieldType.SInt32;
-                case FieldDescriptorProto.Types.Type.Sint64:
-                    return FieldType.SInt64;
-                default:
-                    throw new ArgumentException("Invalid type specified");
-            }
+                FieldDescriptorProto.Types.Type.Double => FieldType.Double,
+                FieldDescriptorProto.Types.Type.Float => FieldType.Float,
+                FieldDescriptorProto.Types.Type.Int64 => FieldType.Int64,
+                FieldDescriptorProto.Types.Type.Uint64 => FieldType.UInt64,
+                FieldDescriptorProto.Types.Type.Int32 => FieldType.Int32,
+                FieldDescriptorProto.Types.Type.Fixed64 => FieldType.Fixed64,
+                FieldDescriptorProto.Types.Type.Fixed32 => FieldType.Fixed32,
+                FieldDescriptorProto.Types.Type.Bool => FieldType.Bool,
+                FieldDescriptorProto.Types.Type.String => FieldType.String,
+                FieldDescriptorProto.Types.Type.Group => FieldType.Group,
+                FieldDescriptorProto.Types.Type.Message => FieldType.Message,
+                FieldDescriptorProto.Types.Type.Bytes => FieldType.Bytes,
+                FieldDescriptorProto.Types.Type.Uint32 => FieldType.UInt32,
+                FieldDescriptorProto.Types.Type.Enum => FieldType.Enum,
+                FieldDescriptorProto.Types.Type.Sfixed32 => FieldType.SFixed32,
+                FieldDescriptorProto.Types.Type.Sfixed64 => FieldType.SFixed64,
+                FieldDescriptorProto.Types.Type.Sint32 => FieldType.SInt32,
+                FieldDescriptorProto.Types.Type.Sint64 => FieldType.SInt64,
+                _ => throw new ArgumentException("Invalid type specified"),
+            };
         }
 
         /// <summary>
@@ -391,11 +372,11 @@
 
                 if (fieldType == FieldType.Message || fieldType == FieldType.Group)
                 {
-                    if (!(typeDescriptor is MessageDescriptor))
+                    if (typeDescriptor is not MessageDescriptor m)
                     {
                         throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not a message type.");
                     }
-                    messageType = (MessageDescriptor) typeDescriptor;
+                    messageType = m;
 
                     if (Proto.HasDefaultValue)
                     {
@@ -404,11 +385,11 @@
                 }
                 else if (fieldType == FieldType.Enum)
                 {
-                    if (!(typeDescriptor is EnumDescriptor))
+                    if (typeDescriptor is not EnumDescriptor e)
                     {
                         throw new DescriptorValidationException(this, $"\"{Proto.TypeName}\" is not an enum type.");
                     }
-                    enumType = (EnumDescriptor) typeDescriptor;
+                    enumType = e;
                 }
                 else
                 {
diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
index d7701da..cb711de 100644
--- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
@@ -35,7 +35,6 @@
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
-using System.Diagnostics;
 using System.Linq;
 using System.Threading;
 using static Google.Protobuf.Reflection.SourceCodeInfo.Types;
@@ -173,25 +172,18 @@
             return list[index];
         }
 
-        private IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
-        {
-            switch (fieldNumber)
+        private IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) =>
+            fieldNumber switch
             {
-                case FileDescriptorProto.ServiceFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) Services;
-                case FileDescriptorProto.MessageTypeFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) MessageTypes;
-                case FileDescriptorProto.EnumTypeFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) EnumTypes;
-                default:
-                    return null;
-            }
-        }
+                FileDescriptorProto.ServiceFieldNumber => (IReadOnlyList<DescriptorBase>)Services,
+                FileDescriptorProto.MessageTypeFieldNumber => (IReadOnlyList<DescriptorBase>)MessageTypes,
+                FileDescriptorProto.EnumTypeFieldNumber => (IReadOnlyList<DescriptorBase>)EnumTypes,
+                _ => null,
+            };
 
         internal DescriptorDeclaration GetDeclaration(IDescriptor descriptor)
         {
-            DescriptorDeclaration declaration;
-            declarations.Value.TryGetValue(descriptor, out declaration);
+            declarations.Value.TryGetValue(descriptor, out DescriptorDeclaration declaration);
             return declaration;
         }
 
@@ -227,8 +219,7 @@
                     throw new DescriptorValidationException(@this, "Invalid public dependency index.");
                 }
                 string name = proto.Dependency[index];
-                FileDescriptor file;
-                if (!nameToFileMap.TryGetValue(name, out file))
+                if (!nameToFileMap.TryGetValue(name, out FileDescriptor file))
                 {
                     if (!allowUnknownDependencies)
                     {
@@ -332,7 +323,7 @@
         /// <param name="name">The unqualified type name to look for.</param>
         /// <typeparam name="T">The type of descriptor to look for</typeparam>
         /// <returns>The type's descriptor, or null if not found.</returns>
-        public T FindTypeByName<T>(String name)
+        public T FindTypeByName<T>(string name)
             where T : class, IDescriptor
         {
             // Don't allow looking up nested types.  This will make optimization
@@ -507,8 +498,7 @@
                 var dependencies = new List<FileDescriptor>();
                 foreach (var dependencyName in proto.Dependency)
                 {
-                    FileDescriptor dependency;
-                    if (!descriptorsByName.TryGetValue(dependencyName, out dependency))
+                    if (!descriptorsByName.TryGetValue(dependencyName, out FileDescriptor dependency))
                     {
                         throw new ArgumentException($"Dependency missing: {dependencyName}");
                     }
@@ -565,7 +555,7 @@
         /// <value>
         /// The file descriptor for <c>descriptor.proto</c>.
         /// </value>
-        public static FileDescriptor DescriptorProtoFileDescriptor { get { return DescriptorReflection.Descriptor; } }
+        public static FileDescriptor DescriptorProtoFileDescriptor => DescriptorReflection.Descriptor;
 
         /// <summary>
         /// The (possibly empty) set of custom options for this file.
diff --git a/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs b/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs
index d0a495b..dbf16c6 100644
--- a/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs
+++ b/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs
@@ -29,6 +29,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
+
 using System;
 using System.Diagnostics.CodeAnalysis;
 
@@ -57,7 +58,7 @@
         /// Irrelevant for file descriptors; the CLR type for the message for message descriptors.
         /// </summary>
         [DynamicallyAccessedMembers(MessageAccessibility)]
-        public Type ClrType { get; private set; }
+        public Type ClrType { get; }
 
         /// <summary>
         /// Irrelevant for file descriptors; the parser for message descriptors.
diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
index 415b7be..f2bb61c 100644
--- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
@@ -130,20 +130,14 @@
         /// </summary>
         public override string Name => Proto.Name;
 
-        internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
-        {
-            switch (fieldNumber)
+        internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) =>
+            fieldNumber switch
             {
-                case DescriptorProto.FieldFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) fieldsInDeclarationOrder;
-                case DescriptorProto.NestedTypeFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) NestedTypes;
-                case DescriptorProto.EnumTypeFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) EnumTypes;
-                default:
-                    return null;
-            }
-        }
+                DescriptorProto.FieldFieldNumber => (IReadOnlyList<DescriptorBase>)fieldsInDeclarationOrder,
+                DescriptorProto.NestedTypeFieldNumber => (IReadOnlyList<DescriptorBase>)NestedTypes,
+                DescriptorProto.EnumTypeFieldNumber => (IReadOnlyList<DescriptorBase>)EnumTypes,
+                _ => null,
+            };
 
         internal DescriptorProto Proto { get; }
 
@@ -272,7 +266,7 @@
         /// </summary>
         /// <param name="name">The unqualified name of the field (e.g. "foo").</param>
         /// <returns>The field's descriptor, or null if not found.</returns>
-        public FieldDescriptor FindFieldByName(String name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
+        public FieldDescriptor FindFieldByName(string name) => File.DescriptorPool.FindSymbol<FieldDescriptor>(FullName + "." + name);
 
         /// <summary>
         /// Finds a field by field number.
diff --git a/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs
index f5ecf2e..4f0ea43 100644
--- a/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs
@@ -40,35 +40,31 @@
     /// </summary>
     public sealed class MethodDescriptor : DescriptorBase
     {
-        private readonly MethodDescriptorProto proto;
-        private readonly ServiceDescriptor service;
-        private MessageDescriptor inputType;
-        private MessageDescriptor outputType;
 
         /// <value>
         /// The service this method belongs to.
         /// </value>
-        public ServiceDescriptor Service { get { return service; } }
+        public ServiceDescriptor Service { get; }
 
         /// <value>
         /// The method's input type.
         /// </value>
-        public MessageDescriptor InputType { get { return inputType; } }
+        public MessageDescriptor InputType { get; private set; }
 
         /// <value>
         /// The method's input type.
         /// </value>
-        public MessageDescriptor OutputType { get { return outputType; } }
+        public MessageDescriptor OutputType { get; private set; }
 
         /// <value>
         /// Indicates if client streams multiple requests.
         /// </value>
-        public bool IsClientStreaming { get { return proto.ClientStreaming; } }
+        public bool IsClientStreaming => Proto.ClientStreaming;
 
         /// <value>
         /// Indicates if server streams multiple responses.
         /// </value>
-        public bool IsServerStreaming { get { return proto.ServerStreaming; } }
+        public bool IsServerStreaming => Proto.ServerStreaming;
 
         /// <summary>
         /// The (possibly empty) set of custom options for this method.
@@ -91,7 +87,7 @@
         public T GetOption<T>(Extension<MethodOptions, T> extension)
         {
             var value = Proto.Options.GetExtension(extension);
-            return value is IDeepCloneable<T> ? (value as IDeepCloneable<T>).Clone() : value;
+            return value is IDeepCloneable<T> c ? c.Clone() : value;
         }
 
         /// <summary>
@@ -107,12 +103,12 @@
                                   ServiceDescriptor parent, int index)
             : base(file, parent.FullName + "." + proto.Name, index)
         {
-            this.proto = proto;
-            service = parent;
+            Proto = proto;
+            Service = parent;
             file.DescriptorPool.AddSymbol(this);
         }
 
-        internal MethodDescriptorProto Proto { get { return proto; } }
+        internal MethodDescriptorProto Proto { get; private set; }
 
         /// <summary>
         /// Returns a clone of the underlying <see cref="MethodDescriptorProto"/> describing this method.
@@ -125,23 +121,23 @@
         /// <summary>
         /// The brief name of the descriptor's target.
         /// </summary>
-        public override string Name { get { return proto.Name; } }
+        public override string Name => Proto.Name;
 
         internal void CrossLink()
         {
             IDescriptor lookup = File.DescriptorPool.LookupSymbol(Proto.InputType, this);
-            if (!(lookup is MessageDescriptor))
+            if (lookup is not MessageDescriptor inpoutType)
             {
                 throw new DescriptorValidationException(this, "\"" + Proto.InputType + "\" is not a message type.");
             }
-            inputType = (MessageDescriptor) lookup;
+            InputType = inpoutType;
 
             lookup = File.DescriptorPool.LookupSymbol(Proto.OutputType, this);
-            if (!(lookup is MessageDescriptor))
+            if (lookup is not MessageDescriptor outputType)
             {
                 throw new DescriptorValidationException(this, "\"" + Proto.OutputType + "\" is not a message type.");
             }
-            outputType = (MessageDescriptor) lookup;
+            OutputType = outputType;
         }
     }
 }
diff --git a/csharp/src/Google.Protobuf/Reflection/OriginalNameAttribute.cs b/csharp/src/Google.Protobuf/Reflection/OriginalNameAttribute.cs
index 63f5228..880716a 100644
--- a/csharp/src/Google.Protobuf/Reflection/OriginalNameAttribute.cs
+++ b/csharp/src/Google.Protobuf/Reflection/OriginalNameAttribute.cs
@@ -60,6 +60,5 @@
             Name = ProtoPreconditions.CheckNotNull(name, nameof(name));
             PreferredAlias = true;
         }
-
     }
 }
diff --git a/csharp/src/Google.Protobuf/Reflection/PackageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/PackageDescriptor.cs
index e547d83..0b6de81 100644
--- a/csharp/src/Google.Protobuf/Reflection/PackageDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/PackageDescriptor.cs
@@ -39,30 +39,15 @@
     /// </summary>
     internal sealed class PackageDescriptor : IDescriptor
     {
-        private readonly string name;
-        private readonly string fullName;
-        private readonly FileDescriptor file;
-
         internal PackageDescriptor(string name, string fullName, FileDescriptor file)
         {
-            this.file = file;
-            this.fullName = fullName;
-            this.name = name;
+            File = file;
+            FullName = fullName;
+            Name = name;
         }
 
-        public string Name
-        {
-            get { return name; }
-        }
-
-        public string FullName
-        {
-            get { return fullName; }
-        }
-
-        public FileDescriptor File
-        {
-            get { return file; }
-        }
+        public string Name { get; }
+        public string FullName { get; }
+        public FileDescriptor File { get; }
     }
 }
\ No newline at end of file
diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
index 73efcc2..2a6afdb 100644
--- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
+++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
@@ -221,20 +221,18 @@
 
             public object GetExtension(IMessage message)
             {
-                if (!(message is T1))
+                if (message is not T1 extensionMessage)
                 {
                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
                 }
 
-                T1 extensionMessage = (T1)message;
-
-                if (extension is Extension<T1, T3>)
+                if (extension is Extension<T1, T3> ext13)
                 {
-                    return extensionMessage.GetExtension(extension as Extension<T1, T3>);
+                    return extensionMessage.GetExtension(ext13);
                 }
-                else if (extension is RepeatedExtension<T1, T3>)
+                else if (extension is RepeatedExtension<T1, T3> repeatedExt13)
                 {
-                    return extensionMessage.GetOrInitializeExtension(extension as RepeatedExtension<T1, T3>);
+                    return extensionMessage.GetOrInitializeExtension(repeatedExt13);
                 }
                 else
                 {
@@ -244,16 +242,14 @@
 
             public bool HasExtension(IMessage message)
             {
-                if (!(message is T1))
+                if (message is not T1 extensionMessage)
                 {
                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
                 }
 
-                T1 extensionMessage = (T1)message;
-
-                if (extension is Extension<T1, T3>)
+                if (extension is Extension<T1, T3> ext13)
                 {
-                    return extensionMessage.HasExtension(extension as Extension<T1, T3>);
+                    return extensionMessage.HasExtension(ext13);
                 }
                 else if (extension is RepeatedExtension<T1, T3>)
                 {
@@ -267,16 +263,14 @@
 
             public void SetExtension(IMessage message, object value)
             {
-                if (!(message is T1))
+                if (message is not T1 extensionMessage)
                 {
                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
                 }
 
-                T1 extensionMessage = (T1)message;
-
-                if (extension is Extension<T1, T3>)
+                if (extension is Extension<T1, T3> ext13)
                 {
-                    extensionMessage.SetExtension(extension as Extension<T1, T3>, (T3)value);
+                    extensionMessage.SetExtension(ext13, (T3)value);
                 }
                 else if (extension is RepeatedExtension<T1, T3>)
                 {
@@ -290,20 +284,18 @@
 
             public void ClearExtension(IMessage message)
             {
-                if (!(message is T1))
+                if (message is not T1 extensionMessage)
                 {
                     throw new InvalidCastException("Cannot access extension on message that isn't IExtensionMessage");
                 }
 
-                T1 extensionMessage = (T1)message;
-
-                if (extension is Extension<T1, T3>)
+                if (extension is Extension<T1, T3> ext13)
                 {
-                    extensionMessage.ClearExtension(extension as Extension<T1, T3>);
+                    extensionMessage.ClearExtension(ext13);
                 }
-                else if (extension is RepeatedExtension<T1, T3>)
+                else if (extension is RepeatedExtension<T1, T3> repeatedExt13)
                 {
-                    extensionMessage.GetExtension(extension as RepeatedExtension<T1, T3>).Clear();
+                    extensionMessage.GetExtension(repeatedExt13).Clear();
                 }
                 else
                 {
diff --git a/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs b/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs
index afb4a69..2073be3 100644
--- a/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs
@@ -60,6 +60,5 @@
         {
             throw new InvalidOperationException("SetValue is not implemented for repeated fields");
         }
-
     }
 }
diff --git a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
index 944ea11..55d812b 100644
--- a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
@@ -33,7 +33,6 @@
 using Google.Protobuf.Collections;
 using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 
 namespace Google.Protobuf.Reflection
 {
@@ -42,14 +41,11 @@
     /// </summary>
     public sealed class ServiceDescriptor : DescriptorBase
     {
-        private readonly ServiceDescriptorProto proto;
-        private readonly IList<MethodDescriptor> methods;
-
         internal ServiceDescriptor(ServiceDescriptorProto proto, FileDescriptor file, int index)
             : base(file, file.ComputeFullName(null, proto.Name), index)
         {
-            this.proto = proto;
-            methods = DescriptorUtil.ConvertAndMakeReadOnly(proto.Method,
+            Proto = proto;
+            Methods = DescriptorUtil.ConvertAndMakeReadOnly(proto.Method,
                                                             (method, i) => new MethodDescriptor(method, file, this, i));
 
             file.DescriptorPool.AddSymbol(this);
@@ -58,20 +54,16 @@
         /// <summary>
         /// The brief name of the descriptor's target.
         /// </summary>
-        public override string Name { get { return proto.Name; } }
+        public override string Name => Proto.Name;
 
-        internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber)
-        {
-            switch (fieldNumber)
+        internal override IReadOnlyList<DescriptorBase> GetNestedDescriptorListForField(int fieldNumber) =>
+            fieldNumber switch
             {
-                case ServiceDescriptorProto.MethodFieldNumber:
-                    return (IReadOnlyList<DescriptorBase>) methods;
-                default:
-                    return null;
-            }
-        }
+                ServiceDescriptorProto.MethodFieldNumber => (IReadOnlyList<DescriptorBase>)Methods,
+                _ => null,
+            };
 
-        internal ServiceDescriptorProto Proto { get { return proto; } }
+        internal ServiceDescriptorProto Proto { get; }
 
         /// <summary>
         /// Returns a clone of the underlying <see cref="ServiceDescriptorProto"/> describing this service.
@@ -84,20 +76,15 @@
         /// <value>
         /// An unmodifiable list of methods in this service.
         /// </value>
-        public IList<MethodDescriptor> Methods
-        {
-            get { return methods; }
-        }
+        public IList<MethodDescriptor> Methods { get; }
 
         /// <summary>
         /// Finds a method by name.
         /// </summary>
         /// <param name="name">The unqualified name of the method (e.g. "Foo").</param>
         /// <returns>The method's descriptor, or null if not found.</returns>
-        public MethodDescriptor FindMethodByName(String name)
-        {
-            return File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name);
-        }
+        public MethodDescriptor FindMethodByName(string name) =>
+            File.DescriptorPool.FindSymbol<MethodDescriptor>(FullName + "." + name);
 
         /// <summary>
         /// The (possibly empty) set of custom options for this service.
@@ -134,7 +121,7 @@
 
         internal void CrossLink()
         {
-            foreach (MethodDescriptor method in methods)
+            foreach (MethodDescriptor method in Methods)
             {
                 method.CrossLink();
             }
diff --git a/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs b/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs
index ac35e72..284effe 100644
--- a/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs
@@ -31,7 +31,6 @@
 #endregion
 
 using System;
-using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using Google.Protobuf.Compatibility;
@@ -108,7 +107,7 @@
             // Primitive proto3 fields without the optional keyword, which aren't in oneofs.
             else
             {
-                hasDelegate = message => { throw new InvalidOperationException("Presence is not implemented for this field"); };
+                hasDelegate = message => throw new InvalidOperationException("Presence is not implemented for this field");
 
                 // While presence isn't supported, clearing still is; it's just setting to a default value.
                 object defaultValue = GetDefaultValue(descriptor);
@@ -116,42 +115,21 @@
             }
         }
 
-        private static object GetDefaultValue(FieldDescriptor descriptor)
-        {
-            switch (descriptor.FieldType)
+        private static object GetDefaultValue(FieldDescriptor descriptor) =>
+            descriptor.FieldType switch
             {
-                case FieldType.Bool:
-                    return false;
-                case FieldType.Bytes:
-                    return ByteString.Empty;
-                case FieldType.String:
-                    return "";
-                case FieldType.Double:
-                    return 0.0;
-                case FieldType.SInt32:
-                case FieldType.Int32:
-                case FieldType.SFixed32:
-                case FieldType.Enum:
-                    return 0;
-                case FieldType.Fixed32:
-                case FieldType.UInt32:
-                    return (uint)0;
-                case FieldType.Fixed64:
-                case FieldType.UInt64:
-                    return 0UL;
-                case FieldType.SFixed64:
-                case FieldType.Int64:
-                case FieldType.SInt64:
-                    return 0L;
-                case FieldType.Float:
-                    return 0f;
-                case FieldType.Message:
-                case FieldType.Group: // Never expect to get this, but...
-                    return null;
-                default:
-                    throw new ArgumentException("Invalid field type");
-            }
-        }
+                FieldType.Bool => false,
+                FieldType.Bytes => ByteString.Empty,
+                FieldType.String => "",
+                FieldType.Double => 0.0,
+                FieldType.SInt32 or FieldType.Int32 or FieldType.SFixed32 or FieldType.Enum => 0,
+                FieldType.Fixed32 or FieldType.UInt32 => (uint)0,
+                FieldType.Fixed64 or FieldType.UInt64 => 0UL,
+                FieldType.SFixed64 or FieldType.Int64 or FieldType.SInt64 => 0L,
+                FieldType.Float => 0f,
+                FieldType.Message or FieldType.Group => null,
+                _ => throw new ArgumentException("Invalid field type"),
+            };
 
         public override void Clear(IMessage message) => clearDelegate(message);
         public override bool HasValue(IMessage message) => hasDelegate(message);
diff --git a/csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs b/csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs
index e94e3e6..222bc7e 100644
--- a/csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs
+++ b/csharp/src/Google.Protobuf/Reflection/TypeRegistry.cs
@@ -29,6 +29,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
+
 using System.Collections.Generic;
 using System.Linq;
 
@@ -60,9 +61,8 @@
         /// if there is no such message descriptor.</returns>
         public MessageDescriptor Find(string fullName)
         {
-            MessageDescriptor ret;
             // Ignore the return value as ret will end up with the right value either way.
-            fullNameToMessageMap.TryGetValue(fullName, out ret);
+            fullNameToMessageMap.TryGetValue(fullName, out MessageDescriptor ret);
             return ret;
         }
 
diff --git a/csharp/src/Google.Protobuf/UnknownField.cs b/csharp/src/Google.Protobuf/UnknownField.cs
index 4793a64..abec108 100644
--- a/csharp/src/Google.Protobuf/UnknownField.cs
+++ b/csharp/src/Google.Protobuf/UnknownField.cs
@@ -30,9 +30,7 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 using Google.Protobuf.Collections;
 
 namespace Google.Protobuf
@@ -73,13 +71,12 @@
             {
                 return true;
             }
-            UnknownField otherField = other as UnknownField;
-            return otherField != null
-                   && Lists.Equals(varintList, otherField.varintList)
-                   && Lists.Equals(fixed32List, otherField.fixed32List)
-                   && Lists.Equals(fixed64List, otherField.fixed64List)
-                   && Lists.Equals(lengthDelimitedList, otherField.lengthDelimitedList)
-                   && Lists.Equals(groupList, otherField.groupList);
+            return other is UnknownField otherField
+                && Lists.Equals(varintList, otherField.varintList)
+                && Lists.Equals(fixed32List, otherField.fixed32List)
+                && Lists.Equals(fixed64List, otherField.fixed64List)
+                && Lists.Equals(lengthDelimitedList, otherField.lengthDelimitedList)
+                && Lists.Equals(groupList, otherField.groupList);
         }
 
         /// <summary>
diff --git a/csharp/src/Google.Protobuf/UnknownFieldSet.cs b/csharp/src/Google.Protobuf/UnknownFieldSet.cs
index 9888dd1..b97eb5e 100644
--- a/csharp/src/Google.Protobuf/UnknownFieldSet.cs
+++ b/csharp/src/Google.Protobuf/UnknownFieldSet.cs
@@ -32,9 +32,7 @@
 
 using System;
 using System.Collections.Generic;
-using System.IO;
 using System.Security;
-using Google.Protobuf.Reflection;
 
 namespace Google.Protobuf
 {
@@ -49,14 +47,13 @@
     /// </summary>
     public sealed partial class UnknownFieldSet
     {
-        private readonly IDictionary<int, UnknownField> fields;
+        private readonly IDictionary<int, UnknownField> fields = new Dictionary<int, UnknownField>();
 
         /// <summary>
         /// Creates a new UnknownFieldSet.
         /// </summary>
         internal UnknownFieldSet()
         {
-            this.fields = new Dictionary<int, UnknownField>();
         }
 
         /// <summary>
@@ -125,8 +122,7 @@
             }
             foreach (KeyValuePair<int, UnknownField> leftEntry in fields)
             {
-                UnknownField rightValue;
-                if (!otherFields.TryGetValue(leftEntry.Key, out rightValue))
+                if (!otherFields.TryGetValue(leftEntry.Key, out UnknownField rightValue))
                 {
                     return false;
                 }
@@ -170,8 +166,7 @@
                 return null;
             }
 
-            UnknownField existing;
-            if (fields.TryGetValue(number, out existing))
+            if (fields.TryGetValue(number, out UnknownField existing))
             {
                 return existing;
             }
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs
index cebbcd2..04b64ab 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs
@@ -112,7 +112,7 @@
             T target = new T();
             if (GetTypeName(TypeUrl) != target.Descriptor.FullName)
             {
-                result = default(T); // Can't use null as there's no class constraint, but this always *will* be null in real usage.
+                result = default; // Can't use null as there's no class constraint, but this always *will* be null in real usage.
                 return false;
             }
             target.MergeFrom(Value);
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs
index 2f5172f..c52147f 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs
@@ -59,8 +59,8 @@
         /// <returns>The difference between the two specified timestamps.</returns>
         public static Duration operator -(Timestamp lhs, Timestamp rhs)
         {
-            ProtoPreconditions.CheckNotNull(lhs, "lhs");
-            ProtoPreconditions.CheckNotNull(rhs, "rhs");
+            ProtoPreconditions.CheckNotNull(lhs, nameof(lhs));
+            ProtoPreconditions.CheckNotNull(rhs, nameof(rhs));
             checked
             {
                 return Duration.Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos);
@@ -75,8 +75,8 @@
         /// <returns>The result of adding the duration to the timestamp.</returns>
         public static Timestamp operator +(Timestamp lhs, Duration rhs)
         {
-            ProtoPreconditions.CheckNotNull(lhs, "lhs");
-            ProtoPreconditions.CheckNotNull(rhs, "rhs");
+            ProtoPreconditions.CheckNotNull(lhs, nameof(lhs));
+            ProtoPreconditions.CheckNotNull(rhs, nameof(rhs));
             checked
             {
                 return Normalize(lhs.Seconds + rhs.Seconds, lhs.Nanos + rhs.Nanos);
@@ -91,8 +91,8 @@
         /// <returns>The result of subtracting the duration from the timestamp.</returns>
         public static Timestamp operator -(Timestamp lhs, Duration rhs)
         {
-            ProtoPreconditions.CheckNotNull(lhs, "lhs");
-            ProtoPreconditions.CheckNotNull(rhs, "rhs");
+            ProtoPreconditions.CheckNotNull(lhs, nameof(lhs));
+            ProtoPreconditions.CheckNotNull(rhs, nameof(rhs));
             checked
             {
                 return Normalize(lhs.Seconds - rhs.Seconds, lhs.Nanos - rhs.Nanos);
@@ -308,7 +308,7 @@
         /// <returns>true if the two timestamps refer to the same nanosecond</returns>
         public static bool operator ==(Timestamp a, Timestamp b)
         {
-            return ReferenceEquals(a, b) || (ReferenceEquals(a, null) ? (ReferenceEquals(b, null) ? true : false) : a.Equals(b));
+            return ReferenceEquals(a, b) || (a is null ? (b is null) : a.Equals(b));
         }
 
         /// <summary>
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/ValuePartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/ValuePartial.cs
index d34b560..a6846e7 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/ValuePartial.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/ValuePartial.cs
@@ -41,7 +41,7 @@
         /// <returns>A newly-created Value message with the given value.</returns>
         public static Value ForString(string value)
         {
-            ProtoPreconditions.CheckNotNull(value, "value");
+            ProtoPreconditions.CheckNotNull(value, nameof(value));
             return new Value { StringValue = value };
         }
 
@@ -81,7 +81,7 @@
         /// <returns>A newly-created Value message an initial list value.</returns>
         public static Value ForList(params Value[] values)
         {
-            ProtoPreconditions.CheckNotNull(values, "values");
+            ProtoPreconditions.CheckNotNull(values, nameof(values));
             return new Value { ListValue = new ListValue { Values = { values } } };
         }
 
@@ -92,7 +92,7 @@
         /// <returns>A newly-created Value message an initial struct value.</returns>
         public static Value ForStruct(Struct value)
         {
-            ProtoPreconditions.CheckNotNull(value, "value");
+            ProtoPreconditions.CheckNotNull(value, nameof(value));
             return new Value { StructValue = value };
         }
     }
diff --git a/csharp/src/Google.Protobuf/WriteBufferHelper.cs b/csharp/src/Google.Protobuf/WriteBufferHelper.cs
index f2a59bc..9230aa6 100644
--- a/csharp/src/Google.Protobuf/WriteBufferHelper.cs
+++ b/csharp/src/Google.Protobuf/WriteBufferHelper.cs
@@ -32,7 +32,6 @@
 
 using System;
 using System.Buffers;
-using System.IO;
 using System.Runtime.CompilerServices;
 using System.Security;
 
diff --git a/csharp/src/Google.Protobuf/WriteContext.cs b/csharp/src/Google.Protobuf/WriteContext.cs
index e822e1d..082c20e 100644
--- a/csharp/src/Google.Protobuf/WriteContext.cs
+++ b/csharp/src/Google.Protobuf/WriteContext.cs
@@ -32,14 +32,8 @@
 
 using System;
 using System.Buffers;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.IO;
 using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 using System.Security;
-using System.Text;
-using Google.Protobuf.Collections;
 
 namespace Google.Protobuf
 {
@@ -101,166 +95,112 @@
         /// Writes a double field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteDouble(double value)
-        {
-            WritingPrimitives.WriteDouble(ref buffer, ref state, value);
-        }
+        public void WriteDouble(double value) => WritingPrimitives.WriteDouble(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a float field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteFloat(float value)
-        {
-            WritingPrimitives.WriteFloat(ref buffer, ref state, value);
-        }
+        public void WriteFloat(float value) => WritingPrimitives.WriteFloat(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a uint64 field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteUInt64(ulong value)
-        {
-            WritingPrimitives.WriteUInt64(ref buffer, ref state, value);
-        }
+        public void WriteUInt64(ulong value) => WritingPrimitives.WriteUInt64(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes an int64 field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteInt64(long value)
-        {
-            WritingPrimitives.WriteInt64(ref buffer, ref state, value);
-        }
+        public void WriteInt64(long value) => WritingPrimitives.WriteInt64(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes an int32 field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteInt32(int value)
-        {
-            WritingPrimitives.WriteInt32(ref buffer, ref state, value);
-        }
+        public void WriteInt32(int value) => WritingPrimitives.WriteInt32(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a fixed64 field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteFixed64(ulong value)
-        {
-            WritingPrimitives.WriteFixed64(ref buffer, ref state, value);
-        }
+        public void WriteFixed64(ulong value) => WritingPrimitives.WriteFixed64(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a fixed32 field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteFixed32(uint value)
-        {
-            WritingPrimitives.WriteFixed32(ref buffer, ref state, value);
-        }
+        public void WriteFixed32(uint value) => WritingPrimitives.WriteFixed32(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a bool field value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteBool(bool value)
-        {
-            WritingPrimitives.WriteBool(ref buffer, ref state, value);
-        }
+        public void WriteBool(bool value) => WritingPrimitives.WriteBool(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a string field value, without a tag.
         /// The data is length-prefixed.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteString(string value)
-        {
-            WritingPrimitives.WriteString(ref buffer, ref state, value);
-        }
+        public void WriteString(string value) => WritingPrimitives.WriteString(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a message, without a tag.
         /// The data is length-prefixed.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteMessage(IMessage value)
-        {
-            WritingPrimitivesMessages.WriteMessage(ref this, value);
-        }
+        public void WriteMessage(IMessage value) => WritingPrimitivesMessages.WriteMessage(ref this, value);
 
         /// <summary>
         /// Writes a group, without a tag, to the stream.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteGroup(IMessage value)
-        {
-            WritingPrimitivesMessages.WriteGroup(ref this, value);
-        }
+        public void WriteGroup(IMessage value) => WritingPrimitivesMessages.WriteGroup(ref this, value);
 
         /// <summary>
         /// Write a byte string, without a tag, to the stream.
         /// The data is length-prefixed.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteBytes(ByteString value)
-        {
-            WritingPrimitives.WriteBytes(ref buffer, ref state, value);
-        }
+        public void WriteBytes(ByteString value) => WritingPrimitives.WriteBytes(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a uint32 value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteUInt32(uint value)
-        {
-            WritingPrimitives.WriteUInt32(ref buffer, ref state, value);
-        }
+        public void WriteUInt32(uint value) => WritingPrimitives.WriteUInt32(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes an enum value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteEnum(int value)
-        {
-            WritingPrimitives.WriteEnum(ref buffer, ref state, value);
-        }
+        public void WriteEnum(int value) => WritingPrimitives.WriteEnum(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes an sfixed32 value, without a tag.
         /// </summary>
         /// <param name="value">The value to write.</param>
-        public void WriteSFixed32(int value)
-        {
-            WritingPrimitives.WriteSFixed32(ref buffer, ref state, value);
-        }
+        public void WriteSFixed32(int value) => WritingPrimitives.WriteSFixed32(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes an sfixed64 value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteSFixed64(long value)
-        {
-            WritingPrimitives.WriteSFixed64(ref buffer, ref state, value);
-        }
+        public void WriteSFixed64(long value) => WritingPrimitives.WriteSFixed64(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes an sint32 value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteSInt32(int value)
-        {
-            WritingPrimitives.WriteSInt32(ref buffer, ref state, value);
-        }
+        public void WriteSInt32(int value) => WritingPrimitives.WriteSInt32(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes an sint64 value, without a tag.
         /// </summary>
         /// <param name="value">The value to write</param>
-        public void WriteSInt64(long value)
-        {
-            WritingPrimitives.WriteSInt64(ref buffer, ref state, value);
-        }
+        public void WriteSInt64(long value) => WritingPrimitives.WriteSInt64(ref buffer, ref state, value);
 
         /// <summary>
         /// Writes a length (in bytes) for length-delimited data.
@@ -269,48 +209,33 @@
         /// This method simply writes a rawint, but exists for clarity in calling code.
         /// </remarks>
         /// <param name="length">Length value, in bytes.</param>
-        public void WriteLength(int length)
-        {
-            WritingPrimitives.WriteLength(ref buffer, ref state, length);
-        }
+        public void WriteLength(int length) => WritingPrimitives.WriteLength(ref buffer, ref state, length);
 
         /// <summary>
         /// Encodes and writes a tag.
         /// </summary>
         /// <param name="fieldNumber">The number of the field to write the tag for</param>
         /// <param name="type">The wire format type of the tag to write</param>
-        public void WriteTag(int fieldNumber, WireFormat.WireType type)
-        {
-            WritingPrimitives.WriteTag(ref buffer, ref state, fieldNumber, type);
-        }
+        public void WriteTag(int fieldNumber, WireFormat.WireType type) => WritingPrimitives.WriteTag(ref buffer, ref state, fieldNumber, type);
 
         /// <summary>
         /// Writes an already-encoded tag.
         /// </summary>
         /// <param name="tag">The encoded tag</param>
-        public void WriteTag(uint tag)
-        {
-            WritingPrimitives.WriteTag(ref buffer, ref state, tag);
-        }
+        public void WriteTag(uint tag) => WritingPrimitives.WriteTag(ref buffer, ref state, tag);
 
         /// <summary>
         /// Writes the given single-byte tag.
         /// </summary>
         /// <param name="b1">The encoded tag</param>
-        public void WriteRawTag(byte b1)
-        {
-            WritingPrimitives.WriteRawTag(ref buffer, ref state, b1);
-        }
+        public void WriteRawTag(byte b1) => WritingPrimitives.WriteRawTag(ref buffer, ref state, b1);
 
         /// <summary>
         /// Writes the given two-byte tag.
         /// </summary>
         /// <param name="b1">The first byte of the encoded tag</param>
         /// <param name="b2">The second byte of the encoded tag</param>
-        public void WriteRawTag(byte b1, byte b2)
-        {
-            WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2);
-        }
+        public void WriteRawTag(byte b1, byte b2) => WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2);
 
         /// <summary>
         /// Writes the given three-byte tag.
@@ -318,10 +243,7 @@
         /// <param name="b1">The first byte of the encoded tag</param>
         /// <param name="b2">The second byte of the encoded tag</param>
         /// <param name="b3">The third byte of the encoded tag</param>
-        public void WriteRawTag(byte b1, byte b2, byte b3)
-        {
-            WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3);
-        }
+        public void WriteRawTag(byte b1, byte b2, byte b3) => WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3);
 
         /// <summary>
         /// Writes the given four-byte tag.
@@ -330,10 +252,7 @@
         /// <param name="b2">The second byte of the encoded tag</param>
         /// <param name="b3">The third byte of the encoded tag</param>
         /// <param name="b4">The fourth byte of the encoded tag</param>
-        public void WriteRawTag(byte b1, byte b2, byte b3, byte b4)
-        {
-            WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3, b4);
-        }
+        public void WriteRawTag(byte b1, byte b2, byte b3, byte b4) => WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3, b4);
 
         /// <summary>
         /// Writes the given five-byte tag.
@@ -343,20 +262,11 @@
         /// <param name="b3">The third byte of the encoded tag</param>
         /// <param name="b4">The fourth byte of the encoded tag</param>
         /// <param name="b5">The fifth byte of the encoded tag</param>
-        public void WriteRawTag(byte b1, byte b2, byte b3, byte b4, byte b5)
-        {
-            WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3, b4, b5);
-        }
+        public void WriteRawTag(byte b1, byte b2, byte b3, byte b4, byte b5) => WritingPrimitives.WriteRawTag(ref buffer, ref state, b1, b2, b3, b4, b5);
 
-        internal void Flush()
-        {
-            WriteBufferHelper.Flush(ref buffer, ref state);
-        }
+        internal void Flush() => WriteBufferHelper.Flush(ref buffer, ref state);
 
-        internal void CheckNoSpaceLeft()
-        {
-            WriteBufferHelper.CheckNoSpaceLeft(ref state);
-        }
+        internal void CheckNoSpaceLeft() => WriteBufferHelper.CheckNoSpaceLeft(ref state);
 
         internal void CopyStateTo(CodedOutputStream output)
         {
diff --git a/csharp/src/Google.Protobuf/WriterInternalState.cs b/csharp/src/Google.Protobuf/WriterInternalState.cs
index a779305..ea4515a 100644
--- a/csharp/src/Google.Protobuf/WriterInternalState.cs
+++ b/csharp/src/Google.Protobuf/WriterInternalState.cs
@@ -30,20 +30,8 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using System;
-using System.Buffers;
-using System.Buffers.Binary;
-using System.Collections.Generic;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Text;
-using Google.Protobuf.Collections;
-
 namespace Google.Protobuf
 {
-    
     // warning: this is a mutable struct, so it needs to be only passed as a ref!
     internal struct WriterInternalState
     {
diff --git a/csharp/src/Google.Protobuf/WritingPrimitives.cs b/csharp/src/Google.Protobuf/WritingPrimitives.cs
index 8beefc5..cfba5c7 100644
--- a/csharp/src/Google.Protobuf/WritingPrimitives.cs
+++ b/csharp/src/Google.Protobuf/WritingPrimitives.cs
@@ -32,7 +32,6 @@
 
 using System;
 using System.Buffers.Binary;
-using System.Diagnostics;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 #if GOOGLE_PROTOBUF_SIMD
diff --git a/csharp/src/Google.Protobuf/WritingPrimitivesMessages.cs b/csharp/src/Google.Protobuf/WritingPrimitivesMessages.cs
index cd2d437..6e70ee2 100644
--- a/csharp/src/Google.Protobuf/WritingPrimitivesMessages.cs
+++ b/csharp/src/Google.Protobuf/WritingPrimitivesMessages.cs
@@ -30,9 +30,7 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
-using System;
 using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices.ComTypes;
 using System.Security;
 
 namespace Google.Protobuf