Merge pull request #694 from jskeet/groups

Fix groups handling in C#
diff --git a/csharp/src/AddressBook/Addressbook.cs b/csharp/src/AddressBook/Addressbook.cs
index 85fa297..f2be5ba 100644
--- a/csharp/src/AddressBook/Addressbook.cs
+++ b/csharp/src/AddressBook/Addressbook.cs
@@ -189,10 +189,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -335,10 +332,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 10: {
                 Number = input.ReadString();
@@ -441,10 +435,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             people_.AddEntriesFrom(input, _repeated_people_codec);
diff --git a/csharp/src/Google.Protobuf.Conformance/Conformance.cs b/csharp/src/Google.Protobuf.Conformance/Conformance.cs
index 50a6756..e905d4e 100644
--- a/csharp/src/Google.Protobuf.Conformance/Conformance.cs
+++ b/csharp/src/Google.Protobuf.Conformance/Conformance.cs
@@ -321,10 +321,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             ProtobufPayload = input.ReadBytes();
@@ -557,10 +554,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             ParseError = input.ReadString();
@@ -1829,10 +1823,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             OptionalInt32 = input.ReadInt32();
@@ -2256,10 +2247,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 8: {
                 A = input.ReadInt32();
@@ -2373,10 +2361,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             C = input.ReadInt32();
diff --git a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs
index c4c92ef..42c740a 100644
--- a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs
+++ b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs
@@ -442,5 +442,92 @@
             var input = new CodedInputStream(new byte[] { 0 });

             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadTag());

         }

+

+        [Test]

+        public void SkipGroup()

+        {

+            // Create an output stream with a group in:

+            // Field 1: string "field 1"

+            // Field 2: group containing:

+            //   Field 1: fixed int32 value 100

+            //   Field 2: string "ignore me"

+            //   Field 3: nested group containing

+            //      Field 1: fixed int64 value 1000

+            // Field 3: string "field 3"

+            var stream = new MemoryStream();

+            var output = new CodedOutputStream(stream);

+            output.WriteTag(1, WireFormat.WireType.LengthDelimited);

+            output.WriteString("field 1");

+            

+            // The outer group...

+            output.WriteTag(2, WireFormat.WireType.StartGroup);

+            output.WriteTag(1, WireFormat.WireType.Fixed32);

+            output.WriteFixed32(100);

+            output.WriteTag(2, WireFormat.WireType.LengthDelimited);

+            output.WriteString("ignore me");

+            // The nested group...

+            output.WriteTag(3, WireFormat.WireType.StartGroup);

+            output.WriteTag(1, WireFormat.WireType.Fixed64);

+            output.WriteFixed64(1000);

+            // Note: Not sure the field number is relevant for end group...

+            output.WriteTag(3, WireFormat.WireType.EndGroup);

+

+            // End the outer group

+            output.WriteTag(2, WireFormat.WireType.EndGroup);

+

+            output.WriteTag(3, WireFormat.WireType.LengthDelimited);

+            output.WriteString("field 3");

+            output.Flush();

+            stream.Position = 0;

+

+            // Now act like a generated client

+            var input = new CodedInputStream(stream);

+            Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited), input.ReadTag());

+            Assert.AreEqual("field 1", input.ReadString());

+            Assert.AreEqual(WireFormat.MakeTag(2, WireFormat.WireType.StartGroup), input.ReadTag());

+            input.SkipLastField(); // Should consume the whole group, including the nested one.

+            Assert.AreEqual(WireFormat.MakeTag(3, WireFormat.WireType.LengthDelimited), input.ReadTag());

+            Assert.AreEqual("field 3", input.ReadString());

+        }

+

+        [Test]

+        public void EndOfStreamReachedWhileSkippingGroup()

+        {

+            var stream = new MemoryStream();

+            var output = new CodedOutputStream(stream);

+            output.WriteTag(1, WireFormat.WireType.StartGroup);

+            output.WriteTag(2, WireFormat.WireType.StartGroup);

+            output.WriteTag(2, WireFormat.WireType.EndGroup);

+

+            output.Flush();

+            stream.Position = 0;

+

+            // Now act like a generated client

+            var input = new CodedInputStream(stream);

+            input.ReadTag();

+            Assert.Throws<InvalidProtocolBufferException>(() => input.SkipLastField());

+        }

+

+        [Test]

+        public void RecursionLimitAppliedWhileSkippingGroup()

+        {

+            var stream = new MemoryStream();

+            var output = new CodedOutputStream(stream);

+            for (int i = 0; i < CodedInputStream.DefaultRecursionLimit + 1; i++)

+            {

+                output.WriteTag(1, WireFormat.WireType.StartGroup);

+            }

+            for (int i = 0; i < CodedInputStream.DefaultRecursionLimit + 1; i++)

+            {

+                output.WriteTag(1, WireFormat.WireType.EndGroup);

+            }

+            output.Flush();

+            stream.Position = 0;

+

+            // Now act like a generated client

+            var input = new CodedInputStream(stream);

+            Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.StartGroup), input.ReadTag());

+            Assert.Throws<InvalidProtocolBufferException>(() => input.SkipLastField());

+        }

     }

 }
\ No newline at end of file
diff --git a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
index 6fdd106..575d458 100644
--- a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
+++ b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
@@ -629,5 +629,27 @@
             var data = new byte[] { 130, 3, 1 };            

             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(data));

         }

+

+        /// <summary>

+        /// Demonstrates current behaviour with an extraneous end group tag - see issue 688

+        /// for details; we may want to change this.

+        /// </summary>

+        [Test]

+        public void ExtraEndGroupSkipped()

+        {

+            var message = SampleMessages.CreateFullTestAllTypes();

+            var stream = new MemoryStream();

+            var output = new CodedOutputStream(stream);

+

+            output.WriteTag(100, WireFormat.WireType.EndGroup);

+            output.WriteTag(TestAllTypes.SingleFixed32FieldNumber, WireFormat.WireType.Fixed32);

+            output.WriteFixed32(123);

+

+            output.Flush();

+

+            stream.Position = 0;

+            var parsed = TestAllTypes.Parser.ParseFrom(stream);

+            Assert.AreEqual(new TestAllTypes { SingleFixed32 = 123 }, parsed);

+        }

     }

 }

diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs b/csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs
index f6835c4..e9e1819 100644
--- a/csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs
+++ b/csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs
@@ -476,10 +476,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             mapInt32Int32_.AddEntriesFrom(input, _map_mapInt32Int32_codec);
@@ -648,10 +645,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             if (testMap_ == null) {
@@ -748,10 +742,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             mapInt32Message_.AddEntriesFrom(input, _map_mapInt32Message_codec);
@@ -859,10 +850,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             map1_.AddEntriesFrom(input, _map_map1_codec);
@@ -1156,10 +1144,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             mapInt32Int32_.AddEntriesFrom(input, _map_mapInt32Int32_codec);
@@ -1309,10 +1294,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             type_.AddEntriesFrom(input, _map_type_codec);
@@ -1416,10 +1398,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             entry_.AddEntriesFrom(input, _map_entry_codec);
diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs
index 646a01a..bf527ac 100644
--- a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs
+++ b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportProto3.cs
@@ -139,10 +139,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             D = input.ReadInt32();
diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs
index 225775a..ec46090 100644
--- a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs
+++ b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestImportPublicProto3.cs
@@ -125,10 +125,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             E = input.ReadInt32();
diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs
index 1bf40ea..63119a3 100644
--- a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs
+++ b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestIssues.cs
@@ -142,10 +142,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -222,10 +219,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
             }
           }
@@ -302,10 +296,7 @@
               while ((tag = input.ReadTag()) != 0) {
                 switch(tag) {
                   default:
-                    if (pb::WireFormat.IsEndGroupTag(tag)) {
-                      return;
-                    }
-                    input.ConsumeLastField();
+                    input.SkipLastField();
                     break;
                 }
               }
@@ -441,10 +432,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             value_ = (global::UnitTest.Issues.TestProtos.NegativeEnum) input.ReadEnum();
@@ -534,10 +522,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -730,10 +715,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             PrimitiveValue = input.ReadInt32();
@@ -860,10 +842,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Item = input.ReadInt32();
@@ -987,10 +966,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Types_ = input.ReadInt32();
@@ -1075,10 +1051,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
             }
           }
@@ -1343,10 +1316,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             PlainString = input.ReadString();
diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs
index 58e5be6..bf4590a 100644
--- a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs
+++ b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestProto3.cs
@@ -1211,10 +1211,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             SingleInt32 = input.ReadInt32();
@@ -1546,10 +1543,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 8: {
                 Bb = input.ReadInt32();
@@ -1698,10 +1692,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             if (child_ == null) {
@@ -1818,10 +1809,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             DeprecatedInt32 = input.ReadInt32();
@@ -1923,10 +1911,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             C = input.ReadInt32();
@@ -2006,10 +1991,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -2110,10 +2092,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             if (foreignNested_ == null) {
@@ -2240,10 +2219,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             A = input.ReadInt32();
@@ -2374,10 +2350,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             if (a_ == null) {
@@ -2489,10 +2462,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             if (bb_ == null) {
@@ -2622,10 +2592,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             if (a_ == null) {
@@ -2859,10 +2826,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             PrimitiveField = input.ReadInt32();
@@ -3066,10 +3030,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             MyInt = input.ReadInt64();
@@ -3209,10 +3170,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 8: {
                 Bb = input.ReadInt32();
@@ -3323,10 +3281,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             sparseEnum_ = (global::Google.Protobuf.TestProtos.TestSparseEnum) input.ReadEnum();
@@ -3428,10 +3383,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Data = input.ReadString();
@@ -3525,10 +3477,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             data_.AddEntriesFrom(input, _repeated_data_codec);
@@ -3630,10 +3579,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Data = input.ReadBytes();
@@ -3735,10 +3681,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Data = input.ReadBytes();
@@ -3840,10 +3783,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Data = input.ReadInt32();
@@ -3945,10 +3885,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Data = input.ReadUInt32();
@@ -4050,10 +3987,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Data = input.ReadInt64();
@@ -4155,10 +4089,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Data = input.ReadUInt64();
@@ -4260,10 +4191,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Data = input.ReadBool();
@@ -4438,10 +4366,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             FooInt = input.ReadInt32();
@@ -4730,10 +4655,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 722:
           case 720: {
@@ -5075,10 +4997,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 722:
           case 720: {
@@ -5308,10 +5227,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 98:
           case 101: {
@@ -5439,10 +5355,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             A = input.ReadString();
@@ -5522,10 +5435,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -5601,10 +5511,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -5680,10 +5587,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -5759,10 +5663,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -5838,10 +5739,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
@@ -5917,10 +5815,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestWellKnownTypes.cs b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestWellKnownTypes.cs
index 0840fa2..16634e0 100644
--- a/csharp/src/Google.Protobuf.Test/TestProtos/UnittestWellKnownTypes.cs
+++ b/csharp/src/Google.Protobuf.Test/TestProtos/UnittestWellKnownTypes.cs
@@ -679,10 +679,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             if (anyField_ == null) {
@@ -1136,10 +1133,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             anyField_.AddEntriesFrom(input, _repeated_anyField_codec);
@@ -1757,10 +1751,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             global::Google.Protobuf.WellKnownTypes.Any subBuilder = new global::Google.Protobuf.WellKnownTypes.Any();
@@ -2205,10 +2196,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             anyField_.AddEntriesFrom(input, _map_anyField_codec);
diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
index d2a0705..fbc0ff0 100644
--- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
+++ b/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
@@ -322,5 +322,28 @@
             // A normal implementation would have 0 now, as the explicit default would have been overwritten the 5.
             Assert.AreEqual(5, message.Int32Field);
         }
+
+        [Test]
+        public void UnknownFieldInWrapper()
+        {
+            var stream = new MemoryStream();
+            var output = new CodedOutputStream(stream);
+            var wrapperTag = WireFormat.MakeTag(TestWellKnownTypes.Int32FieldFieldNumber, WireFormat.WireType.LengthDelimited);
+            var unknownTag = WireFormat.MakeTag(15, WireFormat.WireType.Varint);
+            var valueTag = WireFormat.MakeTag(Int32Value.ValueFieldNumber, WireFormat.WireType.Varint);
+
+            output.WriteTag(wrapperTag);
+            output.WriteLength(4); // unknownTag + value 5 + valueType + value 6, each 1 byte
+            output.WriteTag(unknownTag);
+            output.WriteInt32((int) valueTag); // Sneakily "pretend" it's a tag when it's really a value
+            output.WriteTag(valueTag);
+            output.WriteInt32(6);
+
+            output.Flush();
+            stream.Position = 0;
+            
+            var message = TestWellKnownTypes.Parser.ParseFrom(stream);
+            Assert.AreEqual(6, message.Int32Field);
+        }
     }
 }
diff --git a/csharp/src/Google.Protobuf/CodedInputStream.cs b/csharp/src/Google.Protobuf/CodedInputStream.cs
index 0e2495f..dcd19e4 100644
--- a/csharp/src/Google.Protobuf/CodedInputStream.cs
+++ b/csharp/src/Google.Protobuf/CodedInputStream.cs
@@ -236,17 +236,16 @@
 

         #region Validation

         /// <summary>

-        /// Verifies that the last call to ReadTag() returned the given tag value.

-        /// This is used to verify that a nested group ended with the correct

-        /// end tag.

+        /// Verifies that the last call to ReadTag() returned tag 0 - in other words,

+        /// we've reached the end of the stream when we expected to.

         /// </summary>

-        /// <exception cref="InvalidProtocolBufferException">The last

+        /// <exception cref="InvalidProtocolBufferException">The 

         /// tag read was not the one specified</exception>

-        internal void CheckLastTagWas(uint value)

+        internal void CheckReadEndOfStreamTag()

         {

-            if (lastTag != value)

+            if (lastTag != 0)

             {

-                throw InvalidProtocolBufferException.InvalidEndTag();

+                throw InvalidProtocolBufferException.MoreDataAvailable();

             }

         }

         #endregion

@@ -275,6 +274,11 @@
         /// <summary>

         /// Reads a field tag, returning the tag of 0 for "end of stream".

         /// </summary>

+        /// <remarks>

+        /// If this method returns 0, it doesn't necessarily mean the end of all

+        /// the data in this CodedInputStream; it may be the end of the logical stream

+        /// for an embedded message, for example.

+        /// </remarks>

         /// <returns>The next field tag, or 0 for end of stream. (0 is never a valid tag.)</returns>

         public uint ReadTag()

         {

@@ -329,22 +333,24 @@
         }

 

         /// <summary>

-        /// Consumes the data for the field with the tag we've just read.

+        /// Skips the data for the field with the tag we've just read.

         /// This should be called directly after <see cref="ReadTag"/>, when

         /// the caller wishes to skip an unknown field.

         /// </summary>

-        public void ConsumeLastField()

+        public void SkipLastField()

         {

             if (lastTag == 0)

             {

-                throw new InvalidOperationException("ConsumeLastField cannot be called at the end of a stream");

+                throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");

             }

             switch (WireFormat.GetTagWireType(lastTag))

             {

                 case WireFormat.WireType.StartGroup:

+                    SkipGroup();

+                    break;

                 case WireFormat.WireType.EndGroup:

-                    // TODO: Work out how to skip them instead? See issue 688.

-                    throw new InvalidProtocolBufferException("Group tags not supported by proto3 C# implementation");

+                    // Just ignore; there's no data following the tag.

+                    break;

                 case WireFormat.WireType.Fixed32:

                     ReadFixed32();

                     break;

@@ -361,6 +367,29 @@
             }

         }

 

+        private void SkipGroup()

+        {

+            // Note: Currently we expect this to be the way that groups are read. We could put the recursion

+            // depth changes into the ReadTag method instead, potentially...

+            recursionDepth++;

+            if (recursionDepth >= recursionLimit)

+            {

+                throw InvalidProtocolBufferException.RecursionLimitExceeded();

+            }

+            uint tag;

+            do

+            {

+                tag = ReadTag();

+                if (tag == 0)

+                {

+                    throw InvalidProtocolBufferException.TruncatedMessage();

+                }

+                // This recursion will allow us to handle nested groups.

+                SkipLastField();

+            } while (WireFormat.GetTagWireType(tag) != WireFormat.WireType.EndGroup);

+            recursionDepth--;

+        }

+

         /// <summary>

         /// Reads a double field from the stream.

         /// </summary>

@@ -475,7 +504,7 @@
             int oldLimit = PushLimit(length);

             ++recursionDepth;

             builder.MergeFrom(this);

-            CheckLastTagWas(0);

+            CheckReadEndOfStreamTag();

             // Check that we've read exactly as much data as expected.

             if (!ReachedLimit)

             {

diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs
index 5eb2c2f..dc4b04c 100644
--- a/csharp/src/Google.Protobuf/Collections/MapField.cs
+++ b/csharp/src/Google.Protobuf/Collections/MapField.cs
@@ -637,10 +637,9 @@
                         {
                             Value = codec.valueCodec.Read(input);
                         }
-                        else if (WireFormat.IsEndGroupTag(tag))
+                        else 
                         {
-                            // TODO(jonskeet): Do we need this? (Given that we don't support groups...)
-                            return;
+                            input.SkipLastField();
                         }
                     }
                 }
diff --git a/csharp/src/Google.Protobuf/FieldCodec.cs b/csharp/src/Google.Protobuf/FieldCodec.cs
index 15d52c7..20a1f43 100644
--- a/csharp/src/Google.Protobuf/FieldCodec.cs
+++ b/csharp/src/Google.Protobuf/FieldCodec.cs
@@ -304,12 +304,13 @@
                     {
                         value = codec.Read(input);
                     }
-                    if (WireFormat.IsEndGroupTag(tag))
+                    else
                     {
-                        break;
+                        input.SkipLastField();
                     }
+
                 }
-                input.CheckLastTagWas(0);
+                input.CheckReadEndOfStreamTag();
                 input.PopLimit(oldLimit);
 
                 return value;
diff --git a/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs b/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs
index 6905a6a..01d5539 100644
--- a/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs
+++ b/csharp/src/Google.Protobuf/InvalidProtocolBufferException.cs
@@ -45,6 +45,12 @@
         {

         }

 

+        internal static InvalidProtocolBufferException MoreDataAvailable()

+        {

+            return new InvalidProtocolBufferException(

+                "Completed reading a message while more data was available in the stream.");

+        }

+

         internal static InvalidProtocolBufferException TruncatedMessage()

         {

             return new InvalidProtocolBufferException(

diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs
index ee78dc8..d2d057c 100644
--- a/csharp/src/Google.Protobuf/MessageExtensions.cs
+++ b/csharp/src/Google.Protobuf/MessageExtensions.cs
@@ -50,7 +50,7 @@
             Preconditions.CheckNotNull(data, "data");
             CodedInputStream input = new CodedInputStream(data);
             message.MergeFrom(input);
-            input.CheckLastTagWas(0);
+            input.CheckReadEndOfStreamTag();
         }
 
         /// <summary>
@@ -64,7 +64,7 @@
             Preconditions.CheckNotNull(data, "data");
             CodedInputStream input = data.CreateCodedInput();
             message.MergeFrom(input);
-            input.CheckLastTagWas(0);
+            input.CheckReadEndOfStreamTag();
         }
 
         /// <summary>
@@ -78,7 +78,7 @@
             Preconditions.CheckNotNull(input, "input");
             CodedInputStream codedInput = new CodedInputStream(input);
             message.MergeFrom(codedInput);
-            codedInput.CheckLastTagWas(0);
+            codedInput.CheckReadEndOfStreamTag();
         }
 
         /// <summary>
diff --git a/csharp/src/Google.Protobuf/Reflection/DescriptorProtoFile.cs b/csharp/src/Google.Protobuf/Reflection/DescriptorProtoFile.cs
index d66bdb8..59c5e69 100644
--- a/csharp/src/Google.Protobuf/Reflection/DescriptorProtoFile.cs
+++ b/csharp/src/Google.Protobuf/Reflection/DescriptorProtoFile.cs
@@ -242,10 +242,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             file_.AddEntriesFrom(input, _repeated_file_codec);
@@ -539,10 +536,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -833,10 +827,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -1000,10 +991,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 8: {
                 Start = input.ReadInt32();
@@ -1131,10 +1119,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 8: {
                 Start = input.ReadInt32();
@@ -1424,10 +1409,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -1597,10 +1579,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -1741,10 +1720,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -1904,10 +1880,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -2059,10 +2032,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -2288,10 +2258,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -2716,10 +2683,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             JavaPackage = input.ReadString();
@@ -2969,10 +2933,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             MessageSetWireFormat = input.ReadBool();
@@ -3214,10 +3175,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             ctype_ = (global::Google.Protobuf.Reflection.FieldOptions.Types.CType) input.ReadEnum();
@@ -3397,10 +3355,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 16: {
             AllowAlias = input.ReadBool();
@@ -3524,10 +3479,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Deprecated = input.ReadBool();
@@ -3647,10 +3599,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 264: {
             Deprecated = input.ReadBool();
@@ -3770,10 +3719,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 264: {
             Deprecated = input.ReadBool();
@@ -4003,10 +3949,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 18: {
             name_.AddEntriesFrom(input, _repeated_name_codec);
@@ -4155,10 +4098,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 10: {
                 NamePart_ = input.ReadString();
@@ -4261,10 +4201,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             location_.AddEntriesFrom(input, _repeated_location_codec);
@@ -4431,10 +4368,7 @@
           while ((tag = input.ReadTag()) != 0) {
             switch(tag) {
               default:
-                if (pb::WireFormat.IsEndGroupTag(tag)) {
-                  return;
-                }
-                input.ConsumeLastField();
+                input.SkipLastField();
                 break;
               case 10:
               case 8: {
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs
index 9fc653b..204b37c 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Any.cs
@@ -150,10 +150,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             TypeUrl = input.ReadString();
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Api.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Api.cs
index 8a94e7b..a5f9509 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Api.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Api.cs
@@ -213,10 +213,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -439,10 +436,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Duration.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Duration.cs
index d74636d..aa34f2d 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Duration.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Duration.cs
@@ -151,10 +151,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Seconds = input.ReadInt64();
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Empty.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Empty.cs
index 0f1d7f5..7d699c1 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Empty.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Empty.cs
@@ -106,10 +106,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
         }
       }
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs
index 9bd47a9..0dac440 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMask.cs
@@ -120,10 +120,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             paths_.AddEntriesFrom(input, _repeated_paths_codec);
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/SourceContext.cs b/csharp/src/Google.Protobuf/WellKnownTypes/SourceContext.cs
index ae79884..8347999 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/SourceContext.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/SourceContext.cs
@@ -129,10 +129,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             FileName = input.ReadString();
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs
index ea8b105..1e8a823 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Struct.cs
@@ -139,10 +139,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             fields_.AddEntriesFrom(input, _map_fields_codec);
@@ -392,10 +389,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             kind_ = input.ReadEnum();
@@ -520,10 +514,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             values_.AddEntriesFrom(input, _repeated_values_codec);
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs
index 89355bd..d7c0954 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs
@@ -151,10 +151,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Seconds = input.ReadInt64();
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Type.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Type.cs
index 36116a6..ff2ecc5 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Type.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Type.cs
@@ -226,10 +226,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -496,10 +493,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             kind_ = (global::Google.Protobuf.WellKnownTypes.Field.Types.Kind) input.ReadEnum();
@@ -716,10 +710,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -872,10 +863,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
@@ -1010,10 +998,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Name = input.ReadString();
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/Wrappers.cs b/csharp/src/Google.Protobuf/WellKnownTypes/Wrappers.cs
index 19ed599..9ecaf47 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/Wrappers.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/Wrappers.cs
@@ -138,10 +138,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 9: {
             Value = input.ReadDouble();
@@ -243,10 +240,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 13: {
             Value = input.ReadFloat();
@@ -348,10 +342,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Value = input.ReadInt64();
@@ -453,10 +444,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Value = input.ReadUInt64();
@@ -558,10 +546,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Value = input.ReadInt32();
@@ -663,10 +648,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Value = input.ReadUInt32();
@@ -768,10 +750,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 8: {
             Value = input.ReadBool();
@@ -873,10 +852,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Value = input.ReadString();
@@ -978,10 +954,7 @@
       while ((tag = input.ReadTag()) != 0) {
         switch(tag) {
           default:
-            if (pb::WireFormat.IsEndGroupTag(tag)) {
-              return;
-            }
-            input.ConsumeLastField();
+            input.SkipLastField();
             break;
           case 10: {
             Value = input.ReadBytes();
diff --git a/csharp/src/Google.Protobuf/WireFormat.cs b/csharp/src/Google.Protobuf/WireFormat.cs
index bbd7e4f..b0e4a41 100644
--- a/csharp/src/Google.Protobuf/WireFormat.cs
+++ b/csharp/src/Google.Protobuf/WireFormat.cs
@@ -99,16 +99,6 @@
         }

 

         /// <summary>

-        /// Determines whether the given tag is an end group tag.

-        /// </summary>

-        /// <param name="tag">The tag to check.</param>

-        /// <returns><c>true</c> if the given tag is an end group tag; <c>false</c> otherwise.</returns>

-        public static bool IsEndGroupTag(uint tag)

-        {

-            return (WireType) (tag & TagTypeMask) == WireType.EndGroup;

-        }

-

-        /// <summary>

         /// Given a tag value, determines the field number (the upper 29 bits).

         /// </summary>

         public static int GetTagFieldNumber(uint tag)

diff --git a/src/google/protobuf/compiler/csharp/csharp_message.cc b/src/google/protobuf/compiler/csharp/csharp_message.cc
index 40c13de..a71a790 100644
--- a/src/google/protobuf/compiler/csharp/csharp_message.cc
+++ b/src/google/protobuf/compiler/csharp/csharp_message.cc
@@ -423,10 +423,7 @@
   printer->Indent();
   printer->Print(
     "default:\n"
-    "  if (pb::WireFormat.IsEndGroupTag(tag)) {\n"
-    "    return;\n"
-    "  }\n"
-    "  input.ConsumeLastField();\n" // We're not storing the data, but we still need to consume it.
+    "  input.SkipLastField();\n" // We're not storing the data, but we still need to consume it.
     "  break;\n");
   for (int i = 0; i < fields_by_number().size(); i++) {
     const FieldDescriptor* field = fields_by_number()[i];