Эх сурвалжийг харах

make all writing use WriteContext

Jan Tattermusch 5 жил өмнө
parent
commit
ca7bc464a9

+ 24 - 2
csharp/src/Google.Protobuf.Test/FieldCodecTest.cs

@@ -124,7 +124,18 @@ namespace Google.Protobuf
             {
                 var stream = new MemoryStream();
                 var codedOutput = new CodedOutputStream(stream);
-                codec.ValueWriter(codedOutput, sampleValue);
+
+                // TODO: simplify
+                WriteContext.Initialize(codedOutput, out WriteContext ctx);
+                try
+                {
+                    codec.ValueWriter(ref ctx, sampleValue);
+                }
+                finally
+                {
+                    ctx.CopyStateTo(codedOutput);
+                }
+                
                 codedOutput.Flush();
                 stream.Position = 0;
                 var codedInput = new CodedInputStream(stream);
@@ -175,7 +186,18 @@ namespace Google.Protobuf
                 if (codec.DefaultValue != null) // This part isn't appropriate for message types.
                 {
                     codedOutput = new CodedOutputStream(stream);
-                    codec.ValueWriter(codedOutput, codec.DefaultValue);
+
+                    // TODO: simplify
+                    WriteContext.Initialize(codedOutput, out WriteContext ctx);
+                    try
+                    {
+                        codec.ValueWriter(ref ctx, codec.DefaultValue);
+                    }
+                    finally
+                    {
+                        ctx.CopyStateTo(codedOutput);
+                    }
+                    
                     codedOutput.Flush();
                     Assert.AreNotEqual(0, stream.Position);
                     Assert.AreEqual(stream.Position, codec.ValueSizeCalculator(codec.DefaultValue));

+ 31 - 2
csharp/src/Google.Protobuf/Collections/MapField.cs

@@ -464,14 +464,43 @@ namespace Google.Protobuf.Collections
         /// <param name="output">The output stream to write to.</param>
         /// <param name="codec">The codec to use for each entry.</param>
         public void WriteTo(CodedOutputStream output, Codec codec)
+        {
+            WriteContext.Initialize(output, out WriteContext ctx);
+            try
+            {
+                WriteTo(ref ctx, codec);
+            }
+            finally
+            {
+                ctx.CopyStateTo(output);
+            }
+
+            //var message = new Codec.MessageAdapter(codec);
+            //foreach (var entry in list)
+            //{
+            //    message.Key = entry.Key;
+            //    message.Value = entry.Value;
+            //    output.WriteTag(codec.MapTag);
+            //    output.WriteMessage(message);
+            //}
+        }
+
+        /// <summary>
+        /// Writes the contents of this map to the given write context, using the specified codec
+        /// to encode each entry.
+        /// </summary>
+        /// <param name="ctx">The write context to write to.</param>
+        /// <param name="codec">The codec to use for each entry.</param>
+        [SecuritySafeCritical]
+        public void WriteTo(ref WriteContext ctx, Codec codec)
         {
             var message = new Codec.MessageAdapter(codec);
             foreach (var entry in list)
             {
                 message.Key = entry.Key;
                 message.Value = entry.Value;
-                output.WriteTag(codec.MapTag);
-                output.WriteMessage(message);
+                ctx.WriteTag(codec.MapTag);
+                ctx.WriteMessage(message);
             }
         }
 

+ 59 - 7
csharp/src/Google.Protobuf/Collections/RepeatedField.cs

@@ -148,7 +148,7 @@ namespace Google.Protobuf.Collections
         /// Calculates the size of this collection based on the given codec.
         /// </summary>
         /// <param name="codec">The codec to use when encoding each field.</param>
-        /// <returns>The number of bytes that would be written to a <see cref="CodedOutputStream"/> by <see cref="WriteTo"/>,
+        /// <returns>The number of bytes that would be written to an output by one of the <c>WriteTo</c> methods,
         /// using the same codec.</returns>
         public int CalculateSize(FieldCodec<T> codec)
         {
@@ -206,6 +206,58 @@ namespace Google.Protobuf.Collections
         /// <param name="output">The output stream to write to.</param>
         /// <param name="codec">The codec to use when encoding each value.</param>
         public void WriteTo(CodedOutputStream output, FieldCodec<T> codec)
+        {
+            WriteContext.Initialize(output, out WriteContext ctx);
+            try
+            {
+                WriteTo(ref ctx, codec);
+            }
+            finally
+            {
+                ctx.CopyStateTo(output);
+            }
+
+            //if (count == 0)
+            //{
+            //    return;
+            //}
+            //var writer = codec.ValueWriter;
+            //var tag = codec.Tag;
+            //if (codec.PackedRepeatedField)
+            //{
+            //    // Packed primitive type
+            //    int size = CalculatePackedDataSize(codec);
+            //    output.WriteTag(tag);
+            //    output.WriteLength(size);
+            //    for (int i = 0; i < count; i++)
+            //    {
+            //        writer(output, array[i]);
+            //    }
+            //}
+            //else
+            //{
+            //    // Not packed: a simple tag/value pair for each value.
+            //    // Can't use codec.WriteTagAndValue, as that omits default values.
+            //    for (int i = 0; i < count; i++)
+            //    {
+            //        output.WriteTag(tag);
+            //        writer(output, array[i]);
+            //        if (codec.EndTag != 0)
+            //        {
+            //            output.WriteTag(codec.EndTag);
+            //        }
+            //    }
+            //}
+        }
+
+        /// <summary>
+        /// Writes the contents of this collection to the given write context,
+        /// encoding each value using the specified codec.
+        /// </summary>
+        /// <param name="ctx">The write context to write to.</param>
+        /// <param name="codec">The codec to use when encoding each value.</param>
+        [SecuritySafeCritical]
+        public void WriteTo(ref WriteContext ctx, FieldCodec<T> codec)
         {
             if (count == 0)
             {
@@ -217,11 +269,11 @@ namespace Google.Protobuf.Collections
             {
                 // Packed primitive type
                 int size = CalculatePackedDataSize(codec);
-                output.WriteTag(tag);
-                output.WriteLength(size);
+                ctx.WriteTag(tag);
+                ctx.WriteLength(size);
                 for (int i = 0; i < count; i++)
                 {
-                    writer(output, array[i]);
+                    writer(ref ctx, array[i]);
                 }
             }
             else
@@ -230,11 +282,11 @@ namespace Google.Protobuf.Collections
                 // Can't use codec.WriteTagAndValue, as that omits default values.
                 for (int i = 0; i < count; i++)
                 {
-                    output.WriteTag(tag);
-                    writer(output, array[i]);
+                    ctx.WriteTag(tag);
+                    writer(ref ctx, array[i]);
                     if (codec.EndTag != 0)
                     {
-                        output.WriteTag(codec.EndTag);
+                        ctx.WriteTag(codec.EndTag);
                     }
                 }
             }

+ 25 - 1
csharp/src/Google.Protobuf/ExtensionSet.cs

@@ -34,6 +34,7 @@ using Google.Protobuf.Collections;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Security;
 
 namespace Google.Protobuf
 {
@@ -343,10 +344,33 @@ namespace Google.Protobuf
         /// Writes the extension values in this set to the output stream
         /// </summary>
         public void WriteTo(CodedOutputStream stream)
+        {
+            
+            WriteContext.Initialize(stream, out WriteContext ctx);
+            try
+            {
+                WriteTo(ref ctx);
+            }
+            finally
+            {
+                ctx.CopyStateTo(stream);
+            }
+
+            // foreach (var value in ValuesByNumber.Values)
+            // {
+            //     value.WriteTo(stream);
+            // }
+        }
+
+        /// <summary>
+        /// Writes the extension values in this set to the write context
+        /// </summary>
+        [SecuritySafeCritical]
+        public void WriteTo(ref WriteContext ctx)
         {
             foreach (var value in ValuesByNumber.Values)
             {
-                value.WriteTo(stream);
+                value.WriteTo(ref ctx);
             }
         }
 

+ 11 - 11
csharp/src/Google.Protobuf/ExtensionValue.cs

@@ -41,7 +41,7 @@ namespace Google.Protobuf
         void MergeFrom(ref ParseContext ctx);
 
         void MergeFrom(IExtensionValue value);
-        void WriteTo(CodedOutputStream output);
+        void WriteTo(ref WriteContext ctx);
         int CalculateSize();
         bool IsInitialized();
     }
@@ -106,13 +106,13 @@ namespace Google.Protobuf
             }
         }
 
-        public void WriteTo(CodedOutputStream output)
+        public void WriteTo(ref WriteContext ctx)
         {
-            output.WriteTag(codec.Tag);
-            codec.ValueWriter(output, field);
+            ctx.WriteTag(codec.Tag);
+            codec.ValueWriter(ref ctx, field);
             if (codec.EndTag != 0)
             {
-                output.WriteTag(codec.EndTag);
+                ctx.WriteTag(codec.EndTag);
             }
         }
 
@@ -181,10 +181,10 @@ namespace Google.Protobuf
             }
         }
 
-        public void MergeFrom(CodedInputStream input)
-        {
-            field.AddEntriesFrom(input, codec);
-        }
+        //public void MergeFrom(CodedInputStream input)
+        //{
+        //    field.AddEntriesFrom(input, codec);
+        //}
 
         public void MergeFrom(ref ParseContext ctx)
         {
@@ -199,9 +199,9 @@ namespace Google.Protobuf
             }
         }
 
-        public void WriteTo(CodedOutputStream output)
+        public void WriteTo(ref WriteContext ctx)
         {
-            field.WriteTo(output, codec);
+            field.WriteTo(ref ctx, codec);
         }
 
         public RepeatedField<T> GetValue() => field;

+ 59 - 31
csharp/src/Google.Protobuf/FieldCodec.cs

@@ -219,7 +219,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<string> ForString(uint tag, string defaultValue)
         {
-            return new FieldCodec<string>((ref ParseContext ctx) => ctx.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag, defaultValue);
+            return new FieldCodec<string>((ref ParseContext ctx) => ctx.ReadString(), (ref WriteContext ctx, string value) => ctx.WriteString(value), CodedOutputStream.ComputeStringSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -230,7 +230,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<ByteString> ForBytes(uint tag, ByteString defaultValue)
         {
-            return new FieldCodec<ByteString>((ref ParseContext ctx) => ctx.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag, defaultValue);
+            return new FieldCodec<ByteString>((ref ParseContext ctx) => ctx.ReadBytes(), (ref WriteContext ctx, ByteString value) => ctx.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -241,7 +241,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<bool> ForBool(uint tag, bool defaultValue)
         {
-            return new FieldCodec<bool>((ref ParseContext ctx) => ctx.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.BoolSize, tag, defaultValue);
+            return new FieldCodec<bool>((ref ParseContext ctx) => ctx.ReadBool(), (ref WriteContext ctx, bool value) => ctx.WriteBool(value), CodedOutputStream.BoolSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -252,7 +252,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<int> ForInt32(uint tag, int defaultValue)
         {
-            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag, defaultValue);
+            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadInt32(), (ref WriteContext output, int value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -263,7 +263,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<int> ForSInt32(uint tag, int defaultValue)
         {
-            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag, defaultValue);
+            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSInt32(), (ref WriteContext output, int value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -274,7 +274,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<uint> ForFixed32(uint tag, uint defaultValue)
         {
-            return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadFixed32(), (output, value) => output.WriteFixed32(value), 4, tag, defaultValue);
+            return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadFixed32(), (ref WriteContext output, uint value) => output.WriteFixed32(value), 4, tag, defaultValue);
         }
 
         /// <summary>
@@ -285,7 +285,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<int> ForSFixed32(uint tag, int defaultValue)
         {
-            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSFixed32(), (output, value) => output.WriteSFixed32(value), 4, tag, defaultValue);
+            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSFixed32(), (ref WriteContext output, int value) => output.WriteSFixed32(value), 4, tag, defaultValue);
         }
 
         /// <summary>
@@ -296,7 +296,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<uint> ForUInt32(uint tag, uint defaultValue)
         {
-            return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag, defaultValue);
+            return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadUInt32(), (ref WriteContext output, uint value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -307,7 +307,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<long> ForInt64(uint tag, long defaultValue)
         {
-            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag, defaultValue);
+            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadInt64(), (ref WriteContext output, long value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -318,7 +318,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<long> ForSInt64(uint tag, long defaultValue)
         {
-            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag, defaultValue);
+            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSInt64(), (ref WriteContext output, long value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -329,7 +329,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<ulong> ForFixed64(uint tag, ulong defaultValue)
         {
-            return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadFixed64(), (output, value) => output.WriteFixed64(value), 8, tag, defaultValue);
+            return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadFixed64(), (ref WriteContext output, ulong value) => output.WriteFixed64(value), 8, tag, defaultValue);
         }
 
         /// <summary>
@@ -340,7 +340,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<long> ForSFixed64(uint tag, long defaultValue)
         {
-            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSFixed64(), (output, value) => output.WriteSFixed64(value), 8, tag, defaultValue);
+            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSFixed64(), (ref WriteContext output, long value) => output.WriteSFixed64(value), 8, tag, defaultValue);
         }
 
         /// <summary>
@@ -351,7 +351,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<ulong> ForUInt64(uint tag, ulong defaultValue)
         {
-            return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag, defaultValue);
+            return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadUInt64(), (ref WriteContext output, ulong value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -362,7 +362,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<float> ForFloat(uint tag, float defaultValue)
         {
-            return new FieldCodec<float>((ref ParseContext ctx) => ctx.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.FloatSize, tag, defaultValue);
+            return new FieldCodec<float>((ref ParseContext ctx) => ctx.ReadFloat(), (ref WriteContext output, float value) => output.WriteFloat(value), CodedOutputStream.FloatSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -373,7 +373,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<double> ForDouble(uint tag, double defaultValue)
         {
-            return new FieldCodec<double>((ref ParseContext ctx) => ctx.ReadDouble(), (output, value) => output.WriteDouble(value), CodedOutputStream.DoubleSize, tag, defaultValue);
+            return new FieldCodec<double>((ref ParseContext ctx) => ctx.ReadDouble(), (ref WriteContext output, double value) => output.WriteDouble(value), CodedOutputStream.DoubleSize, tag, defaultValue);
         }
 
         // Enums are tricky. We can probably use expression trees to build these delegates automatically,
@@ -391,7 +391,7 @@ namespace Google.Protobuf
         {
             return new FieldCodec<T>((ref ParseContext ctx) => fromInt32(
                 ctx.ReadEnum()),
-                (output, value) => output.WriteEnum(toInt32(value)),
+                (ref WriteContext output, T value) => output.WriteEnum(toInt32(value)),
                 value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag, defaultValue);
         }
 
@@ -410,7 +410,7 @@ namespace Google.Protobuf
                     ctx.ReadMessage(message); 
                     return message; 
                 },
-                (output, value) => output.WriteMessage(value),
+                (ref WriteContext output, T value) => output.WriteMessage(value),
                 (ref ParseContext ctx, ref T v) => 
                 {
                     if (v == null)
@@ -455,7 +455,7 @@ namespace Google.Protobuf
                     ctx.ReadGroup(message);
                     return message;
                 },
-                (output, value) => output.WriteGroup(value), 
+                (ref WriteContext output, T value) => output.WriteGroup(value), 
                 (ref ParseContext ctx, ref T v) => 
                 {
                     if (v == null)
@@ -492,7 +492,7 @@ namespace Google.Protobuf
             var nestedCodec = WrapperCodecs.GetCodec<T>();
             return new FieldCodec<T>(
                 (ref ParseContext ctx) => WrapperCodecs.Read<T>(ref ctx, nestedCodec),
-                (output, value) => WrapperCodecs.Write<T>(output, value, nestedCodec),
+                (ref WriteContext output, T value) => WrapperCodecs.Write<T>(ref output, value, nestedCodec),
                 (ref ParseContext ctx, ref T v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
                 (ref T v, T v2) => { v = v2; return v == null; },
                 value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
@@ -509,7 +509,7 @@ namespace Google.Protobuf
             var nestedCodec = WrapperCodecs.GetCodec<T>();
             return new FieldCodec<T?>(
                 WrapperCodecs.GetReader<T>(),
-                (output, value) => WrapperCodecs.Write<T>(output, value.Value, nestedCodec),
+                (ref WriteContext output, T? value) => WrapperCodecs.Write<T>(ref output, value.Value, nestedCodec),
                 (ref ParseContext ctx, ref T? v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
                 (ref T? v, T? v2) => { if (v2.HasValue) { v = v2; } return v.HasValue; },
                 value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
@@ -616,10 +616,10 @@ namespace Google.Protobuf
                 return value;
             }
 
-            internal static void Write<T>(CodedOutputStream output, T value, FieldCodec<T> codec)
+            internal static void Write<T>(ref WriteContext ctx, T value, FieldCodec<T> codec)
             {
-                output.WriteLength(codec.CalculateSizeWithTag(value));
-                codec.WriteTagAndValue(output, value);
+                ctx.WriteLength(codec.CalculateSizeWithTag(value));
+                codec.WriteTagAndValue(ref ctx, value);
             }
 
             internal  static int CalculateSize<T>(T value, FieldCodec<T> codec)
@@ -631,6 +631,7 @@ namespace Google.Protobuf
     }
 
     internal delegate TValue ValueReader<out TValue>(ref ParseContext ctx);
+    internal delegate void ValueWriter<T>(ref WriteContext ctx, T value);
 
     /// <summary>
     /// <para>
@@ -685,7 +686,7 @@ namespace Google.Protobuf
         /// <summary>
         /// Returns a delegate to write a value (unconditionally) to a coded output stream.
         /// </summary>
-        internal Action<CodedOutputStream, T> ValueWriter { get; }
+        internal ValueWriter<T> ValueWriter { get; }
 
         /// <summary>
         /// Returns the size calculator for just a value.
@@ -744,7 +745,7 @@ namespace Google.Protobuf
 
         internal FieldCodec(
                 ValueReader<T> reader,
-                Action<CodedOutputStream, T> writer,
+                ValueWriter<T> writer,
                 int fixedSize,
                 uint tag,
                 T defaultValue) : this(reader, writer, _ => fixedSize, tag, defaultValue)
@@ -754,7 +755,7 @@ namespace Google.Protobuf
 
         internal FieldCodec(
             ValueReader<T> reader,
-            Action<CodedOutputStream, T> writer,
+            ValueWriter<T> writer,
             Func<T, int> sizeCalculator,
             uint tag,
             T defaultValue) : this(reader, writer, (ref ParseContext ctx, ref T v) => v = reader(ref ctx), (ref T v, T v2) => { v = v2; return true; }, sizeCalculator, tag, 0, defaultValue)
@@ -763,7 +764,7 @@ namespace Google.Protobuf
 
         internal FieldCodec(
             ValueReader<T> reader,
-            Action<CodedOutputStream, T> writer,
+            ValueWriter<T> writer,
             InputMerger inputMerger,
             ValuesMerger valuesMerger,
             Func<T, int> sizeCalculator,
@@ -774,7 +775,7 @@ namespace Google.Protobuf
 
         internal FieldCodec(
             ValueReader<T> reader,
-            Action<CodedOutputStream, T> writer,
+            ValueWriter<T> writer,
             InputMerger inputMerger,
             ValuesMerger valuesMerger,
             Func<T, int> sizeCalculator,
@@ -802,14 +803,41 @@ namespace Google.Protobuf
         /// Write a tag and the given value, *if* the value is not the default.
         /// </summary>
         public void WriteTagAndValue(CodedOutputStream output, T value)
+        {
+            WriteContext.Initialize(output, out WriteContext ctx);
+            try
+            {
+                WriteTagAndValue(ref ctx, value);
+            }
+            finally
+            {
+                ctx.CopyStateTo(output);
+            }
+
+
+            //if (!IsDefault(value))
+            //{
+            //    output.WriteTag(Tag);
+            //    ValueWriter(output, value);
+            //    if (EndTag != 0)
+            //    {
+            //        output.WriteTag(EndTag);
+            //    }
+            //}
+        }
+
+        /// <summary>
+        /// Write a tag and the given value, *if* the value is not the default.
+        /// </summary>
+        public void WriteTagAndValue(ref WriteContext ctx, T value)
         {
             if (!IsDefault(value))
             {
-                output.WriteTag(Tag);
-                ValueWriter(output, value);
+                ctx.WriteTag(Tag);
+                ValueWriter(ref ctx, value);
                 if (EndTag != 0)
                 {
-                    output.WriteTag(EndTag);
+                    ctx.WriteTag(EndTag);
                 }
             }
         }

+ 3 - 3
csharp/src/Google.Protobuf/UnknownField.cs

@@ -101,8 +101,8 @@ namespace Google.Protobuf
         /// <paramref name="output"/>
         /// </summary>
         /// <param name="fieldNumber">The unknown field number.</param>
-        /// <param name="output">The CodedOutputStream to write to.</param>
-        internal void WriteTo(int fieldNumber, CodedOutputStream output)
+        /// <param name="output">The write context to write to.</param>
+        internal void WriteTo(int fieldNumber, ref WriteContext output)
         {
             if (varintList != null)
             {
@@ -141,7 +141,7 @@ namespace Google.Protobuf
                 foreach (UnknownFieldSet value in groupList)
                 {
                     output.WriteTag(fieldNumber, WireFormat.WireType.StartGroup);
-                    value.WriteTo(output);
+                    value.WriteTo(ref output);
                     output.WriteTag(fieldNumber, WireFormat.WireType.EndGroup);
                 }
             }

+ 23 - 1
csharp/src/Google.Protobuf/UnknownFieldSet.cs

@@ -71,10 +71,32 @@ namespace Google.Protobuf
         /// Serializes the set and writes it to <paramref name="output"/>.
         /// </summary>
         public void WriteTo(CodedOutputStream output)
+        {
+            WriteContext.Initialize(output, out WriteContext ctx);
+            try
+            {
+                WriteTo(ref ctx);
+            }
+            finally
+            {
+                ctx.CopyStateTo(output);
+            }
+
+            //foreach (KeyValuePair<int, UnknownField> entry in fields)
+            //{
+            //    entry.Value.WriteTo(entry.Key, output);
+            //}
+        }
+
+        /// <summary>
+        /// Serializes the set and writes it to <paramref name="ctx"/>.
+        /// </summary>
+        [SecuritySafeCritical]
+        public void WriteTo(ref WriteContext ctx)
         {
             foreach (KeyValuePair<int, UnknownField> entry in fields)
             {
-                entry.Value.WriteTo(entry.Key, output);
+                entry.Value.WriteTo(entry.Key, ref ctx);
             }
         }
 

+ 252 - 1
csharp/src/Google.Protobuf/WriteContext.cs

@@ -85,7 +85,258 @@ namespace Google.Protobuf
             WriteBufferHelper.Initialize(output, out ctx.state.writeBufferHelper, out ctx.buffer);
             ctx.state.limit = ctx.buffer.Length;
             ctx.state.position = 0;
-        }  
+        }
+
+        /// <summary>
+        /// 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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <summary>
+        /// Writes a length (in bytes) for length-delimited data.
+        /// </summary>
+        /// <remarks>
+        /// 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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <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);
+        }
+
+        /// <summary>
+        /// Writes the given three-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>
+        /// <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);
+        }
+
+        /// <summary>
+        /// Writes the given four-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>
+        /// <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);
+        }
+
+        /// <summary>
+        /// Writes the given five-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>
+        /// <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);
+        }
 
         internal void CopyStateTo(CodedOutputStream output)
         {