optimize initialization of ParseContext
diff --git a/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs b/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs
index 3cd70e3..30f3e9e 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/ParseMessagesBenchmark.cs
@@ -195,7 +195,7 @@
 
             public void ParseDelimitedMessagesFromReadOnlySequence(int messageCount)
             {
-                var ctx = new ParseContext(multipleMessagesDataSequence);
+                ParseContext.Initialize(multipleMessagesDataSequence, out ParseContext ctx);
                 for (int i = 0; i < messageCount; i++)
                 {
                     var msg = factory();
diff --git a/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs b/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
index 28c1be5..8d3e13a 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
@@ -105,7 +105,7 @@
         [Arguments(5)]
         public int ParseRawVarint32_ParseContext(int encodedSize)
         {
-            var ctx = CreateParseContext(varintInputBuffers[encodedSize]);
+            InitializeParseContext(varintInputBuffers[encodedSize], out ParseContext ctx);
             int sum = 0;
             for (int i = 0; i < BytesToParse / encodedSize; i++)
             {
@@ -149,7 +149,7 @@
         [Arguments(10)]
         public long ParseRawVarint64_ParseContext(int encodedSize)
         {
-            var ctx = CreateParseContext(varintInputBuffers[encodedSize]);
+            InitializeParseContext(varintInputBuffers[encodedSize], out ParseContext ctx);
             long sum = 0;
             for (int i = 0; i < BytesToParse / encodedSize; i++)
             {
@@ -175,7 +175,7 @@
         public uint ParseFixed32_ParseContext()
         {
             const int encodedSize = sizeof(uint);
-            var ctx = CreateParseContext(fixedIntInputBuffer);
+            InitializeParseContext(fixedIntInputBuffer, out ParseContext ctx);
             uint sum = 0;
             for (uint i = 0; i < BytesToParse / encodedSize; i++)
             {
@@ -201,7 +201,7 @@
         public ulong ParseFixed64_ParseContext()
         {
             const int encodedSize = sizeof(ulong);
-            var ctx = CreateParseContext(fixedIntInputBuffer);
+            InitializeParseContext(fixedIntInputBuffer, out ParseContext ctx);
             ulong sum = 0;
             for (int i = 0; i < BytesToParse / encodedSize; i++)
             {
@@ -227,7 +227,7 @@
         public float ParseRawFloat_ParseContext()
         {
             const int encodedSize = sizeof(float);
-            var ctx = CreateParseContext(floatInputBuffer);
+            InitializeParseContext(floatInputBuffer, out ParseContext ctx);
             float sum = 0;
             for (int i = 0; i < BytesToParse / encodedSize; i++)
             {
@@ -253,7 +253,7 @@
         public double ParseRawDouble_ParseContext()
         {
             const int encodedSize = sizeof(double);
-            var ctx = CreateParseContext(doubleInputBuffer);
+            InitializeParseContext(doubleInputBuffer, out ParseContext ctx);
             double sum = 0;
             for (int i = 0; i < BytesToParse / encodedSize; i++)
             {
@@ -262,9 +262,9 @@
             return sum;
         }
 
-        private static ParseContext CreateParseContext(byte[] buffer)
+        private static void InitializeParseContext(byte[] buffer, out ParseContext ctx)
         {
-            return new ParseContext(new ReadOnlySequence<byte>(buffer));
+            ParseContext.Initialize(new ReadOnlySequence<byte>(buffer), out ctx);
         }
 
         private static byte[] CreateBufferWithRandomVarints(Random random, int valueCount, int encodedSize, int paddingValueCount)
diff --git a/csharp/src/Google.Protobuf/CodedInputStream.cs b/csharp/src/Google.Protobuf/CodedInputStream.cs
index 4c9234d..dc36199 100644
--- a/csharp/src/Google.Protobuf/CodedInputStream.cs
+++ b/csharp/src/Google.Protobuf/CodedInputStream.cs
@@ -431,7 +431,7 @@
         public void ReadMessage(IMessage builder)

         {

             var span = new ReadOnlySpan<byte>(buffer);

-            var ctx = new ParseContext(ref span, ref state);

+            ParseContext.Initialize(ref span, ref state, out ParseContext ctx);

             try

             {

                 ParsingPrimitivesMessages.ReadMessage(ref ctx, builder);

@@ -447,7 +447,7 @@
         /// </summary>

         public void ReadGroup(IMessage builder)

         {

-            var ctx = new ParseContext(this);

+            ParseContext.Initialize(this, out ParseContext ctx);

             try

             {

                 ParsingPrimitivesMessages.ReadGroup(ref ctx, builder);

@@ -691,7 +691,7 @@
         /// </summary>

         public void ReadRawMessage(IMessage message)

         {

-            var ctx = new ParseContext(this);

+            ParseContext.Initialize(this, out ParseContext ctx);

             try

             {

                 ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);

diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs
index 3ea2a68..a2604f1 100644
--- a/csharp/src/Google.Protobuf/Collections/MapField.cs
+++ b/csharp/src/Google.Protobuf/Collections/MapField.cs
@@ -713,7 +713,7 @@
                     // Read it as if we'd seen input with no data (i.e. create a "default" message).

                     if (Value == null)

                     {

-                        var zeroLengthCtx = new ParseContext(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData));

+                        ParseContext.Initialize(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx);

                         Value = codec.valueCodec.Read(ref zeroLengthCtx);

                     }

                 }

diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
index c1b34ea..8cc3536 100644
--- a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
+++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
@@ -95,7 +95,7 @@
         /// <param name="codec">The codec to use in order to read each entry.</param>

         public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)

         {

-            var ctx = new ParseContext(input);

+            ParseContext.Initialize(input, out ParseContext ctx);

             try

             {

                 AddEntriesFrom(ref ctx, codec);

diff --git a/csharp/src/Google.Protobuf/ExtensionValue.cs b/csharp/src/Google.Protobuf/ExtensionValue.cs
index 497ae4f..7cd0c55 100644
--- a/csharp/src/Google.Protobuf/ExtensionValue.cs
+++ b/csharp/src/Google.Protobuf/ExtensionValue.cs
@@ -96,7 +96,7 @@
 
         public void MergeFrom(CodedInputStream input)
         {
-            var ctx = new ParseContext(input);
+            ParseContext.Initialize(input, out ParseContext ctx);
             try
             {
                 codec.ValueMerger(ref ctx, ref field);
diff --git a/csharp/src/Google.Protobuf/FieldCodec.cs b/csharp/src/Google.Protobuf/FieldCodec.cs
index 50cf8f3..1a71c5c 100644
--- a/csharp/src/Google.Protobuf/FieldCodec.cs
+++ b/csharp/src/Google.Protobuf/FieldCodec.cs
@@ -844,7 +844,7 @@
         /// <returns>The value read from the stream.</returns>
         public T Read(CodedInputStream input)
         {
-            var ctx = new ParseContext(input);
+            ParseContext.Initialize(input, out ParseContext ctx);
             try
             {
                 return ValueReader(ref ctx);
diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs
index 97a58c7..51e4091 100644
--- a/csharp/src/Google.Protobuf/MessageExtensions.cs
+++ b/csharp/src/Google.Protobuf/MessageExtensions.cs
@@ -253,7 +253,7 @@
         [SecuritySafeCritical]
         internal static void MergeFrom(this IMessage message, ReadOnlySequence<byte> data, bool discardUnknownFields, ExtensionRegistry registry)
         {
-            var ctx = new ParseContext(data);
+            ParseContext.Initialize(data, out ParseContext ctx);
             ctx.DiscardUnknownFields = discardUnknownFields;
             ctx.ExtensionRegistry = registry;
             ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message);
diff --git a/csharp/src/Google.Protobuf/ParseContext.cs b/csharp/src/Google.Protobuf/ParseContext.cs
index 6c74e73..61af8ea 100644
--- a/csharp/src/Google.Protobuf/ParseContext.cs
+++ b/csharp/src/Google.Protobuf/ParseContext.cs
@@ -58,10 +58,11 @@
         internal ReadOnlySpan<byte> buffer;
         internal ParserInternalState state;
 
-        internal ParseContext(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void Initialize(ref ReadOnlySpan<byte> buffer, ref ParserInternalState state, out ParseContext ctx)
         {
-            this.buffer = buffer;
-            this.state = state;
+            ctx.buffer = buffer;
+            ctx.state = state;
         }
 
         /// <summary>
@@ -69,33 +70,37 @@
         /// WARNING: internally this copies the CodedInputStream's state, so after done with the ParseContext,
         /// the CodedInputStream's state needs to be updated.
         /// </summary>
-        internal ParseContext(CodedInputStream input)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void Initialize(CodedInputStream input, out ParseContext ctx)
         {
-            this.buffer = new ReadOnlySpan<byte>(input.InternalBuffer);
+            ctx.buffer = new ReadOnlySpan<byte>(input.InternalBuffer);
             // TODO: ideally we would use a reference to the original state, but that doesn't seem possible
-            this.state = input.InternalState;  // creates copy of the state
+            ctx.state = input.InternalState;  // creates copy of the state
         }
 
-        internal ParseContext(ReadOnlySequence<byte> input) : this(input, DefaultRecursionLimit)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void Initialize(ReadOnlySequence<byte> input, out ParseContext ctx)
         {
+            Initialize(input, DefaultRecursionLimit, out ctx);
         }
 
-        internal ParseContext(ReadOnlySequence<byte> input, int recursionLimit)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static void Initialize(ReadOnlySequence<byte> input, int recursionLimit, out ParseContext ctx)
         {
-            this.buffer = default;
-            this.state = default;
-            this.state.lastTag = 0;
-            this.state.recursionDepth = 0;
-            this.state.sizeLimit = DefaultSizeLimit;
-            this.state.recursionLimit = recursionLimit;
-            this.state.currentLimit = int.MaxValue;
-            SegmentedBufferHelper.Initialize(input, out this.state.segmentedBufferHelper, out this.buffer);
-            this.state.bufferPos = 0;
-            this.state.bufferSize = this.buffer.Length;
-            this.state.codedInputStream = null;
+            ctx.buffer = default;
+            ctx.state = default;
+            ctx.state.lastTag = 0;
+            ctx.state.recursionDepth = 0;
+            ctx.state.sizeLimit = DefaultSizeLimit;
+            ctx.state.recursionLimit = recursionLimit;
+            ctx.state.currentLimit = int.MaxValue;
+            SegmentedBufferHelper.Initialize(input, out ctx.state.segmentedBufferHelper, out ctx.buffer);
+            ctx.state.bufferPos = 0;
+            ctx.state.bufferSize = ctx.buffer.Length;
+            ctx.state.codedInputStream = null;
 
-            this.state.DiscardUnknownFields = false;
-            this.state.ExtensionRegistry = null;
+            ctx.state.DiscardUnknownFields = false;
+            ctx.state.ExtensionRegistry = null;
         }
 
         /// <summary>
diff --git a/csharp/src/Google.Protobuf/UnknownFieldSet.cs b/csharp/src/Google.Protobuf/UnknownFieldSet.cs
index fbdf304..b2f288a 100644
--- a/csharp/src/Google.Protobuf/UnknownFieldSet.cs
+++ b/csharp/src/Google.Protobuf/UnknownFieldSet.cs
@@ -258,7 +258,7 @@
         public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields,
                                                      CodedInputStream input)
         {
-            var ctx = new ParseContext(input);
+            ParseContext.Initialize(input, out ParseContext ctx);
             try
             {
                 return MergeFieldFrom(unknownFields, ref ctx);