Browse Source

First pass at map support.

More tests required. Generated code in next commit.
Jon Skeet 10 years ago
parent
commit
0d684d3420

+ 1 - 0
cmake/libprotoc.cmake

@@ -19,6 +19,7 @@ set(libprotoc_files
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_field_base.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_field_base.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_helpers.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_helpers.cc
+  ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_map_field.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message_field.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message_field.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc

+ 61 - 0
csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs

@@ -1,4 +1,6 @@
 using System;
 using System;
+using System.Configuration;
+using System.IO;
 using Google.Protobuf.TestProtos;
 using Google.Protobuf.TestProtos;
 using NUnit.Framework;
 using NUnit.Framework;
 
 
@@ -146,6 +148,65 @@ namespace Google.Protobuf
             Assert.AreEqual(message, parsed);
             Assert.AreEqual(message, parsed);
         }
         }
 
 
+        [Test]
+        public void RoundTrip_Maps()
+        {
+            var message = new TestAllTypes
+            {
+                MapBoolToEnum = {
+                    { false, TestAllTypes.Types.NestedEnum.BAR},
+                    { true, TestAllTypes.Types.NestedEnum.BAZ}
+                },
+                MapInt32ToBytes = {
+                    { 5, ByteString.CopyFrom(6, 7, 8) },
+                    { 25, ByteString.CopyFrom(1, 2, 3, 4, 5) },
+                    { 10, ByteString.Empty }
+                },
+                MapStringToNestedMessage = {
+                    { "", new TestAllTypes.Types.NestedMessage { Bb = 10 } },
+                    { "null value", null },
+                }
+            };
+
+            byte[] bytes = message.ToByteArray();
+            TestAllTypes parsed = TestAllTypes.Parser.ParseFrom(bytes);
+            Assert.AreEqual(message, parsed);
+        }
+
+        [Test]
+        public void MapWithEmptyEntry()
+        {
+            var message = new TestAllTypes
+            {
+                MapInt32ToBytes = { { 0, ByteString.Empty } }
+            };
+
+            byte[] bytes = message.ToByteArray();
+            Assert.AreEqual(3, bytes.Length); // Tag for field entry (2 bytes), length of entry (0; 1 byte)
+
+            var parsed = TestAllTypes.Parser.ParseFrom(bytes);
+            Assert.AreEqual(1, parsed.MapInt32ToBytes.Count);
+            Assert.AreEqual(ByteString.Empty, parsed.MapInt32ToBytes[0]);
+        }
+
+        [Test]
+        public void MapWithOnlyValue()
+        {
+            // Hand-craft the stream to contain a single entry with just a value.
+            var memoryStream = new MemoryStream();
+            var output = CodedOutputStream.CreateInstance(memoryStream);
+            output.WriteTag(TestAllTypes.MapStringToNestedMessageFieldNumber, WireFormat.WireType.LengthDelimited);
+            var nestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 };
+            // Size of the entry (tag, size written by WriteMessage, data written by WriteMessage)
+            output.WriteRawVarint32((uint)(nestedMessage.CalculateSize() + 3));
+            output.WriteTag(2, WireFormat.WireType.LengthDelimited);
+            output.WriteMessage(nestedMessage);
+            output.Flush();
+
+            var parsed = TestAllTypes.Parser.ParseFrom(memoryStream.ToArray());
+            Assert.AreEqual(nestedMessage, parsed.MapStringToNestedMessage[""]);
+        }
+
         [Test]
         [Test]
         public void CloneSingleNonMessageValues()
         public void CloneSingleNonMessageValues()
         {
         {

+ 8 - 0
csharp/src/ProtocolBuffers/CodedOutputStream.cs

@@ -475,6 +475,14 @@ namespace Google.Protobuf
             WriteRawVarint32(WireFormat.MakeTag(fieldNumber, type));
             WriteRawVarint32(WireFormat.MakeTag(fieldNumber, type));
         }
         }
 
 
+        /// <summary>
+        /// Writes an already-encoded tag.
+        /// </summary>
+        public void WriteTag(uint tag)
+        {
+            WriteRawVarint32(tag);
+        }
+
         /// <summary>
         /// <summary>
         /// Writes the given single-byte tag directly to the stream.
         /// Writes the given single-byte tag directly to the stream.
         /// </summary>
         /// </summary>

+ 400 - 0
csharp/src/ProtocolBuffers/Collections/MapField.cs

@@ -0,0 +1,400 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2015 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Google.Protobuf.Collections
+{
+    /// <summary>
+    /// Representation of a map field in a Protocol Buffer message.
+    /// </summary>
+    /// <remarks>
+    /// This implementation preserves insertion order for simplicity of testing
+    /// code using maps fields. Overwriting an existing entry does not change the
+    /// position of that entry within the map. Equality is not order-sensitive.
+    /// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal"/>.
+    /// </remarks>
+    /// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam>
+    /// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam>
+    public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IFreezable, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>
+    {
+        // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
+        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>>();
+
+        public MapField<TKey, TValue> Clone()
+        {
+            var clone = new MapField<TKey, TValue>();
+            // Keys are never cloneable. Values might be.
+            if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
+            {
+                foreach (var pair in list)
+                {
+                    clone.Add(pair.Key, pair.Value == null ? pair.Value : ((IDeepCloneable<TValue>) pair.Value).Clone());
+                }
+            }
+            else
+            {
+                // Nothing is cloneable, so we don't need to worry.
+                clone.Add(this);
+            }
+            return clone;
+        }
+
+        public void Add(TKey key, TValue value)
+        {
+            ThrowHelper.ThrowIfNull(key, "key");
+            this.CheckMutable();
+            if (ContainsKey(key))
+            {
+                throw new ArgumentException("Key already exists in map", "key");
+            }
+            this[key] = value;
+        }
+
+        public bool ContainsKey(TKey key)
+        {
+            return map.ContainsKey(key);
+        }
+
+        public bool Remove(TKey key)
+        {
+            this.CheckMutable();
+            LinkedListNode<KeyValuePair<TKey, TValue>> node;
+            if (map.TryGetValue(key, out node))
+            {
+                map.Remove(key);
+                node.List.Remove(node);
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        public bool TryGetValue(TKey key, out TValue value)
+        {
+            LinkedListNode<KeyValuePair<TKey, TValue>> node;
+            if (map.TryGetValue(key, out node))
+            {
+                value = node.Value.Value;
+                return true;
+            }
+            else
+            {
+                value = default(TValue);
+                return false;
+            }
+        }
+
+        public TValue this[TKey key]
+        {
+            get
+            {
+                TValue value;
+                if (TryGetValue(key, out value))
+                {
+                    return value;
+                }
+                throw new KeyNotFoundException();
+            }
+            set
+            {
+                this.CheckMutable();
+                LinkedListNode<KeyValuePair<TKey, TValue>> node;
+                var pair = new KeyValuePair<TKey, TValue>(key, value);
+                if (map.TryGetValue(key, out node))
+                {
+                    node.Value = pair;
+                }
+                else
+                {
+                    node = list.AddLast(pair);
+                    map[key] = node;
+                }
+            }
+        }
+
+        // TODO: Make these views?
+        public ICollection<TKey> Keys { get { return list.Select(t => t.Key).ToList(); } }
+        public ICollection<TValue> Values { get { return list.Select(t => t.Value).ToList(); } }
+
+        public void Add(IDictionary<TKey, TValue> entries)
+        {
+            foreach (var pair in entries)
+            {
+                Add(pair);
+            }
+        }
+
+        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+        {
+            return list.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public void Add(KeyValuePair<TKey, TValue> item)
+        {
+            this.CheckMutable();
+            Add(item.Key, item.Value);
+        }
+
+        public void Clear()
+        {
+            this.CheckMutable();
+            list.Clear();
+            map.Clear();
+        }
+
+        public bool Contains(KeyValuePair<TKey, TValue> item)
+        {
+            TValue value;
+            return TryGetValue(item.Key, out value)
+                && EqualityComparer<TValue>.Default.Equals(item.Value, value);
+        }
+
+        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+        {
+            list.CopyTo(array, arrayIndex);
+        }
+
+        public bool Remove(KeyValuePair<TKey, TValue> item)
+        {
+            this.CheckMutable();
+            return Remove(item.Key);
+        }
+
+        public int Count { get { return list.Count; } }
+        public bool IsReadOnly { get { return frozen; } }
+
+        public void Freeze()
+        {
+            if (IsFrozen)
+            {
+                return;
+            }
+            frozen = true;
+            // Only values can be frozen, as all the key types are simple.
+            // Everything can be done in-place, as we're just freezing objects.
+            if (typeof(IFreezable).IsAssignableFrom(typeof(TValue)))
+            {
+                for (var node = list.First; node != null; node = node.Next)
+                {
+                    var pair = node.Value;
+                    IFreezable freezableValue = pair.Value as IFreezable;
+                    if (freezableValue != null)
+                    {
+                        freezableValue.Freeze();
+                    }
+                }
+            }
+        }
+
+        public bool IsFrozen { get { return frozen; } }
+
+        public override bool Equals(object other)
+        {
+            return Equals(other as MapField<TKey, TValue>);
+        }
+
+        public override int GetHashCode()
+        {
+            var valueComparer = EqualityComparer<TValue>.Default;
+            int hash = 0;
+            foreach (var pair in list)
+            {
+                hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value);
+            }
+            return hash;
+        }
+
+        public bool Equals(MapField<TKey, TValue> other)
+        {
+            if (other == null)
+            {
+                return false;
+            }
+            if (other == this)
+            {
+                return true;
+            }
+            if (other.Count != this.Count)
+            {
+                return false;
+            }
+            var valueComparer = EqualityComparer<TValue>.Default;
+            foreach (var pair in this)
+            {
+                TValue value;
+                if (!other.TryGetValue(pair.Key, out value))
+                {
+                    return false;
+                }
+                if (!valueComparer.Equals(value, pair.Value))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void AddEntriesFrom(CodedInputStream input, Codec codec)            
+        {
+            // TODO: Peek at the next tag and see if it's the same. If it is, we can reuse the entry object...
+            var adapter = new Codec.MessageAdapter(codec);
+            adapter.Reset();
+            input.ReadMessage(adapter);
+            this[adapter.Key] = adapter.Value;
+        }
+
+        public void WriteTo(CodedOutputStream output, 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);
+            }
+        }
+
+        public int CalculateSize(Codec codec)
+        {
+            var message = new Codec.MessageAdapter(codec);
+            int size = 0;
+            foreach (var entry in list)
+            {
+                message.Key = entry.Key;
+                message.Value = entry.Value;
+                size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag);
+                size += CodedOutputStream.ComputeMessageSize(message);
+            }
+            return size;
+        }
+
+        /// <summary>
+        /// A codec for a specific map field. This contains all the information required to encoded and
+        /// decode the nested messages.
+        /// </summary>
+        public sealed class Codec
+        {
+            private readonly FieldCodec<TKey> keyCodec;
+            private readonly FieldCodec<TValue> valueCodec;
+            private readonly uint mapTag;
+
+            public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)
+            {
+                this.keyCodec = keyCodec;
+                this.valueCodec = valueCodec;
+                this.mapTag = mapTag;
+            }
+
+            /// <summary>
+            /// The tag used in the enclosing message to indicate map entries.
+            /// </summary>
+            internal uint MapTag { get { return mapTag; } }
+
+            /// <summary>
+            /// A mutable message class, used for parsing and serializing. This
+            /// delegates the work to a codec, but implements the <see cref="IMessage"/> interface
+            /// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>.
+            /// This is nested inside Codec as it's tightly coupled to the associated codec,
+            /// and it's simpler if it has direct access to all its fields.
+            /// </summary>
+            internal class MessageAdapter : IMessage
+            {
+                private readonly Codec codec;
+                internal TKey Key { get; set; }
+                internal TValue Value { get; set; }
+                internal int Size { get; set; }
+
+                internal MessageAdapter(Codec codec)
+                {
+                    this.codec = codec;
+                }
+
+                internal void Reset()
+                {
+                    Key = codec.keyCodec.DefaultValue;
+                    Value = codec.valueCodec.DefaultValue;
+                }
+
+                public void MergeFrom(CodedInputStream input)
+                {
+                    uint tag;
+                    while (input.ReadTag(out tag))
+                    {
+                        if (tag == 0)
+                        {
+                            throw InvalidProtocolBufferException.InvalidTag();
+                        }
+                        if (tag == codec.keyCodec.Tag)
+                        {
+                            Key = codec.keyCodec.Read(input);
+                        }
+                        else if (tag == codec.valueCodec.Tag)
+                        {
+                            Value = codec.valueCodec.Read(input);
+                        }
+                        else if (WireFormat.IsEndGroupTag(tag))
+                        {
+                            // TODO(jonskeet): Do we need this? (Given that we don't support groups...)
+                            return;
+                        }
+                    }
+                }
+
+                public void WriteTo(CodedOutputStream output)
+                {
+                    codec.keyCodec.Write(output, Key);
+                    codec.valueCodec.Write(output, Value);
+                }
+
+                public int CalculateSize()
+                {
+                    return codec.keyCodec.CalculateSize(Key) + codec.valueCodec.CalculateSize(Value);
+                }
+            }
+        }
+    }
+}

+ 178 - 0
csharp/src/ProtocolBuffers/FieldCodec.cs

@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+
+namespace Google.Protobuf
+{
+    /// <summary>
+    /// Factory methods for <see cref="FieldCodec{T}"/>.
+    /// </summary>
+    public static class FieldCodec
+    {
+        public static FieldCodec<string> ForString(uint tag)
+        {
+            return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); 
+        }
+
+        public static FieldCodec<ByteString> ForBytes(uint tag)
+        {
+            return new FieldCodec<ByteString>(input => input.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag);
+        }
+
+        public static FieldCodec<bool> ForBool(uint tag)
+        {
+            return new FieldCodec<bool>(input => input.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.ComputeBoolSize, tag);
+        }
+
+        public static FieldCodec<int> ForInt32(uint tag)
+        {
+            return new FieldCodec<int>(input => input.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag);
+        }
+
+        public static FieldCodec<int> ForSInt32(uint tag)
+        {
+            return new FieldCodec<int>(input => input.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag);
+        }
+
+        public static FieldCodec<uint> ForFixedInt32(uint tag)
+        {
+            return new FieldCodec<uint>(input => input.ReadFixed32(), (output, value) => output.WriteFixed32(value), CodedOutputStream.ComputeFixed32Size, tag);
+        }
+
+        public static FieldCodec<uint> ForUInt32(uint tag)
+        {
+            return new FieldCodec<uint>(input => input.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag);
+        }
+
+        public static FieldCodec<long> ForInt64(uint tag)
+        {
+            return new FieldCodec<long>(input => input.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag);
+        }
+
+        public static FieldCodec<long> ForSInt64(uint tag)
+        {
+            return new FieldCodec<long>(input => input.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag);
+        }
+
+        public static FieldCodec<ulong> ForFixedInt64(uint tag)
+        {
+            return new FieldCodec<ulong>(input => input.ReadFixed64(), (output, value) => output.WriteFixed64(value), CodedOutputStream.ComputeFixed64Size, tag);
+        }
+
+        public static FieldCodec<ulong> ForUInt64(uint tag)
+        {
+            return new FieldCodec<ulong>(input => input.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag);
+        }
+
+        public static FieldCodec<float> ForFloat(uint tag)
+        {
+            return new FieldCodec<float>(input => input.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.ComputeFloatSize, tag);
+        }
+
+        public static FieldCodec<double> ForDouble(uint tag)
+        {
+            return new FieldCodec<double>(input => input.ReadFloat(), (output, value) => output.WriteDouble(value), CodedOutputStream.ComputeDoubleSize, tag);
+        }
+
+        // Enums are tricky. We can probably use expression trees to build these delegates automatically,
+        // but it's easy to generate the code fdor it.
+        public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
+        {
+            return new FieldCodec<T>(input => fromInt32(
+                input.ReadEnum()),
+                (output, value) => output.WriteEnum(toInt32(value)),
+                value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag);
+        }
+
+        public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : IMessage<T>
+        {
+            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>
+    /// An encode/decode pair for a single field. This effectively encapsulates
+    /// all the information needed to read or write the field value from/to a coded
+    /// stream.
+    /// </summary>
+    /// <remarks>
+    /// This never writes default values to the stream, and is not currently designed
+    /// to play well with packed arrays.
+    /// </remarks>
+    public sealed class FieldCodec<T>
+    {
+        private static readonly Func<T, bool> IsDefault;
+        private static readonly T Default;
+
+        static FieldCodec()
+        {
+            if (typeof(T) == typeof(string))
+            {
+                Default = (T)(object)"";
+                IsDefault = CreateDefaultValueCheck<string>(x => x.Length == 0);
+            }
+            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)));
+            }
+        }
+
+        private static Func<T, bool> CreateDefaultValueCheck<TTmp>(Func<TTmp, bool> check)
+        {
+            return (Func<T, bool>)(object)check;
+        }
+
+        private readonly Func<CodedInputStream, T> reader;
+        private readonly Action<CodedOutputStream, T> writer;
+        private readonly Func<T, int> sizeComputer;
+        private readonly uint tag;
+        private readonly int tagSize;
+
+        internal FieldCodec(
+            Func<CodedInputStream, T> reader,
+            Action<CodedOutputStream, T> writer,
+            Func<T, int> sizeComputer,
+            uint tag)
+        {
+            this.reader = reader;
+            this.writer = writer;
+            this.sizeComputer = sizeComputer;
+            this.tag = tag;
+            tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
+        }
+
+        public uint Tag { get { return tag; } }
+
+        public T DefaultValue { get { return Default; } }
+
+        public void Write(CodedOutputStream output, T value)
+        {
+            if (!IsDefault(value))
+            {
+                output.WriteTag(tag);
+                writer(output, value);
+            }
+        }
+
+        public T Read(CodedInputStream input)
+        {
+            return reader(input);
+        }
+
+        public int CalculateSize(T value)
+        {
+            return IsDefault(value) ? 0 : sizeComputer(value) + CodedOutputStream.ComputeRawVarint32Size(tag);
+        }        
+    }
+}

+ 2 - 0
csharp/src/ProtocolBuffers/ProtocolBuffers.csproj

@@ -60,6 +60,7 @@
     <Compile Include="CodedOutputStream.cs" />
     <Compile Include="CodedOutputStream.cs" />
     <Compile Include="Collections\Dictionaries.cs" />
     <Compile Include="Collections\Dictionaries.cs" />
     <Compile Include="Collections\Lists.cs" />
     <Compile Include="Collections\Lists.cs" />
+    <Compile Include="Collections\MapField.cs" />
     <Compile Include="Collections\ReadOnlyDictionary.cs" />
     <Compile Include="Collections\ReadOnlyDictionary.cs" />
     <Compile Include="Collections\RepeatedField.cs" />
     <Compile Include="Collections\RepeatedField.cs" />
     <Compile Include="Collections\RepeatedFieldExtensions.cs" />
     <Compile Include="Collections\RepeatedFieldExtensions.cs" />
@@ -84,6 +85,7 @@
     <Compile Include="Descriptors\MethodDescriptor.cs" />
     <Compile Include="Descriptors\MethodDescriptor.cs" />
     <Compile Include="Descriptors\PackageDescriptor.cs" />
     <Compile Include="Descriptors\PackageDescriptor.cs" />
     <Compile Include="Descriptors\ServiceDescriptor.cs" />
     <Compile Include="Descriptors\ServiceDescriptor.cs" />
+    <Compile Include="FieldCodec.cs" />
     <Compile Include="FrameworkPortability.cs" />
     <Compile Include="FrameworkPortability.cs" />
     <Compile Include="Freezable.cs" />
     <Compile Include="Freezable.cs" />
     <Compile Include="MessageExtensions.cs" />
     <Compile Include="MessageExtensions.cs" />

+ 2 - 0
src/Makefile.am

@@ -425,6 +425,8 @@ libprotoc_la_SOURCES =                                         \
   google/protobuf/compiler/csharp/csharp_generator.cc          \
   google/protobuf/compiler/csharp/csharp_generator.cc          \
   google/protobuf/compiler/csharp/csharp_helpers.cc            \
   google/protobuf/compiler/csharp/csharp_helpers.cc            \
   google/protobuf/compiler/csharp/csharp_helpers.h             \
   google/protobuf/compiler/csharp/csharp_helpers.h             \
+  google/protobuf/compiler/csharp/csharp_map_field.cc          \
+  google/protobuf/compiler/csharp/csharp_map_field.h           \
   google/protobuf/compiler/csharp/csharp_message.cc            \
   google/protobuf/compiler/csharp/csharp_message.cc            \
   google/protobuf/compiler/csharp/csharp_message.h             \
   google/protobuf/compiler/csharp/csharp_message.h             \
   google/protobuf/compiler/csharp/csharp_message_field.cc      \
   google/protobuf/compiler/csharp/csharp_message_field.cc      \

+ 6 - 0
src/google/protobuf/compiler/csharp/csharp_enum_field.cc

@@ -74,6 +74,12 @@ void EnumFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
     "}\n");
     "}\n");
 }
 }
 
 
+void EnumFieldGenerator::GenerateCodecCode(io::Printer* printer) {
+    printer->Print(
+        variables_,
+        "pb::FieldCodec.ForEnum($tag$, x => (int) x, x => ($type_name$) x)");
+}
+
 EnumOneofFieldGenerator::EnumOneofFieldGenerator(const FieldDescriptor* descriptor,
 EnumOneofFieldGenerator::EnumOneofFieldGenerator(const FieldDescriptor* descriptor,
 						 int fieldOrdinal)
 						 int fieldOrdinal)
   : PrimitiveOneofFieldGenerator(descriptor, fieldOrdinal) {
   : PrimitiveOneofFieldGenerator(descriptor, fieldOrdinal) {

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

@@ -46,6 +46,7 @@ class EnumFieldGenerator : public PrimitiveFieldGenerator {
   EnumFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
   EnumFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
   ~EnumFieldGenerator();
   ~EnumFieldGenerator();
 
 
+  virtual void GenerateCodecCode(io::Printer* printer);
   virtual void GenerateParsingCode(io::Printer* printer);
   virtual void GenerateParsingCode(io::Printer* printer);
   virtual void GenerateSerializationCode(io::Printer* printer);
   virtual void GenerateSerializationCode(io::Printer* printer);
   virtual void GenerateSerializedSizeCode(io::Printer* printer);
   virtual void GenerateSerializedSizeCode(io::Printer* printer);

+ 13 - 3
src/google/protobuf/compiler/csharp/csharp_field_base.cc

@@ -65,6 +65,7 @@ void FieldGeneratorBase::SetCommonFieldVariables(
     tag_bytes += ", " + SimpleItoa(tag_array[i]);
     tag_bytes += ", " + SimpleItoa(tag_array[i]);
   }
   }
 
 
+  (*variables)["tag"] = SimpleItoa(tag);
   (*variables)["tag_size"] = SimpleItoa(tag_size);
   (*variables)["tag_size"] = SimpleItoa(tag_size);
   (*variables)["tag_bytes"] = tag_bytes;
   (*variables)["tag_bytes"] = tag_bytes;
 
 
@@ -112,6 +113,11 @@ void FieldGeneratorBase::GenerateFreezingCode(io::Printer* printer) {
   // special handling for freezing, so default to not generating any code.
   // special handling for freezing, so default to not generating any code.
 }
 }
 
 
+void FieldGeneratorBase::GenerateCodecCode(io::Printer* printer) {
+    // No-op: expect this to be overridden by appropriate types.
+    // Could fail if we get called here though...
+}
+
 void FieldGeneratorBase::AddDeprecatedFlag(io::Printer* printer) {
 void FieldGeneratorBase::AddDeprecatedFlag(io::Printer* printer) {
   if (descriptor_->options().deprecated())
   if (descriptor_->options().deprecated())
   {
   {
@@ -151,12 +157,16 @@ std::string FieldGeneratorBase::name() {
 }
 }
 
 
 std::string FieldGeneratorBase::type_name() {
 std::string FieldGeneratorBase::type_name() {
-  switch (descriptor_->type()) {
+  return type_name(descriptor_);
+}
+
+std::string FieldGeneratorBase::type_name(const FieldDescriptor* descriptor) {
+  switch (descriptor->type()) {
     case FieldDescriptor::TYPE_ENUM:
     case FieldDescriptor::TYPE_ENUM:
-      return GetClassName(descriptor_->enum_type());
+      return GetClassName(descriptor->enum_type());
     case FieldDescriptor::TYPE_MESSAGE:
     case FieldDescriptor::TYPE_MESSAGE:
     case FieldDescriptor::TYPE_GROUP:
     case FieldDescriptor::TYPE_GROUP:
-      return GetClassName(descriptor_->message_type());
+      return GetClassName(descriptor->message_type());
     case FieldDescriptor::TYPE_DOUBLE:
     case FieldDescriptor::TYPE_DOUBLE:
       return "double";
       return "double";
     case FieldDescriptor::TYPE_FLOAT:
     case FieldDescriptor::TYPE_FLOAT:

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

@@ -49,6 +49,7 @@ class FieldGeneratorBase : public SourceGeneratorBase {
 
 
   virtual void GenerateCloningCode(io::Printer* printer) = 0;
   virtual void GenerateCloningCode(io::Printer* printer) = 0;
   virtual void GenerateFreezingCode(io::Printer* printer);
   virtual void GenerateFreezingCode(io::Printer* printer);
+  virtual void GenerateCodecCode(io::Printer* printer);
   virtual void GenerateMembers(io::Printer* printer) = 0;
   virtual void GenerateMembers(io::Printer* printer) = 0;
   virtual void GenerateMergingCode(io::Printer* printer) = 0;
   virtual void GenerateMergingCode(io::Printer* printer) = 0;
   virtual void GenerateParsingCode(io::Printer* printer) = 0;
   virtual void GenerateParsingCode(io::Printer* printer) = 0;
@@ -76,6 +77,7 @@ class FieldGeneratorBase : public SourceGeneratorBase {
   std::string property_name();
   std::string property_name();
   std::string name();
   std::string name();
   std::string type_name();
   std::string type_name();
+  std::string type_name(const FieldDescriptor* descriptor);
   bool has_default_value();
   bool has_default_value();
   bool is_nullable_type();
   bool is_nullable_type();
   std::string default_value();
   std::string default_value();

+ 6 - 1
src/google/protobuf/compiler/csharp/csharp_helpers.cc

@@ -46,6 +46,7 @@
 
 
 #include <google/protobuf/compiler/csharp/csharp_field_base.h>
 #include <google/protobuf/compiler/csharp/csharp_field_base.h>
 #include <google/protobuf/compiler/csharp/csharp_enum_field.h>
 #include <google/protobuf/compiler/csharp/csharp_enum_field.h>
+#include <google/protobuf/compiler/csharp/csharp_map_field.h>
 #include <google/protobuf/compiler/csharp/csharp_message_field.h>
 #include <google/protobuf/compiler/csharp/csharp_message_field.h>
 #include <google/protobuf/compiler/csharp/csharp_primitive_field.h>
 #include <google/protobuf/compiler/csharp/csharp_primitive_field.h>
 #include <google/protobuf/compiler/csharp/csharp_repeated_enum_field.h>
 #include <google/protobuf/compiler/csharp/csharp_repeated_enum_field.h>
@@ -355,7 +356,11 @@ FieldGeneratorBase* CreateFieldGenerator(const FieldDescriptor* descriptor,
     case FieldDescriptor::TYPE_GROUP:
     case FieldDescriptor::TYPE_GROUP:
     case FieldDescriptor::TYPE_MESSAGE:
     case FieldDescriptor::TYPE_MESSAGE:
       if (descriptor->is_repeated()) {
       if (descriptor->is_repeated()) {
-        return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal);
+        if (descriptor->is_map()) {
+          return new MapFieldGenerator(descriptor, fieldOrdinal);
+        } else {
+          return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal);
+        }
       } else {
       } else {
 	if (descriptor->containing_oneof()) {
 	if (descriptor->containing_oneof()) {
 	  return new MessageOneofFieldGenerator(descriptor, fieldOrdinal);
 	  return new MessageOneofFieldGenerator(descriptor, fieldOrdinal);

+ 141 - 0
src/google/protobuf/compiler/csharp/csharp_map_field.cc

@@ -0,0 +1,141 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2015 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <sstream>
+
+#include <google/protobuf/compiler/code_generator.h>
+#include <google/protobuf/compiler/plugin.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
+#include <google/protobuf/io/printer.h>
+#include <google/protobuf/io/zero_copy_stream.h>
+#include <google/protobuf/stubs/strutil.h>
+
+#include <google/protobuf/compiler/csharp/csharp_helpers.h>
+#include <google/protobuf/compiler/csharp/csharp_map_field.h>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace csharp {
+
+MapFieldGenerator::MapFieldGenerator(const FieldDescriptor* descriptor,
+                                             int fieldOrdinal)
+    : FieldGeneratorBase(descriptor, fieldOrdinal) {
+}
+
+MapFieldGenerator::~MapFieldGenerator() {
+}
+
+void MapFieldGenerator::GenerateMembers(io::Printer* printer) {   
+  const FieldDescriptor* key_descriptor =
+      descriptor_->message_type()->FindFieldByName("key");
+  const FieldDescriptor* value_descriptor =
+      descriptor_->message_type()->FindFieldByName("value");
+  variables_["key_type_name"] = type_name(key_descriptor);
+  variables_["value_type_name"] = type_name(value_descriptor);
+  scoped_ptr<FieldGeneratorBase> key_generator(CreateFieldGenerator(key_descriptor, 1));  
+  scoped_ptr<FieldGeneratorBase> value_generator(CreateFieldGenerator(value_descriptor, 2));
+
+  printer->Print(
+    variables_,
+    "private static readonly pbc::MapField<$key_type_name$, $value_type_name$>.Codec _map_$name$_codec\n"
+    "    = new pbc::MapField<$key_type_name$, $value_type_name$>.Codec(");
+  key_generator->GenerateCodecCode(printer);
+  printer->Print(", ");
+  value_generator->GenerateCodecCode(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");
+  AddDeprecatedFlag(printer);
+  printer->Print(
+    variables_,
+    "public pbc::MapField<$key_type_name$, $value_type_name$> $property_name$ {\n"
+    "  get { return $name$_; }\n"
+    "}\n");
+}
+
+void MapFieldGenerator::GenerateMergingCode(io::Printer* printer) {
+  printer->Print(
+      variables_,
+      "$name$_.Add(other.$name$_);\n");
+}
+
+void MapFieldGenerator::GenerateParsingCode(io::Printer* printer) {
+  printer->Print(
+    variables_,
+    "$name$_.AddEntriesFrom(input, _map_$name$_codec);\n");
+}
+
+void MapFieldGenerator::GenerateSerializationCode(io::Printer* printer) {
+  printer->Print(
+    variables_,
+    "$name$_.WriteTo(output, _map_$name$_codec);\n");
+}
+
+void MapFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
+  printer->Print(
+    variables_,
+    "size += $name$_.CalculateSize(_map_$name$_codec);\n");
+}
+
+void MapFieldGenerator::WriteHash(io::Printer* printer) {
+  printer->Print(
+    variables_,
+    "hash ^= $property_name$.GetHashCode();\n");
+}
+void MapFieldGenerator::WriteEquals(io::Printer* printer) {
+  printer->Print(
+    variables_,
+    "if (!$property_name$.Equals(other.$property_name$)) return false;\n");
+}
+void MapFieldGenerator::WriteToString(io::Printer* printer) {
+    /*
+  variables_["field_name"] = GetFieldName(descriptor_);
+  printer->Print(
+    variables_,
+    "PrintField(\"$field_name$\", has$property_name$, $name$_, writer);\n");*/
+}
+
+void MapFieldGenerator::GenerateCloningCode(io::Printer* printer) {
+  printer->Print(variables_,
+    "$name$_ = other.$name$_.Clone();\n");
+}
+
+void MapFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
+  printer->Print(variables_,
+    "$name$_.Freeze();\n");
+}
+
+}  // namespace csharp
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google

+ 71 - 0
src/google/protobuf/compiler/csharp/csharp_map_field.h

@@ -0,0 +1,71 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__
+#define GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__
+
+#include <string>
+
+#include <google/protobuf/compiler/code_generator.h>
+#include <google/protobuf/compiler/csharp/csharp_field_base.h>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace csharp {
+
+class MapFieldGenerator : public FieldGeneratorBase {
+ public:
+  MapFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
+  ~MapFieldGenerator();
+
+  virtual void GenerateCloningCode(io::Printer* printer);
+  virtual void GenerateFreezingCode(io::Printer* printer);
+  virtual void GenerateMembers(io::Printer* printer);
+  virtual void GenerateMergingCode(io::Printer* printer);
+  virtual void GenerateParsingCode(io::Printer* printer);
+  virtual void GenerateSerializationCode(io::Printer* printer);
+  virtual void GenerateSerializedSizeCode(io::Printer* printer);
+
+  virtual void WriteHash(io::Printer* printer);
+  virtual void WriteEquals(io::Printer* printer);
+  virtual void WriteToString(io::Printer* printer);
+
+ private:
+  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MapFieldGenerator);
+};
+
+}  // namespace csharp
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__
+

+ 1 - 5
src/google/protobuf/compiler/csharp/csharp_message.cc

@@ -268,8 +268,6 @@ void MessageGenerator::Generate(io::Printer* printer) {
       "}\n\n");
       "}\n\n");
   }
   }
 
 
-  // TODO(jonskeet): Map properties
-
   // Standard methods
   // Standard methods
   GenerateFrameworkMethods(printer);
   GenerateFrameworkMethods(printer);
   GenerateMessageSerializationMethods(printer);
   GenerateMessageSerializationMethods(printer);
@@ -299,7 +297,6 @@ void MessageGenerator::Generate(io::Printer* printer) {
   printer->Outdent();
   printer->Outdent();
   printer->Print("}\n");
   printer->Print("}\n");
   printer->Print("\n");
   printer->Print("\n");
-
 }
 }
 
 
 void MessageGenerator::GenerateCloningCode(io::Printer* printer) {
 void MessageGenerator::GenerateCloningCode(io::Printer* printer) {
@@ -451,7 +448,7 @@ void MessageGenerator::GenerateMessageSerializationMethods(io::Printer* printer)
   }
   }
   printer->Print("return size;\n");
   printer->Print("return size;\n");
   printer->Outdent();
   printer->Outdent();
-  printer->Print("}\n");
+  printer->Print("}\n\n");
 }
 }
 
 
 void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
 void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
@@ -469,7 +466,6 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
     "if (other == null) {\n"
     "if (other == null) {\n"
     "  return;\n"
     "  return;\n"
     "}\n");
     "}\n");
-  // TODO(jonskeet): Maps?
   // Merge non-oneof fields
   // Merge non-oneof fields
   for (int i = 0; i < descriptor_->field_count(); i++) {
   for (int i = 0; i < descriptor_->field_count(); i++) {
     if (!descriptor_->field(i)->containing_oneof()) {      
     if (!descriptor_->field(i)->containing_oneof()) {      

+ 6 - 0
src/google/protobuf/compiler/csharp/csharp_message_field.cc

@@ -138,6 +138,12 @@ void MessageFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
     "if ($has_property_check$) $property_name$.Freeze();\n");
     "if ($has_property_check$) $property_name$.Freeze();\n");
 }
 }
 
 
+void MessageFieldGenerator::GenerateCodecCode(io::Printer* printer) {
+  printer->Print(
+    variables_,
+    "pb::FieldCodec.ForMessage($tag$, $type_name$.Parser)");
+}
+
 MessageOneofFieldGenerator::MessageOneofFieldGenerator(const FieldDescriptor* descriptor,
 MessageOneofFieldGenerator::MessageOneofFieldGenerator(const FieldDescriptor* descriptor,
 						       int fieldOrdinal)
 						       int fieldOrdinal)
     : MessageFieldGenerator(descriptor, fieldOrdinal) {
     : MessageFieldGenerator(descriptor, fieldOrdinal) {

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

@@ -46,6 +46,7 @@ class MessageFieldGenerator : public FieldGeneratorBase {
   MessageFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
   MessageFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
   ~MessageFieldGenerator();
   ~MessageFieldGenerator();
 
 
+  virtual void GenerateCodecCode(io::Printer* printer);
   virtual void GenerateCloningCode(io::Printer* printer);
   virtual void GenerateCloningCode(io::Printer* printer);
   virtual void GenerateFreezingCode(io::Printer* printer);
   virtual void GenerateFreezingCode(io::Printer* printer);
   virtual void GenerateMembers(io::Printer* printer);
   virtual void GenerateMembers(io::Printer* printer);

+ 7 - 1
src/google/protobuf/compiler/csharp/csharp_primitive_field.cc

@@ -129,7 +129,7 @@ void PrimitiveFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
       "size += $tag_size$ + $fixed_size$;\n",
       "size += $tag_size$ + $fixed_size$;\n",
       "fixed_size", SimpleItoa(fixedSize),
       "fixed_size", SimpleItoa(fixedSize),
       "tag_size", variables_["tag_size"]);
       "tag_size", variables_["tag_size"]);
-  }    
+  }
   printer->Outdent();
   printer->Outdent();
   printer->Print("}\n");
   printer->Print("}\n");
 }
 }
@@ -155,6 +155,12 @@ void PrimitiveFieldGenerator::GenerateCloningCode(io::Printer* printer) {
     "$name$_ = other.$name$_;\n");
     "$name$_ = other.$name$_;\n");
 }
 }
 
 
+void PrimitiveFieldGenerator::GenerateCodecCode(io::Printer* printer) {
+  printer->Print(
+    variables_,
+    "pb::FieldCodec.For$capitalized_type_name$($tag$)");
+}
+
 PrimitiveOneofFieldGenerator::PrimitiveOneofFieldGenerator(
 PrimitiveOneofFieldGenerator::PrimitiveOneofFieldGenerator(
     const FieldDescriptor* descriptor, int fieldOrdinal)
     const FieldDescriptor* descriptor, int fieldOrdinal)
     : PrimitiveFieldGenerator(descriptor, fieldOrdinal) {
     : PrimitiveFieldGenerator(descriptor, fieldOrdinal) {

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

@@ -46,6 +46,7 @@ class PrimitiveFieldGenerator : public FieldGeneratorBase {
   PrimitiveFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
   PrimitiveFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
   ~PrimitiveFieldGenerator();
   ~PrimitiveFieldGenerator();
 
 
+  virtual void GenerateCodecCode(io::Printer* printer);
   virtual void GenerateCloningCode(io::Printer* printer);
   virtual void GenerateCloningCode(io::Printer* printer);
   virtual void GenerateMembers(io::Printer* printer);
   virtual void GenerateMembers(io::Printer* printer);
   virtual void GenerateMergingCode(io::Printer* printer);
   virtual void GenerateMergingCode(io::Printer* printer);

+ 5 - 0
src/google/protobuf/unittest_proto3.proto

@@ -140,6 +140,11 @@ message TestAllTypes {
     string oneof_string = 113;
     string oneof_string = 113;
     bytes oneof_bytes = 114;
     bytes oneof_bytes = 114;
   }
   }
+  
+  // Sample maps
+  map<string, NestedMessage> map_string_to_nested_message = 200;
+  map<int32, bytes> map_int32_to_bytes = 201;
+  map<bool, NestedEnum> map_bool_to_enum = 202;
 }
 }
 
 
 // This proto includes a recusively nested message.
 // This proto includes a recusively nested message.