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

First pass at wrapper types.

- We do still generate the message types, as otherwise reflection breaks, even though it doesn't actually use those types.
- JSON handling hasn't been implemented yet
Jon Skeet 10 жил өмнө
parent
commit
8a0312b201

+ 2 - 0
csharp/src/ProtocolBuffers.Test/ByteStringTest.cs

@@ -50,6 +50,7 @@ namespace Google.Protobuf
             EqualityTester.AssertInequality(b1, b3);
             EqualityTester.AssertInequality(b1, b4);
             EqualityTester.AssertInequality(b1, null);
+#pragma warning disable 1718 // Deliberately calling ==(b1, b1) and !=(b1, b1)
             Assert.IsTrue(b1 == b1);
             Assert.IsTrue(b1 == b2);
             Assert.IsFalse(b1 == b3);
@@ -58,6 +59,7 @@ namespace Google.Protobuf
             Assert.IsTrue((ByteString) null == null);
             Assert.IsFalse(b1 != b1);
             Assert.IsFalse(b1 != b2);
+#pragma warning disable 1718
             Assert.IsTrue(b1 != b3);
             Assert.IsTrue(b1 != b4);
             Assert.IsTrue(b1 != null);

+ 45 - 40
csharp/src/ProtocolBuffers.Test/Collections/MapFieldTest.cs

@@ -103,32 +103,35 @@ namespace Google.Protobuf.Collections
         }
 
         [Test]
-        public void Add_ForbidsNullKeys()
+        public void NullValues()
         {
-            var map = new MapField<string, ForeignMessage>();
-            Assert.Throws<ArgumentNullException>(() => map.Add(null, new ForeignMessage()));
+            TestNullValues<int?>(0);
+            TestNullValues("");
+            TestNullValues(new TestAllTypes());
         }
 
-        [Test]
-        public void Add_AcceptsNullMessageValues()
+        private void TestNullValues<T>(T nonNullValue)
         {
-            var map = new MapField<string, ForeignMessage>();
-            map.Add("missing", null);
-            Assert.IsNull(map["missing"]);
-        }
+            var map = new MapField<int, T>(false);
+            var nullValue = (T) (object) null;
+            Assert.Throws<ArgumentNullException>(() => map.Add(0, nullValue));
+            Assert.Throws<ArgumentNullException>(() => map[0] = nullValue);
+            map.Add(1, nonNullValue);
+            map[1] = nonNullValue;
 
-        [Test]
-        public void Add_ForbidsNullStringValues()
-        {
-            var map = new MapField<string, string>();
-            Assert.Throws<ArgumentNullException>(() => map.Add("missing", null));
+            // Doesn't throw...
+            map = new MapField<int, T>(true);
+            map.Add(0, nullValue);
+            map[0] = nullValue;
+            map.Add(1, nonNullValue);
+            map[1] = nonNullValue;
         }
 
         [Test]
-        public void Add_ForbidsNullByteStringValues()
+        public void Add_ForbidsNullKeys()
         {
-            var map = new MapField<string, ByteString>();
-            Assert.Throws<ArgumentNullException>(() => map.Add("missing", null));
+            var map = new MapField<string, ForeignMessage>();
+            Assert.Throws<ArgumentNullException>(() => map.Add(null, new ForeignMessage()));
         }
 
         [Test]
@@ -137,29 +140,7 @@ namespace Google.Protobuf.Collections
             var map = new MapField<string, ForeignMessage>();
             Assert.Throws<ArgumentNullException>(() => map[null] = new ForeignMessage());
         }
-
-        [Test]
-        public void Indexer_AcceptsNullMessageValues()
-        {
-            var map = new MapField<string, ForeignMessage>();
-            map["missing"] = null;
-            Assert.IsNull(map["missing"]);
-        }
-
-        [Test]
-        public void Indexer_ForbidsNullStringValues()
-        {
-            var map = new MapField<string, string>();
-            Assert.Throws<ArgumentNullException>(() => map["missing"] = null);
-        }
-
-        [Test]
-        public void Indexer_ForbidsNullByteStringValues()
-        {
-            var map = new MapField<string, ByteString>();
-            Assert.Throws<ArgumentNullException>(() => map["missing"] = null);
-        }
-
+        
         [Test]
         public void AddPreservesInsertionOrder()
         {
@@ -528,6 +509,30 @@ namespace Google.Protobuf.Collections
             Assert.Throws<NotSupportedException>(() => dictionary["a"] = "c");
         }
 
+        [Test]
+        public void AllowNullValues_Property()
+        {
+            // Non-message reference type values are non-nullable by default, but can be overridden
+            Assert.IsFalse(new MapField<int, string>().AllowsNullValues);
+            Assert.IsFalse(new MapField<int, string>(false).AllowsNullValues);
+            Assert.IsTrue(new MapField<int, string>(true).AllowsNullValues);
+
+            // Non-nullable value type values are never nullable
+            Assert.IsFalse(new MapField<int, int>().AllowsNullValues);
+            Assert.IsFalse(new MapField<int, int>(false).AllowsNullValues);
+            Assert.Throws<ArgumentException>(() => new MapField<int, int>(true));
+
+            // Message type values are nullable by default, but can be overridden
+            Assert.IsTrue(new MapField<int, TestAllTypes>().AllowsNullValues);
+            Assert.IsFalse(new MapField<int, TestAllTypes>(false).AllowsNullValues);
+            Assert.IsTrue(new MapField<int, TestAllTypes>(true).AllowsNullValues);
+
+            // Nullable value type values are nullable by default, but can be overridden
+            Assert.IsTrue(new MapField<int, int?>().AllowsNullValues);
+            Assert.IsFalse(new MapField<int, int?>(false).AllowsNullValues);
+            Assert.IsTrue(new MapField<int, int?>(true).AllowsNullValues);
+        }
+
         private static KeyValuePair<TKey, TValue> NewKeyValuePair<TKey, TValue>(TKey key, TValue value)
         {
             return new KeyValuePair<TKey, TValue>(key, value);

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 407 - 235
csharp/src/ProtocolBuffers.Test/TestProtos/UnittestWellKnownTypes.cs


+ 178 - 9
csharp/src/ProtocolBuffers.Test/WellKnownTypes/WrappersTest.cs

@@ -30,8 +30,10 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endregion
 
+using System;
 using Google.Protobuf.TestProtos;
 using NUnit.Framework;
+using System.Collections;
 
 namespace Google.Protobuf.WellKnownTypes
 {
@@ -52,6 +54,36 @@ namespace Google.Protobuf.WellKnownTypes
             Assert.IsNull(message.Uint64Field);
         }
 
+        [Test]
+        public void NonDefaultSingleValues()
+        {
+            var message = new TestWellKnownTypes
+            {
+                StringField = "x",
+                BytesField = ByteString.CopyFrom(1, 2, 3),
+                BoolField = true,
+                FloatField = 12.5f,
+                DoubleField = 12.25d,
+                Int32Field = 1,
+                Int64Field = 2,
+                Uint32Field = 3,
+                Uint64Field = 4
+            };
+
+            var bytes = message.ToByteArray();
+            var parsed = TestWellKnownTypes.Parser.ParseFrom(bytes);
+
+            Assert.AreEqual("x", parsed.StringField);
+            Assert.AreEqual(ByteString.CopyFrom(1, 2, 3), parsed.BytesField);
+            Assert.AreEqual(true, parsed.BoolField);
+            Assert.AreEqual(12.5f, parsed.FloatField);
+            Assert.AreEqual(12.25d, parsed.DoubleField);
+            Assert.AreEqual(1, parsed.Int32Field);
+            Assert.AreEqual(2L, parsed.Int64Field);
+            Assert.AreEqual(3U, parsed.Uint32Field);
+            Assert.AreEqual(4UL, parsed.Uint64Field);
+        }
+
         [Test]
         public void NonNullDefaultIsPreservedThroughSerialization()
         {
@@ -71,15 +103,152 @@ namespace Google.Protobuf.WellKnownTypes
             var bytes = message.ToByteArray();
             var parsed = TestWellKnownTypes.Parser.ParseFrom(bytes);
 
-            Assert.AreEqual("", message.StringField);
-            Assert.AreEqual(ByteString.Empty, message.BytesField);
-            Assert.AreEqual(false, message.BoolField);
-            Assert.AreEqual(0f, message.FloatField);
-            Assert.AreEqual(0d, message.DoubleField);
-            Assert.AreEqual(0, message.Int32Field);
-            Assert.AreEqual(0L, message.Int64Field);
-            Assert.AreEqual(0U, message.Uint32Field);
-            Assert.AreEqual(0UL, message.Uint64Field);
+            Assert.AreEqual("", parsed.StringField);
+            Assert.AreEqual(ByteString.Empty, parsed.BytesField);
+            Assert.AreEqual(false, parsed.BoolField);
+            Assert.AreEqual(0f, parsed.FloatField);
+            Assert.AreEqual(0d, parsed.DoubleField);
+            Assert.AreEqual(0, parsed.Int32Field);
+            Assert.AreEqual(0L, parsed.Int64Field);
+            Assert.AreEqual(0U, parsed.Uint32Field);
+            Assert.AreEqual(0UL, parsed.Uint64Field);
+        }
+
+        [Test]
+        public void RepeatedWrappersProhibitNullItems()
+        {
+            var message = new RepeatedWellKnownTypes();
+            Assert.Throws<ArgumentNullException>(() => message.BoolField.Add((bool?) null));
+            Assert.Throws<ArgumentNullException>(() => message.Int32Field.Add((int?) null));
+            Assert.Throws<ArgumentNullException>(() => message.StringField.Add((string) null));
+            Assert.Throws<ArgumentNullException>(() => message.BytesField.Add((ByteString) null));
+        }
+
+        [Test]
+        public void RepeatedWrappersSerializeDeserialize()
+        {
+            var message = new RepeatedWellKnownTypes
+            {
+                BoolField = { true, false },
+                BytesField = { ByteString.CopyFrom(1, 2, 3), ByteString.CopyFrom(4, 5, 6), ByteString.Empty },
+                DoubleField = { 12.5, -1.5, 0d },
+                FloatField = { 123.25f, -20f, 0f },
+                Int32Field = { int.MaxValue, int.MinValue, 0 },
+                Int64Field = { long.MaxValue, long.MinValue, 0L },                
+                StringField = { "First", "Second", "" },
+                Uint32Field = { uint.MaxValue, uint.MinValue, 0U },
+                Uint64Field = { ulong.MaxValue, ulong.MinValue, 0UL },
+            };
+            var bytes = message.ToByteArray();
+            var parsed = RepeatedWellKnownTypes.Parser.ParseFrom(bytes);
+
+            Assert.AreEqual(message, parsed);
+            // Just to test a single value for sanity...
+            Assert.AreEqual("Second", message.StringField[1]);
+        }
+
+        [Test]
+        public void MapWrappersSerializeDeserialize()
+        {
+            var message = new MapWellKnownTypes
+            {
+                BoolField = { { 10, false }, { 20, true } },
+                BytesField = {
+                    { -1, ByteString.CopyFrom(1, 2, 3) },
+                    { 10, ByteString.CopyFrom(4, 5, 6) },
+                    { 1000, ByteString.Empty },
+                    { 10000, null }
+                },
+                DoubleField = { { 1, 12.5 }, { 10, -1.5 }, { 20, 0d } },
+                FloatField = { { 2, 123.25f }, { 3, -20f }, { 4, 0f } },
+                Int32Field = { { 5, int.MaxValue }, { 6, int.MinValue }, { 7, 0 } },
+                Int64Field = { { 8, long.MaxValue }, { 9, long.MinValue }, { 10, 0L } },
+                StringField = { { 11, "First" }, { 12, "Second" }, { 13, "" }, { 14, null } },
+                Uint32Field = { { 15, uint.MaxValue }, { 16, uint.MinValue }, { 17, 0U } },
+                Uint64Field = { { 18, ulong.MaxValue }, { 19, ulong.MinValue }, { 20, 0UL } },
+            };
+        }
+
+        [Test]
+        public void Reflection_SingleValues()
+        {
+            var message = new TestWellKnownTypes
+            {
+                StringField = "x",
+                BytesField = ByteString.CopyFrom(1, 2, 3),
+                BoolField = true,
+                FloatField = 12.5f,
+                DoubleField = 12.25d,
+                Int32Field = 1,
+                Int64Field = 2,
+                Uint32Field = 3,
+                Uint64Field = 4
+            };
+            var fields = ((IReflectedMessage) message).Fields;
+
+            Assert.AreEqual("x", fields[TestWellKnownTypes.StringFieldFieldNumber].GetValue(message));
+            Assert.AreEqual(ByteString.CopyFrom(1, 2, 3), fields[TestWellKnownTypes.BytesFieldFieldNumber].GetValue(message));
+            Assert.AreEqual(true, fields[TestWellKnownTypes.BoolFieldFieldNumber].GetValue(message));
+            Assert.AreEqual(12.5f, fields[TestWellKnownTypes.FloatFieldFieldNumber].GetValue(message));
+            Assert.AreEqual(12.25d, fields[TestWellKnownTypes.DoubleFieldFieldNumber].GetValue(message));
+            Assert.AreEqual(1, fields[TestWellKnownTypes.Int32FieldFieldNumber].GetValue(message));
+            Assert.AreEqual(2L, fields[TestWellKnownTypes.Int64FieldFieldNumber].GetValue(message));
+            Assert.AreEqual(3U, fields[TestWellKnownTypes.Uint32FieldFieldNumber].GetValue(message));
+            Assert.AreEqual(4UL, fields[TestWellKnownTypes.Uint64FieldFieldNumber].GetValue(message));
+
+            // And a couple of null fields...
+            message.StringField = null;
+            message.FloatField = null;
+            Assert.IsNull(fields[TestWellKnownTypes.StringFieldFieldNumber].GetValue(message));
+            Assert.IsNull(fields[TestWellKnownTypes.FloatFieldFieldNumber].GetValue(message));
+        }
+
+        [Test]
+        public void Reflection_RepeatedFields()
+        {
+            // Just a single example... note that we can't have a null value here
+            var message = new RepeatedWellKnownTypes { Int32Field = { 1, 2 } };
+            var fields = ((IReflectedMessage) message).Fields;
+            var list = (IList) fields[RepeatedWellKnownTypes.Int32FieldFieldNumber].GetValue(message);
+            CollectionAssert.AreEqual(new[] { 1, 2 }, list);
+        }
+
+        [Test]
+        public void Reflection_MapFields()
+        {
+            // Just a single example... note that we can't have a null value here
+            var message = new MapWellKnownTypes { Int32Field = { { 1, 2 }, { 3, null } } };
+            var fields = ((IReflectedMessage) message).Fields;
+            var dictionary = (IDictionary) fields[MapWellKnownTypes.Int32FieldFieldNumber].GetValue(message);
+            Assert.AreEqual(2, dictionary[1]);
+            Assert.IsNull(dictionary[3]);
+            Assert.IsTrue(dictionary.Contains(3));
+        }
+
+        // Merging is odd with wrapper types, due to the way that default values aren't emitted in
+        // the binary stream. In fact we cheat a little bit - a message with an explicitly present default
+        // value will have that default value ignored.
+        [Test]
+        [TestCase("x", "y", "y")]
+        [TestCase("x", "", "x")]
+        [TestCase("x", null, "x")]
+        [TestCase("", "y", "y")]
+        [TestCase("", "", "")]
+        [TestCase("", null, "")]
+        [TestCase(null, "y", "y")]
+        [TestCase(null, "", "")]
+        [TestCase(null, null, null)]
+        public void Merging(string original, string merged, string expected)
+        {
+            var originalMessage = new TestWellKnownTypes { StringField = original };
+            var mergingMessage = new TestWellKnownTypes { StringField = merged };
+            originalMessage.MergeFrom(mergingMessage);
+            Assert.AreEqual(expected, originalMessage.StringField);
+
+            // Try it using MergeFrom(CodedInputStream) too...
+            originalMessage = new TestWellKnownTypes { StringField = original };
+            originalMessage.MergeFrom(mergingMessage.ToByteArray());
+            Assert.AreEqual(expected, originalMessage.StringField);
         }
     }
 }

+ 3 - 5
csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs

@@ -129,8 +129,7 @@ namespace Google.Protobuf
         public static int ComputeStringSize(String value)
         {
             int byteArraySize = Utf8Encoding.GetByteCount(value);
-            return ComputeRawVarint32Size((uint) byteArraySize) +
-                   byteArraySize;
+            return ComputeLengthSize(byteArraySize) + byteArraySize;
         }
 
         /// <summary>
@@ -149,7 +148,7 @@ namespace Google.Protobuf
         public static int ComputeMessageSize(IMessage value)
         {
             int size = value.CalculateSize();
-            return ComputeRawVarint32Size((uint) size) + size;
+            return ComputeLengthSize(size) + size;
         }
 
         /// <summary>
@@ -158,8 +157,7 @@ namespace Google.Protobuf
         /// </summary>
         public static int ComputeBytesSize(ByteString value)
         {
-            return ComputeRawVarint32Size((uint) value.Length) +
-                   value.Length;
+            return ComputeLengthSize(value.Length) + value.Length;
         }
 
         /// <summary>

+ 2 - 2
csharp/src/ProtocolBuffers/CodedOutputStream.cs

@@ -274,7 +274,7 @@ namespace Google.Protobuf
         /// <param name="value">The value to write</param>
         public void WriteMessage(IMessage value)
         {
-            WriteRawVarint32((uint) value.CalculateSize());
+            WriteLength(value.CalculateSize());
             value.WriteTo(this);
         }
 
@@ -285,7 +285,7 @@ namespace Google.Protobuf
         /// <param name="value">The value to write</param>
         public void WriteBytes(ByteString value)
         {
-            WriteRawVarint32((uint) value.Length);
+            WriteLength(value.Length);
             value.WriteRawBytesTo(this);
         }
 

+ 32 - 2
csharp/src/ProtocolBuffers/Collections/MapField.cs

@@ -51,14 +51,38 @@ namespace Google.Protobuf.Collections
     public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IFreezable, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
     {
         // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
+        private bool allowNullValues;
         private bool frozen;
         private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
             new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
         private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
 
+        /// <summary>
+        /// Constructs a new map field, defaulting the value nullability to only allow null values for message types
+        /// and non-nullable value types.
+        /// </summary>
+        public MapField() : this(typeof(IMessage).IsAssignableFrom(typeof(TValue)) || Nullable.GetUnderlyingType(typeof(TValue)) != null)
+        {
+        }
+
+        /// <summary>
+        /// Constructs a new map field, overriding the choice of whether null values are permitted in the map.
+        /// This is used by wrapper types, where maps with string and bytes wrappers as the value types
+        /// support null values.
+        /// </summary>
+        /// <param name="allowNullValues">Whether null values are permitted in the map or not.</param>
+        public MapField(bool allowNullValues)
+        {
+            if (allowNullValues && typeof(TValue).IsValueType && Nullable.GetUnderlyingType(typeof(TValue)) == null)
+            {
+                throw new ArgumentException("allowNullValues", "Non-nullable value types do not support null values");
+            }
+            this.allowNullValues = allowNullValues;
+        }
+
         public MapField<TKey, TValue> Clone()
         {
-            var clone = new MapField<TKey, TValue>();
+            var clone = new MapField<TKey, TValue>(allowNullValues);
             // Keys are never cloneable. Values might be.
             if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
             {
@@ -138,7 +162,8 @@ namespace Google.Protobuf.Collections
             set
             {
                 ThrowHelper.ThrowIfNull(key, "key");
-                if (value == null && (typeof(TValue) == typeof(ByteString) || typeof(TValue) == typeof(string)))
+                // value == null check here is redundant, but avoids boxing.
+                if (value == null && !allowNullValues)
                 {
                     ThrowHelper.ThrowIfNull(value, "value");
                 }
@@ -225,6 +250,11 @@ namespace Google.Protobuf.Collections
             }
         }
 
+        /// <summary>
+        /// Returns whether or not this map allows values to be null.
+        /// </summary>
+        public bool AllowsNullValues { get { return allowNullValues; } }
+
         public int Count { get { return list.Count; } }
         public bool IsReadOnly { get { return frozen; } }
 

+ 1 - 1
csharp/src/ProtocolBuffers/Collections/RepeatedField.cs

@@ -123,7 +123,7 @@ namespace Google.Protobuf.Collections
             {
                 int dataSize = CalculatePackedDataSize(codec);
                 return CodedOutputStream.ComputeRawVarint32Size(tag) +
-                    CodedOutputStream.ComputeRawVarint32Size((uint)dataSize) +
+                    CodedOutputStream.ComputeLengthSize(dataSize) +
                     dataSize;
             }
             else

+ 128 - 21
csharp/src/ProtocolBuffers/FieldCodec.cs

@@ -29,7 +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;
 
@@ -43,7 +43,7 @@ namespace Google.Protobuf
         // TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...)
         public static FieldCodec<string> ForString(uint tag)
         {
-            return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); 
+            return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag);
         }
 
         public static FieldCodec<ByteString> ForBytes(uint tag)
@@ -131,6 +131,106 @@ namespace Google.Protobuf
             return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; },
                 (output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag);
         }
+
+        /// <summary>
+        /// Creates a codec for a wrapper type of a class - which must be string or ByteString.
+        /// </summary>
+        public static FieldCodec<T> ForClassWrapper<T>(uint tag) where T : class
+        {
+            var nestedCodec = WrapperCodecs.GetCodec<T>();
+            return new FieldCodec<T>(
+                input => WrapperCodecs.Read<T>(input, nestedCodec),
+                (output, value) => WrapperCodecs.Write<T>(output, value, nestedCodec),
+                value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
+                tag,
+                null); // Default value for the wrapper
+        }
+
+        /// <summary>
+        /// Creates a codec for a wrapper type of a struct - which must be Int32, Int64, UInt32, UInt64,
+        /// Bool, Single or Double.
+        /// </summary>
+        public static FieldCodec<T?> ForStructWrapper<T>(uint tag) where T : struct
+        {
+            var nestedCodec = WrapperCodecs.GetCodec<T>();
+            return new FieldCodec<T?>(
+                input => WrapperCodecs.Read<T>(input, nestedCodec),
+                (output, value) => WrapperCodecs.Write<T>(output, value.Value, nestedCodec),
+                value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
+                tag,
+                null); // Default value for the wrapper
+        }
+
+        // Helper code to create codecs for wrapper types. Somewhat ugly with all the 
+        private static class WrapperCodecs
+        {
+            private static readonly Dictionary<Type, object> Codecs = new Dictionary<Type, object>
+            {
+                { typeof(bool), ForBool(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+                { typeof(int), ForInt32(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+                { typeof(long), ForInt64(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+                { typeof(uint), ForUInt32(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+                { typeof(ulong), ForUInt64(WireFormat.MakeTag(1, WireFormat.WireType.Varint)) },
+                { typeof(float), ForFloat(WireFormat.MakeTag(1, WireFormat.WireType.Fixed32)) },
+                { typeof(double), ForDouble(WireFormat.MakeTag(1, WireFormat.WireType.Fixed64)) },
+                { typeof(string), ForString(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited)) },
+                { typeof(ByteString), ForBytes(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited)) }
+            };
+
+            /// <summary>
+            /// Returns a field codec which effectively wraps a value of type T in a message.
+            /// 
+            /// </summary>
+            internal static FieldCodec<T> GetCodec<T>()
+            {
+                object value;
+                if (!Codecs.TryGetValue(typeof(T), out value))
+                {
+                    throw new InvalidOperationException("Invalid type argument requested for wrapper codec: " + typeof(T));
+                }
+                return (FieldCodec<T>) value;
+            }
+
+            internal static T Read<T>(CodedInputStream input, FieldCodec<T> codec)
+            {
+                int length = input.ReadLength();
+                int oldLimit = input.PushLimit(length);
+
+                uint tag;
+                T value = codec.DefaultValue;
+                while (input.ReadTag(out tag))
+                {
+                    if (tag == 0)
+                    {
+                        throw InvalidProtocolBufferException.InvalidTag();
+                    }
+                    if (tag == codec.Tag)
+                    {
+                        value = codec.Read(input);
+                    }
+                    if (WireFormat.IsEndGroupTag(tag))
+                    {
+                        break;
+                    }
+                }
+                input.CheckLastTagWas(0);
+                input.PopLimit(oldLimit);
+
+                return value;
+            }
+
+            internal static void Write<T>(CodedOutputStream output, T value, FieldCodec<T> codec)
+            {
+                output.WriteLength(codec.CalculateSizeWithTag(value));
+                codec.WriteTagAndValue(output, value);
+            }
+
+            internal  static int CalculateSize<T>(T value, FieldCodec<T> codec)
+            {
+                int fieldLength = codec.CalculateSizeWithTag(value);
+                return CodedOutputStream.ComputeLengthSize(fieldLength) + fieldLength;
+            }
+        }
     }
 
     /// <summary>
@@ -144,31 +244,19 @@ namespace Google.Protobuf
     /// </remarks>
     public sealed class FieldCodec<T>
     {
-        private static readonly Func<T, bool> IsDefault;
-        private static readonly T Default;
+        private static readonly T DefaultDefault;
 
         static FieldCodec()
         {
             if (typeof(T) == typeof(string))
             {
-                Default = (T)(object)"";
-                IsDefault = CreateDefaultValueCheck<string>(x => x.Length == 0);
+                DefaultDefault = (T)(object)"";
             }
             else if (typeof(T) == typeof(ByteString))
             {
-                Default = (T)(object)ByteString.Empty;
-                IsDefault = CreateDefaultValueCheck<ByteString>(x => x.Length == 0);
-            }
-            else if (!typeof(T).IsValueType)
-            {
-                // Default default
-                IsDefault = CreateDefaultValueCheck<T>(x => x == null);
-            }
-            else
-            {
-                // Default default
-                IsDefault = CreateDefaultValueCheck<T>(x => EqualityComparer<T>.Default.Equals(x, default(T)));
+                DefaultDefault = (T)(object)ByteString.Empty;
             }
+            // Otherwise it's the default value of the CLR type
         }
 
         private static Func<T, bool> CreateDefaultValueCheck<TTmp>(Func<TTmp, bool> check)
@@ -182,18 +270,32 @@ namespace Google.Protobuf
         private readonly uint tag;
         private readonly int tagSize;
         private readonly int fixedSize;
+        // Default value for this codec. Usually the same for every instance of the same type, but
+        // for string/ByteString wrapper fields the codec's default value is null, whereas for
+        // other string/ByteString fields it's "" or ByteString.Empty.
+        private readonly T defaultValue;
 
         internal FieldCodec(
             Func<CodedInputStream, T> reader,
             Action<CodedOutputStream, T> writer,
             Func<T, int> sizeCalculator,
-            uint tag)
+            uint tag) : this(reader, writer, sizeCalculator, tag, DefaultDefault)
+        {
+        }
+
+        internal FieldCodec(
+            Func<CodedInputStream, T> reader,
+            Action<CodedOutputStream, T> writer,
+            Func<T, int> sizeCalculator,
+            uint tag,
+            T defaultValue)
         {
             this.reader = reader;
             this.writer = writer;
             this.sizeCalculator = sizeCalculator;
             this.fixedSize = 0;
             this.tag = tag;
+            this.defaultValue = defaultValue;
             tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
         }
 
@@ -234,7 +336,7 @@ namespace Google.Protobuf
 
         public uint Tag { get { return tag; } }
 
-        public T DefaultValue { get { return Default; } }
+        public T DefaultValue { get { return defaultValue; } }
 
         /// <summary>
         /// Write a tag and the given value, *if* the value is not the default.
@@ -260,6 +362,11 @@ namespace Google.Protobuf
         public int CalculateSizeWithTag(T value)
         {
             return IsDefault(value) ? 0 : sizeCalculator(value) + tagSize;
-        }        
+        }
+
+        private bool IsDefault(T value)
+        {
+            return EqualityComparer<T>.Default.Equals(value, defaultValue);
+        }
     }
 }

+ 25 - 16
src/google/protobuf/compiler/csharp/csharp_field_base.cc

@@ -309,14 +309,23 @@ std::string FieldGeneratorBase::GetBytesDefaultValueInternal() {
 }
 
 std::string FieldGeneratorBase::default_value() {
-  switch (descriptor_->type()) {
+    return default_value(descriptor_);
+}
+
+std::string FieldGeneratorBase::default_value(const FieldDescriptor* descriptor) {
+  switch (descriptor->type()) {
     case FieldDescriptor::TYPE_ENUM:
-      return type_name() + "." + descriptor_->default_value_enum()->name();
+      return type_name() + "." + descriptor->default_value_enum()->name();
     case FieldDescriptor::TYPE_MESSAGE:
     case FieldDescriptor::TYPE_GROUP:
-      return type_name() + ".DefaultInstance";
+      if (IsWrapperType(descriptor)) {
+        const FieldDescriptor* wrapped_field = descriptor->message_type()->field(0);
+        return default_value(wrapped_field);
+      } else {
+        return "null";
+      }
     case FieldDescriptor::TYPE_DOUBLE: {
-      double value = descriptor_->default_value_double();
+      double value = descriptor->default_value_double();
       if (value == numeric_limits<double>::infinity()) {
         return "double.PositiveInfinity";
       } else if (value == -numeric_limits<double>::infinity()) {
@@ -327,7 +336,7 @@ std::string FieldGeneratorBase::default_value() {
       return SimpleDtoa(value) + "D";
     }
     case FieldDescriptor::TYPE_FLOAT: {
-      float value = descriptor_->default_value_float();
+      float value = descriptor->default_value_float();
       if (value == numeric_limits<float>::infinity()) {
         return "float.PositiveInfinity";
       } else if (value == -numeric_limits<float>::infinity()) {
@@ -338,17 +347,17 @@ std::string FieldGeneratorBase::default_value() {
       return SimpleFtoa(value) + "F";
     }
     case FieldDescriptor::TYPE_INT64:
-      return SimpleItoa(descriptor_->default_value_int64()) + "L";
+      return SimpleItoa(descriptor->default_value_int64()) + "L";
     case FieldDescriptor::TYPE_UINT64:
-      return SimpleItoa(descriptor_->default_value_uint64()) + "UL";
+      return SimpleItoa(descriptor->default_value_uint64()) + "UL";
     case FieldDescriptor::TYPE_INT32:
-      return SimpleItoa(descriptor_->default_value_int32());
+      return SimpleItoa(descriptor->default_value_int32());
     case FieldDescriptor::TYPE_FIXED64:
-      return SimpleItoa(descriptor_->default_value_uint64()) + "UL";
+      return SimpleItoa(descriptor->default_value_uint64()) + "UL";
     case FieldDescriptor::TYPE_FIXED32:
-      return SimpleItoa(descriptor_->default_value_uint32());
+      return SimpleItoa(descriptor->default_value_uint32());
     case FieldDescriptor::TYPE_BOOL:
-      if (descriptor_->default_value_bool()) {
+      if (descriptor->default_value_bool()) {
         return "true";
       } else {
         return "false";
@@ -358,15 +367,15 @@ std::string FieldGeneratorBase::default_value() {
     case FieldDescriptor::TYPE_BYTES:
       return GetBytesDefaultValueInternal();
     case FieldDescriptor::TYPE_UINT32:
-      return SimpleItoa(descriptor_->default_value_uint32());
+      return SimpleItoa(descriptor->default_value_uint32());
     case FieldDescriptor::TYPE_SFIXED32:
-      return SimpleItoa(descriptor_->default_value_int32());
+      return SimpleItoa(descriptor->default_value_int32());
     case FieldDescriptor::TYPE_SFIXED64:
-      return SimpleItoa(descriptor_->default_value_int64()) + "L";
+      return SimpleItoa(descriptor->default_value_int64()) + "L";
     case FieldDescriptor::TYPE_SINT32:
-      return SimpleItoa(descriptor_->default_value_int32());
+      return SimpleItoa(descriptor->default_value_int32());
     case FieldDescriptor::TYPE_SINT64:
-      return SimpleItoa(descriptor_->default_value_int64()) + "L";
+      return SimpleItoa(descriptor->default_value_int64()) + "L";
     default:
       GOOGLE_LOG(FATAL)<< "Unknown field type.";
       return "";

+ 1 - 0
src/google/protobuf/compiler/csharp/csharp_field_base.h

@@ -82,6 +82,7 @@ class FieldGeneratorBase : public SourceGeneratorBase {
   bool has_default_value();
   bool is_nullable_type();
   std::string default_value();
+  std::string default_value(const FieldDescriptor* descriptor);
   std::string number();
   std::string capitalized_type_name();
   std::string field_ordinal();

+ 2 - 1
src/google/protobuf/compiler/csharp/csharp_map_field.cc

@@ -61,6 +61,7 @@ void MapFieldGenerator::GenerateMembers(io::Printer* printer) {
       descriptor_->message_type()->FindFieldByName("value");
   variables_["key_type_name"] = type_name(key_descriptor);
   variables_["value_type_name"] = type_name(value_descriptor);
+  variables_["true_for_wrappers"] = IsWrapperType(value_descriptor) ? "true" : "";
   scoped_ptr<FieldGeneratorBase> key_generator(CreateFieldGenerator(key_descriptor, 1));  
   scoped_ptr<FieldGeneratorBase> value_generator(CreateFieldGenerator(value_descriptor, 2));
 
@@ -74,7 +75,7 @@ void MapFieldGenerator::GenerateMembers(io::Printer* printer) {
   printer->Print(
     variables_,
     ", $tag$);\n"
-    "private readonly pbc::MapField<$key_type_name$, $value_type_name$> $name$_ = new pbc::MapField<$key_type_name$, $value_type_name$>();\n");
+    "private readonly pbc::MapField<$key_type_name$, $value_type_name$> $name$_ = new pbc::MapField<$key_type_name$, $value_type_name$>($true_for_wrappers$);\n");
   AddDeprecatedFlag(printer);
   printer->Print(
     variables_,

+ 14 - 1
src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc

@@ -39,6 +39,8 @@
 
 #include <google/protobuf/compiler/csharp/csharp_helpers.h>
 #include <google/protobuf/compiler/csharp/csharp_repeated_message_field.h>
+#include <google/protobuf/compiler/csharp/csharp_message_field.h>
+#include <google/protobuf/compiler/csharp/csharp_wrapper_field.h>
 
 namespace google {
 namespace protobuf {
@@ -58,7 +60,18 @@ void RepeatedMessageFieldGenerator::GenerateMembers(io::Printer* printer) {
   printer->Print(
     variables_,
     "private static readonly pb::FieldCodec<$type_name$> _repeated_$name$_codec\n"
-    "    = pb::FieldCodec.ForMessage($tag$, $type_name$.Parser);\n");
+    "    = ");
+  // Don't want to duplicate the codec code here... maybe we should have a
+  // "create single field generator for this repeated field"
+  // function, but it doesn't seem worth it for just this.
+  if (IsWrapperType(descriptor_)) {
+    scoped_ptr<FieldGeneratorBase> single_generator(new WrapperFieldGenerator(descriptor_, fieldOrdinal_));
+    single_generator->GenerateCodecCode(printer);
+  } else {
+    scoped_ptr<FieldGeneratorBase> single_generator(new MessageFieldGenerator(descriptor_, fieldOrdinal_));
+    single_generator->GenerateCodecCode(printer);
+  }
+  printer->Print(";\n");
   printer->Print(
     variables_,
     "private readonly pbc::RepeatedField<$type_name$> $name$_ = new pbc::RepeatedField<$type_name$>();\n");

+ 41 - 42
src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc

@@ -50,35 +50,34 @@ WrapperFieldGenerator::WrapperFieldGenerator(const FieldDescriptor* descriptor,
     : FieldGeneratorBase(descriptor, fieldOrdinal) {
   variables_["has_property_check"] = name() + "_ != null";
   variables_["has_not_property_check"] = name() + "_ == null";
-  variables_["message_type_name"] = GetClassName(descriptor->message_type());
   const FieldDescriptor* wrapped_field = descriptor->message_type()->field(0);
   is_value_type = wrapped_field->type() != FieldDescriptor::TYPE_STRING &&
       wrapped_field->type() != FieldDescriptor::TYPE_BYTES;
-  variables_["deref"] = is_value_type ? ".Value" : "";
-  // This will always be a single byte, because it's always field 1.
-  variables_["message_tag_bytes"] = SimpleItoa(FixedMakeTag(wrapped_field));
+  if (is_value_type) {
+    variables_["nonnullable_type_name"] = type_name(wrapped_field);
+  }
 }
 
 WrapperFieldGenerator::~WrapperFieldGenerator() {
 }
 
 void WrapperFieldGenerator::GenerateMembers(io::Printer* printer) {
-  // Back the underlying property with an underlying message. This isn't efficient,
-  // but it makes it easier to be compliant with what platforms which don't support wrapper
-  // types would do. Currently, each time the value is changed, we create a new instance.
-  // With suitable care to avoid aliasing, we could probably check whether or not we've already
-  // got an instance, and simply mutate the existing one.
+  printer->Print(
+        variables_,
+        "private static readonly pb::FieldCodec<$type_name$> _single_$name$_codec = ");
+  GenerateCodecCode(printer);
   printer->Print(
     variables_,
-    "private $message_type_name$ $name$_;\n");
+    ";\n"
+    "private $type_name$ $name$_;\n");
   AddDeprecatedFlag(printer);
   printer->Print(
     variables_,
     "$access_level$ $type_name$ $property_name$ {\n"
-    "  get { return $name$_ == null ? ($type_name$) null : $name$_.Value; }\n"
+    "  get { return $name$_; }\n"
     "  set {\n"
     "    pb::Freezable.CheckMutable(this);\n"
-    "    $name$_ = value == null ? null : new $message_type_name$ { Value = value$deref$ };\n"
+    "    $name$_ = value;\n"
     "  }\n"
     "}\n");
 }
@@ -87,28 +86,26 @@ void WrapperFieldGenerator::GenerateMergingCode(io::Printer* printer) {
   printer->Print(
     variables_,
     "if (other.$has_property_check$) {\n"
-    "  if ($has_not_property_check$) {\n"
-    "    $name$_ = new $message_type_name$();\n"
+    "  if ($has_not_property_check$ || other.$property_name$ != $default_value$) {\n"
+    "    $property_name$ = other.$property_name$;\n"
     "  }\n"
-    "  $name$_.MergeFrom(other.$name$_);\n"
     "}\n");
 }
 
 void WrapperFieldGenerator::GenerateParsingCode(io::Printer* printer) {
   printer->Print(
     variables_,
-    "if ($has_not_property_check$) {\n"
-    "  $name$_ = new $message_type_name$();\n"
-    "}\n"
-    "input.ReadMessage($name$_);\n"); // No need to support TYPE_GROUP...
+    "$type_name$ value = _single_$name$_codec.Read(input);\n"
+    "if ($has_not_property_check$ || value != $default_value$) {\n"
+    "  $property_name$ = value;\n"
+    "}\n");
 }
 
 void WrapperFieldGenerator::GenerateSerializationCode(io::Printer* printer) {
   printer->Print(
     variables_,
     "if ($has_property_check$) {\n"
-    "  output.WriteRawTag($tag_bytes$);\n"
-    "  output.WriteMessage($name$_);\n"
+    "  _single_$name$_codec.WriteTagAndValue(output, $property_name$);\n"
     "}\n");
 }
 
@@ -116,7 +113,7 @@ void WrapperFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
   printer->Print(
     variables_,
     "if ($has_property_check$) {\n"
-    "  size += $tag_size$ + pb::CodedOutputStream.ComputeMessageSize($name$_);\n"
+    "  size += _single_$name$_codec.CalculateSizeWithTag($property_name$);\n"
     "}\n");
 }
 
@@ -137,16 +134,20 @@ void WrapperFieldGenerator::WriteToString(io::Printer* printer) {
 }
 
 void WrapperFieldGenerator::GenerateCloningCode(io::Printer* printer) {
-  // This will effectively perform a deep clone - it will create a new
-  // underlying message if necessary
   printer->Print(variables_,
     "$property_name$ = other.$property_name$;\n");
 }
 
 void WrapperFieldGenerator::GenerateCodecCode(io::Printer* printer) {
-  printer->Print(
-    variables_,
-    "pb::FieldCodec.ForWrapperType<$type_name$, $message_type_name$>($tag$, $message_type_name$.Parser)");
+  if (is_value_type) {
+    printer->Print(
+      variables_,
+      "pb::FieldCodec.ForStructWrapper<$nonnullable_type_name$>($tag$)");
+  } else {
+    printer->Print(
+      variables_,
+      "pb::FieldCodec.ForClassWrapper<$type_name$>($tag$)");
+  }
 }
 
 WrapperOneofFieldGenerator::WrapperOneofFieldGenerator(const FieldDescriptor* descriptor,
@@ -159,48 +160,46 @@ WrapperOneofFieldGenerator::~WrapperOneofFieldGenerator() {
 }
 
 void WrapperOneofFieldGenerator::GenerateMembers(io::Printer* printer) {
+  // Note: deliberately _oneof_$name$_codec, not _$oneof_name$_codec... we have one codec per field.
+  printer->Print(
+        variables_,
+        "private static readonly pb::FieldCodec<$type_name$> _oneof_$name$_codec = ");
+  GenerateCodecCode(printer);
+  printer->Print(";\n");
   AddDeprecatedFlag(printer);
   printer->Print(
     variables_,
     "$access_level$ $type_name$ $property_name$ {\n"
-    "  get { return $has_property_check$ ? (($message_type_name$) $oneof_name$_).Value : ($type_name$) null; }\n"
+    "  get { return $has_property_check$ ? ($type_name$) $oneof_name$_ : ($type_name$) null; }\n"
     "  set {\n"
     "    pb::Freezable.CheckMutable(this);\n"
-    "    $oneof_name$_ = value == null ? null : new $message_type_name$ { Value = value$deref$ };\n"
+    "    $oneof_name$_ = value;\n"
     "    $oneof_name$Case_ = value == null ? $oneof_property_name$OneofCase.None : $oneof_property_name$OneofCase.$property_name$;\n"
     "  }\n"
     "}\n");
 }
 
-
-
 void WrapperOneofFieldGenerator::GenerateParsingCode(io::Printer* printer) {
   printer->Print(
     variables_,
-    "$message_type_name$ subBuilder = new $message_type_name$();\n"
-    "if ($has_property_check$) {\n"
-    "  subBuilder.MergeFrom(($message_type_name$) $oneof_name$_);\n"
-    "}\n"
-    "input.ReadMessage(subBuilder);\n"
-    // Don't set the property, which would create a new and equivalent message; just set the two fields.
-    "$oneof_name$Case_ = $oneof_property_name$OneofCase.$property_name$;\n"
-    "$oneof_name$_ = subBuilder;\n");
+    "$property_name$ = _oneof_$name$_codec.Read(input);\n");
 }
 
 void WrapperOneofFieldGenerator::GenerateSerializationCode(io::Printer* printer) {
+  // TODO: I suspect this is wrong...
   printer->Print(
     variables_,
     "if ($has_property_check$) {\n"
-    "  output.WriteRawTag($tag_bytes$);\n"
-    "  output.WriteMessage(($message_type_name$) $oneof_name$_);\n"
+    "  _oneof_$name$_codec.WriteTagAndValue(output, ($type_name$) $oneof_name$_);\n"
     "}\n");
 }
 
 void WrapperOneofFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
+  // TODO: I suspect this is wrong...
   printer->Print(
     variables_,
     "if ($has_property_check$) {\n"
-    "  size += $tag_size$ + pb::CodedOutputStream.ComputeMessageSize((($message_type_name$) $oneof_name$_));\n"
+    "  size += _oneof_$name$_codec.CalculateSizeWithTag($property_name$);\n"
     "}\n");
 }
 

+ 10 - 10
src/google/protobuf/unittest_well_known_types.proto

@@ -52,16 +52,16 @@ message RepeatedWellKnownTypes {
   repeated google.protobuf.Struct struct_field = 7;
   repeated google.protobuf.Timestamp timestamp_field = 8;
   repeated google.protobuf.Type type_field = 9;
-  // TODO: Do these even make sense? Should they be prohibited?
-//  repeated google.protobuf.DoubleValue double_field = 10;
-//  repeated google.protobuf.FloatValue float_field = 11;
-//  repeated google.protobuf.Int64Value int64_field = 12;
-//  repeated google.protobuf.UInt64Value uint64_field = 13;
-//  repeated google.protobuf.Int32Value int32_field = 14;
-//  repeated google.protobuf.UInt32Value uint32_field = 15;
-//  repeated google.protobuf.BoolValue bool_field = 16;
-//  repeated google.protobuf.StringValue string_field = 17;
-//  repeated google.protobuf.BytesValue bytes_field = 18;
+  // These don't actually make a lot of sense, but they're not prohibited...
+  repeated google.protobuf.DoubleValue double_field = 10;
+  repeated google.protobuf.FloatValue float_field = 11;
+  repeated google.protobuf.Int64Value int64_field = 12;
+  repeated google.protobuf.UInt64Value uint64_field = 13;
+  repeated google.protobuf.Int32Value int32_field = 14;
+  repeated google.protobuf.UInt32Value uint32_field = 15;
+  repeated google.protobuf.BoolValue bool_field = 16;
+  repeated google.protobuf.StringValue string_field = 17;
+  repeated google.protobuf.BytesValue bytes_field = 18;
 }
 
 message OneofWellKnownTypes {

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно