THRIFT-5216 generate DeepCopy methods
Client: netstd
Patch: Jens Geyer

This closes #2155
diff --git a/lib/netstd/Makefile.am b/lib/netstd/Makefile.am
index 503a176..abb680c 100644
--- a/lib/netstd/Makefile.am
+++ b/lib/netstd/Makefile.am
@@ -23,12 +23,21 @@
 	$(DOTNETCORE) build -c Release
 
 check-local:
+	$(DOTNETCORE) test Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
 	$(DOTNETCORE) test Tests/Thrift.Tests/Thrift.Tests.csproj
 	$(DOTNETCORE) test Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj
 
 clean-local:
 	$(RM) -r Thrift/bin
 	$(RM) -r Thrift/obj
+	$(RM) -r Benchmarks/Thrift.Benchmarks/bin
+	$(RM) -r Benchmarks/Thrift.Benchmarks/obj
+	$(RM) -r Tests/Thrift.Tests/bin
+	$(RM) -r Tests/Thrift.Tests/obj
+	$(RM) -r Tests/Thrift.IntegrationTests/bin
+	$(RM) -r Tests/Thrift.IntegrationTests/obj
+	$(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/bin
+	$(RM) -r Tests/Thrift.PublicInterfaces.Compile.Tests/obj
 
 EXTRA_DIST = \
 	README.md \
@@ -36,6 +45,7 @@
 	Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj \
 	Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj \
 	Tests/Thrift.PublicInterfaces.Compile.Tests/Properties/AssemblyInfo.cs \
+	Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift \
 	Tests/Thrift.PublicInterfaces.Compile.Tests/CassandraTest.thrift \
 	Tests/Thrift.Tests/Thrift.Tests.csproj \
 	Tests/Thrift.Tests/Protocols \
@@ -55,4 +65,4 @@
 	build.sh \
 	runtests.cmd \
 	runtests.sh
-	
\ No newline at end of file
+	
diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
index d2db348..1938ddc 100644
--- a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
+++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj
@@ -42,13 +42,16 @@
     </Exec>
     <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./CassandraTest.thrift" />
     <Exec Condition="Exists('thrift')" Command="thrift -gen netstd:wcf,union,serial -r ./CassandraTest.thrift" />
-    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -gen netstd:wcf,union,serial -r ./CassandraTest.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../../compiler/cpp/thrift -gen netstd:wcf,union,serial -r ./CassandraTest.thrift" />
+    <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./optional_required_default.thrift" />
+    <Exec Condition="Exists('thrift')" Command="thrift -gen netstd:wcf,union,serial -r ./optional_required_default.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../../compiler/cpp/thrift -gen netstd:wcf,union,serial -r ./optional_required_default.thrift" />
     <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../test/ThriftTest.thrift" />
     <Exec Condition="Exists('thrift')" Command="thrift -gen netstd:wcf,union,serial -r ./../../../../test/ThriftTest.thrift" />
-    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -gen netstd:wcf,union,serial -r ./../../../../test/ThriftTest.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../../compiler/cpp/thrift -gen netstd:wcf,union,serial -r ./../../../../test/ThriftTest.thrift" />
     <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../contrib/fb303/if/fb303.thrift" />
     <Exec Condition="Exists('thrift')" Command="thrift -gen netstd:wcf,union,serial -r ./../../../../contrib/fb303/if/fb303.thrift" />
-    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -gen netstd:wcf,union,serial -r ./../../../../contrib/fb303/if/fb303.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../../compiler/cpp/thrift -gen netstd:wcf,union,serial -r ./../../../../contrib/fb303/if/fb303.thrift" />
   </Target>
 
 </Project>
diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift
new file mode 100644
index 0000000..1f6c7ee
--- /dev/null
+++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/optional_required_default.thrift
@@ -0,0 +1,143 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+// Testcase for THRIFT-5216 generate DeepCopy methods 
+
+namespace netstd OptReqDefTest
+ 
+enum Distance
+{ 
+    foo = 0, 
+    bar = 1,
+    baz = 2
+} 
+
+struct RaceDetails 
+{ 
+	// this is really the max field index used here, intentionally placed at the beginning
+    666: required Distance           triplesix
+    
+    // without default values
+    
+    1: optional Distance           opt_one
+    2: optional double             opt_two
+    3: optional i16                opt_three
+    4: optional string             opt_four
+    5: optional binary             opt_five
+    6: optional list<i32>          opt_six
+    7: optional set<i64>           opt_seven
+    8: optional map<i8,i16>        opt_eight
+
+    11: required Distance          req_one
+    12: required double            req_two
+    13: required i16               req_three
+    14: required string            req_four
+    15: required binary            req_five
+    16: required list<i32>         req_six
+    17: required set<i64>          req_seven
+    18: required map<i8,i16>       req_eight
+    
+    21:          Distance          def_one
+    22:          double            def_two
+    23:          i16               def_three
+    24:          string            def_four
+    25:          binary            def_five
+    26:          list<i32>         def_six
+    27:          set<i64>          def_seven
+    28:          map<i8,i16>       def_eight
+
+    // having default values
+    
+    31: optional Distance          opt_one_with_value   = Distance.bar
+    32: optional double            opt_two_with_value   = 2.22
+    33: optional i16               opt_three_with_value = 3
+    34: optional string            opt_four_with_value  = "four"
+    35: optional binary            opt_five_with_value  = "five\t"
+    36: optional list<i32>         opt_six_with_value   = [6]
+    37: optional set<i64>          opt_seven_with_value = [7]
+    38: optional map<i8,i16>       opt_eight_with_value = { 8 : 8 }
+
+    41: required Distance          req_one_with_value     = Distance.bar
+    42: required double            req_two_with_value     = 2.22
+    43: required i16               req_three_with_value = 3
+    44: required string            req_four_with_value     = "four"
+    45: required binary            req_five_with_value     = "five"
+    46: required list<i32>         req_six_with_value     = [6]
+    47: required set<i64>          req_seven_with_value = [7]
+    48: required map<i8,i16>       req_eight_with_value = { 8 : 8 }
+    
+    51:          Distance          def_one_with_value     = Distance.bar
+    52:          double            def_two_with_value     = 2.22
+    53:          i16               def_three_with_value = 3
+    54:          string            def_four_with_value     = "four"
+    55:          binary            def_five_with_value     = "five"
+    56:          list<i32>         def_six_with_value     = [6]
+    57:          set<i64>          def_seven_with_value = [7]
+    58:          map<i8,i16>       def_eight_with_value = { 8 : 8 }
+    
+    90: optional bool              last_of_the_mohicans
+
+	// some more complicated ones, including recursion
+	
+    300: required list<Distance>            far_list
+    301: optional set<Distance>             far_set
+    302:          map<Distance,Distance>    far_map
+
+    310: required set<list<Distance>>            far_set_list
+    311: optional list<map<i8,set<Distance>>>    far_list_map_set
+    312:          map<Distance,RDs>              far_map_dist_to_rds
+    
+    320: required RaceDetails      req_nested
+    321: optional RaceDetails      opt_nested
+    322:          RaceDetails      def_nested
+
+    330: required jack      	   req_union
+    331: optional jack      	   opt_union
+    332:          jack      	   def_union
+} 
+
+union jack {
+    1: list<RaceDetails2>     stars    
+    2: list<RDs>             stripes    
+
+    310: set<list<Distance>>            far_set_list
+    311: list<map<i8,set<Distance>>>    far_list_map_set
+    312: map<Distance,RDs>              far_map_dist_to_rds
+    
+    320: jack      					nested_union
+    321: RaceDetails      			nested_struct
+
+    401: optional Distance           opt_one
+    402: optional double             opt_two
+    403: optional i16                opt_three
+    404: optional string             opt_four
+    405: optional binary             opt_five
+    406: optional list<i32>          opt_six
+    407: optional set<i64>           opt_seven
+    408: optional map<i8,i16>        opt_eight
+}
+
+typedef RaceDetails  RaceDetails2
+typedef list<RaceDetails>  RDs
+
+exception CrashBoomBang {
+    1 : i32 MyErrorCode
+}
+
+service foobar {
+    set<set<set<Distance>>> DoItNow( 1 : list<list<list<RaceDetails>>> rd, 2: i32 mitDefault = 42) throws (1: CrashBoomBang cbb)
+}
+
diff --git a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
index 1be99b4..061032a 100644
--- a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
+++ b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
@@ -1,4 +1,4 @@
-// Licensed to the Apache Software Foundation(ASF) under one
+// Licensed to the Apache Software Foundation(ASF) under one
 // or more contributor license agreements.See the NOTICE file
 // distributed with this work for additional information
 // regarding copyright ownership.The ASF licenses this file
@@ -17,6 +17,8 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.Xml;
 using System.Text;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Thrift.Collections;
@@ -30,54 +32,209 @@
         //TODO: Add tests for IEnumerable with objects and primitive values inside
 
         [TestMethod]
-        public void TCollection_Equals_Primitive_Test()
+        public void TCollection_List_Equals_Primitive_Test()
         {
             var collection1 = new List<int> {1,2,3};
             var collection2 = new List<int> {1,2,3};
-
-            var result = TCollections.Equals(collection1, collection2);
-
-            Assert.IsTrue(result);
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
         }
 
         [TestMethod]
-        public void TCollection_Equals_Primitive_Different_Test()
+        public void TCollection_List_Equals_Primitive_Different_Test()
         {
             var collection1 = new List<int> { 1, 2, 3 };
             var collection2 = new List<int> { 1, 2 };
+            Assert.IsFalse(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));
 
-            var result = TCollections.Equals(collection1, collection2);
-
-            Assert.IsFalse(result);
+            collection2.Add(4);
+            Assert.IsFalse(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));
         }
 
         [TestMethod]
-        public void TCollection_Equals_Objects_Test()
+        public void TCollection_List_Equals_Objects_Test()
         {
             var collection1 = new List<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
             var collection2 = new List<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
-
-            var result = TCollections.Equals(collection1, collection2);
-
-            // references to different collections
-            Assert.IsFalse(result);
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
         }
 
         [TestMethod]
-        public void TCollection_Equals_OneAndTheSameObject_Test()
+        public void TCollection_List_List_Equals_Objects_Test()
+        {
+            var collection1 = new List<List<ExampleClass>> { new List<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
+            var collection2 = new List<List<ExampleClass>> { new List<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));  // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual()
+        }
+
+        [TestMethod]
+        public void TCollection_List_Equals_OneAndTheSameObject_Test()
         {
             var collection1 = new List<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
             var collection2 = collection1;
-
-            var result = TCollections.Equals(collection1, collection2);
-
-            // references to one and the same collection
-            Assert.IsTrue(result);
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
         }
 
+        [TestMethod]
+        public void TCollection_Set_Equals_Primitive_Test()
+        {
+            var collection1 = new THashSet<int> {1,2,3};
+            var collection2 = new THashSet<int> {1,2,3};
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
+        }
+
+        [TestMethod]
+        public void TCollection_Set_Equals_Primitive_Different_Test()
+        {
+            var collection1 = new THashSet<int> { 1, 2, 3 };
+            var collection2 = new THashSet<int> { 1, 2 };
+            Assert.IsFalse(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));
+
+            collection2.Add(4);
+            Assert.IsFalse(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));
+        }
+
+        [TestMethod]
+        public void TCollection_Set_Equals_Objects_Test()
+        {
+            var collection1 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            var collection2 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
+        }
+
+        [TestMethod]
+        public void TCollection_Set_Set_Equals_Objects_Test()
+        {
+            var collection1 = new THashSet<THashSet<ExampleClass>> { new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
+            var collection2 = new THashSet<THashSet<ExampleClass>> { new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));  // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual()
+        }
+
+        [TestMethod]
+        public void TCollection_Set_Equals_OneAndTheSameObject_Test()
+        {
+            var collection1 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            var collection2 = collection1;      // references to one and the same collection
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
+        }
+
+
+        [TestMethod]
+        public void TCollection_Map_Equals_Primitive_Test()
+        {
+            var collection1 = new Dictionary<int, int> { [1] = 1, [2] = 2, [3] = 3 };
+            var collection2 = new Dictionary<int, int> { [1] = 1, [2] = 2, [3] = 3 };
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
+        }
+
+        [TestMethod]
+        public void TCollection_Map_Equals_Primitive_Different_Test()
+        {
+            var collection1 = new Dictionary<int, int> { [1] = 1, [2] = 2, [3] = 3 };
+            var collection2 = new Dictionary<int, int> { [1] = 1, [2] = 2 };
+            Assert.IsFalse(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));
+
+            collection2[3] = 3;
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
+
+            collection2[3] = 4;
+            Assert.IsFalse(TCollections.Equals(collection1, collection2));
+        }
+
+        [TestMethod]
+        public void TCollection_Map_Equals_Objects_Test()
+        {
+            var collection1 = new Dictionary<int, ExampleClass>
+            {
+                [1] = new ExampleClass { X = 1 },
+                [-1] = new ExampleClass { X = 2 }
+            };
+            var collection2 = new Dictionary<int, ExampleClass>
+            {
+                [1] = new ExampleClass { X = 1 },
+                [-1] = new ExampleClass { X = 2 }
+            };
+
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
+        }
+
+        [TestMethod]
+        public void TCollection_Map_Map_Equals_Objects_Test()
+        {
+            var collection1 = new Dictionary<int, Dictionary<int, ExampleClass>>
+            {
+                [0] = new Dictionary<int, ExampleClass>
+                {
+                    [1] = new ExampleClass { X = 1 },
+                    [-1] = new ExampleClass { X = 2 }
+                }
+            };
+            var collection2 = new Dictionary<int, Dictionary<int, ExampleClass>>
+            {
+                [0] = new Dictionary<int, ExampleClass>
+                {
+                    [1] = new ExampleClass { X = 1 },
+                    [-1] = new ExampleClass { X = 2 }
+                }
+            };
+
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsFalse(collection1.SequenceEqual(collection2));  // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual()
+        }
+
+        [TestMethod]
+        public void TCollection_Map_Equals_OneAndTheSameObject_Test()
+        {
+            var collection1 = new Dictionary<int, ExampleClass>
+            {
+                [1] = new ExampleClass { X = 1 },
+                [-1] = new ExampleClass { X = 2 }
+            };
+            var collection2 = collection1;
+            Assert.IsTrue(TCollections.Equals(collection1, collection2));
+            Assert.IsTrue(collection1.SequenceEqual(collection2));
+        }
+
+
         private class ExampleClass
         {
             public int X { get; set; }
+
+            // all Thrift-generated classes override Equals(), we do just the same
+            public override bool Equals(object that)
+            {
+                if (!(that is ExampleClass other)) return false;
+                if (ReferenceEquals(this, other)) return true;
+
+                return this.X == other.X;
+            }
+
+            //  overriding Equals() requires GetHashCode() as well
+            public override int GetHashCode()
+            {
+                int hashcode = 157;
+                unchecked
+                {
+                    hashcode = (hashcode * 397) + X.GetHashCode();
+                }
+                return hashcode;
+            }
         }
     }
 }
+
diff --git a/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
new file mode 100644
index 0000000..f717b4d
--- /dev/null
+++ b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
@@ -0,0 +1,603 @@
+// Licensed to the Apache Software Foundation(ASF) under one
+// or more contributor license agreements.See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+// 
+//     http://www.apache.org/licenses/LICENSE-2.0
+// 
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using OptReqDefTest;
+using Thrift.Collections;
+
+namespace Thrift.Tests.DataModel
+{
+    // ReSharper disable once InconsistentNaming
+    [TestClass]
+    public class DeepCopyTests
+    {
+        [TestMethod]
+        public void Test_Complex_DeepCopy()
+        {
+            var first = InitializeInstance(new RaceDetails());
+            VerifyIdenticalContent(first, InitializeInstance(new RaceDetails()));
+
+            var second = first.DeepCopy();
+            VerifyIdenticalContent(first, second);
+            ModifyInstance(second,0);
+            VerifyDifferentContent(first, second);
+            VerifyIdenticalContent(first, InitializeInstance(new RaceDetails()));
+
+            var third = second.DeepCopy();
+            VerifyIdenticalContent(second, third);
+            ModifyInstance(third,0);
+            VerifyDifferentContent(second, third);
+            VerifyIdenticalContent(first, InitializeInstance(new RaceDetails()));
+        }
+
+        private RaceDetails MakeNestedRaceDetails(int nesting)
+        {
+            if (++nesting > 1)
+                return null;
+
+            var instance = new RaceDetails();
+            InitializeInstance(instance,nesting);
+            return instance;
+        }
+
+        private jack MakeNestedUnion(int nesting)
+        {
+            if (++nesting > 1)
+                return null;
+
+            var details = new RaceDetails();
+            InitializeInstance(details,nesting);
+            return new jack.nested_struct(details);
+        }
+
+
+        private RaceDetails InitializeInstance(RaceDetails instance, int nesting = 0)
+        {
+            // at init, we intentionally leave all non-required fields unset
+            Assert.IsFalse(instance.__isset.opt_one);
+            Assert.IsFalse(instance.__isset.opt_two);
+            Assert.IsFalse(instance.__isset.opt_three);
+            Assert.IsFalse(instance.__isset.opt_four);
+            Assert.IsFalse(instance.__isset.opt_five);
+            Assert.IsFalse(instance.__isset.opt_six);
+            Assert.IsFalse(instance.__isset.opt_seven);
+            Assert.IsFalse(instance.__isset.opt_eight);
+
+            // set all required to null/default
+            instance.Req_one = default;
+            instance.Req_two = default;
+            instance.Req_three = default;
+            instance.Req_four = default;
+            instance.Req_five = default;
+            instance.Req_six = default; 
+            instance.Req_seven = default;;
+            instance.Req_eight = default; 
+
+            // leave non-required fields unset again
+            Assert.IsFalse(instance.__isset.def_one);
+            Assert.IsFalse(instance.__isset.def_two);
+            Assert.IsFalse(instance.__isset.def_three);
+            Assert.IsFalse(instance.__isset.def_four);
+            Assert.IsFalse(instance.__isset.def_five);
+            Assert.IsFalse(instance.__isset.def_six);
+            Assert.IsFalse(instance.__isset.def_seven);
+            Assert.IsFalse(instance.__isset.def_eight);
+
+            // these should have IDL defaults set
+
+            Assert.IsTrue(instance.__isset.opt_one_with_value);
+            Assert.IsTrue(instance.__isset.opt_two_with_value);
+            Assert.IsTrue(instance.__isset.opt_three_with_value);
+            Assert.IsTrue(instance.__isset.opt_four_with_value);
+            Assert.IsTrue(instance.__isset.opt_five_with_value);
+            Assert.IsTrue(instance.__isset.opt_six_with_value);
+            Assert.IsTrue(instance.__isset.opt_seven_with_value);
+            Assert.IsTrue(instance.__isset.opt_eight_with_value);
+
+            Assert.AreEqual(instance.Req_one_with_value, (Distance)1);
+            Assert.AreEqual(instance.Req_two_with_value, 2.22);
+            Assert.AreEqual(instance.Req_three_with_value, 3);
+            Assert.AreEqual(instance.Req_four_with_value, "four");
+            Assert.AreEqual("five", Encoding.UTF8.GetString(instance.Req_five_with_value));
+
+            Assert.IsTrue(instance.Req_six_with_value.Count == 1);
+            Assert.AreEqual(instance.Req_six_with_value[0], 6 );
+
+            Assert.IsTrue(instance.Req_seven_with_value.Count == 1);
+            Assert.IsTrue(instance.Req_seven_with_value.Contains(7));
+
+            Assert.IsTrue(instance.Req_eight_with_value.Count == 1);
+            Assert.IsTrue(instance.Req_eight_with_value[8] == 8);
+
+            Assert.IsTrue(instance.__isset.def_one_with_value);
+            Assert.IsTrue(instance.__isset.def_two_with_value);
+            Assert.IsTrue(instance.__isset.def_three_with_value);
+            Assert.IsTrue(instance.__isset.def_four_with_value);
+            Assert.IsTrue(instance.__isset.def_five_with_value);
+            Assert.IsTrue(instance.__isset.def_six_with_value);
+            Assert.IsTrue(instance.__isset.def_seven_with_value);
+            Assert.IsTrue(instance.__isset.def_eight_with_value);
+
+            instance.Last_of_the_mohicans = true;
+
+            if (nesting < 2)
+            {
+                instance.Far_list = new List<Distance>() { Distance.foo, Distance.bar, Distance.baz };
+                instance.Far_set = new THashSet<Distance>() { Distance.foo, Distance.bar, Distance.baz };
+                instance.Far_map = new Dictionary<Distance, Distance>() { [Distance.foo] = Distance.foo, [Distance.bar] = Distance.bar, [Distance.baz] = Distance.baz };
+
+                instance.Far_set_list = new THashSet<List<Distance>>() { new List<Distance>() { Distance.foo } };
+                instance.Far_list_map_set = new List<Dictionary<sbyte, THashSet<Distance>>>() { new Dictionary<sbyte, THashSet<Distance>>() { [1] = new THashSet<Distance>() { Distance.baz } } };
+                instance.Far_map_dist_to_rds = new Dictionary<Distance, List<RaceDetails>>() { [Distance.bar] = new List<RaceDetails>() { MakeNestedRaceDetails(nesting) } };
+
+                instance.Req_nested = MakeNestedRaceDetails(nesting);
+                Assert.IsFalse(instance.__isset.opt_nested);
+                Assert.IsFalse(instance.__isset.def_nested);
+
+                instance.Req_union = MakeNestedUnion(nesting);
+                Assert.IsFalse(instance.__isset.opt_union);
+                Assert.IsFalse(instance.__isset.def_union);
+            }
+
+            instance.Triplesix = (Distance)666;
+
+            return instance;
+        }
+
+        private void ModifyInstance(RaceDetails instance, int level)
+        {
+            if ((instance == null) || (++level > 4))
+                return;
+
+            instance.Opt_one = ModifyValue(instance.Opt_one);
+            instance.Opt_two = ModifyValue(instance.Opt_two);
+            instance.Opt_three = ModifyValue(instance.Opt_three);
+            instance.Opt_four = ModifyValue(instance.Opt_four);
+            instance.Opt_five = ModifyValue(instance.Opt_five);
+            instance.Opt_six = ModifyValue(instance.Opt_six);
+            instance.Opt_seven = ModifyValue(instance.Opt_seven);
+            instance.Opt_eight = ModifyValue(instance.Opt_eight);
+
+            instance.Req_one = ModifyValue(instance.Req_one);
+            instance.Req_two = ModifyValue(instance.Req_two);
+            instance.Req_three = ModifyValue(instance.Req_three);
+            instance.Req_four = ModifyValue(instance.Req_four);
+            instance.Req_five = ModifyValue(instance.Req_five);
+            instance.Req_six = ModifyValue(instance.Req_six);
+            instance.Req_seven = ModifyValue(instance.Req_seven);
+            instance.Req_eight = ModifyValue(instance.Req_eight);
+
+            instance.Def_one = ModifyValue(instance.Def_one);
+            instance.Def_two = ModifyValue(instance.Def_two);
+            instance.Def_three = ModifyValue(instance.Def_three);
+            instance.Def_four = ModifyValue(instance.Def_four);
+            instance.Def_five = ModifyValue(instance.Def_five);
+            instance.Def_six = ModifyValue(instance.Def_six);
+            instance.Def_seven = ModifyValue(instance.Def_seven);
+            instance.Def_eight = ModifyValue(instance.Def_eight);
+
+            instance.Opt_one_with_value = ModifyValue(instance.Opt_one_with_value);
+            instance.Opt_two_with_value = ModifyValue(instance.Opt_two_with_value);
+            instance.Opt_three_with_value = ModifyValue(instance.Opt_three_with_value);
+            instance.Opt_four_with_value = ModifyValue(instance.Opt_four_with_value);
+            instance.Opt_five_with_value = ModifyValue(instance.Opt_five_with_value);
+            instance.Opt_six_with_value = ModifyValue(instance.Opt_six_with_value);
+            instance.Opt_seven_with_value = ModifyValue(instance.Opt_seven_with_value);
+            instance.Opt_eight_with_value = ModifyValue(instance.Opt_eight_with_value);
+
+            instance.Req_one_with_value = ModifyValue(instance.Req_one_with_value);
+            instance.Req_two_with_value = ModifyValue(instance.Req_two_with_value);
+            instance.Req_three_with_value = ModifyValue(instance.Req_three_with_value);
+            instance.Req_four_with_value = ModifyValue(instance.Req_four_with_value);
+            instance.Req_five_with_value = ModifyValue(instance.Req_five_with_value);
+            instance.Req_six_with_value = ModifyValue(instance.Req_six_with_value);
+            instance.Req_seven_with_value = ModifyValue(instance.Req_seven_with_value);
+            instance.Req_eight_with_value = ModifyValue(instance.Req_eight_with_value);
+
+            instance.Def_one_with_value = ModifyValue(instance.Def_one_with_value);
+            instance.Def_two_with_value = ModifyValue(instance.Def_two_with_value);
+            instance.Def_three_with_value = ModifyValue(instance.Def_three_with_value);
+            instance.Def_four_with_value = ModifyValue(instance.Def_four_with_value);
+            instance.Def_five_with_value = ModifyValue(instance.Def_five_with_value);
+            instance.Def_six_with_value = ModifyValue(instance.Def_six_with_value);
+            instance.Def_seven_with_value = ModifyValue(instance.Def_seven_with_value);
+            instance.Def_eight_with_value = ModifyValue(instance.Def_eight_with_value);
+
+            instance.Last_of_the_mohicans = ModifyValue(instance.Last_of_the_mohicans);
+
+            instance.Far_list = ModifyValue(instance.Far_list);
+            instance.Far_set = ModifyValue(instance.Far_set);
+            instance.Far_map = ModifyValue(instance.Far_map);
+
+            instance.Far_set_list = ModifyValue(instance.Far_set_list);
+            instance.Far_list_map_set = ModifyValue(instance.Far_list_map_set);
+            instance.Far_map_dist_to_rds = ModifyValue(instance.Far_map_dist_to_rds, level);
+
+            instance.Req_nested = ModifyValue(instance.Req_nested, level);
+            instance.Opt_nested = ModifyValue(instance.Opt_nested, level);
+            instance.Def_nested = ModifyValue(instance.Def_nested, level);
+
+            instance.Req_union = ModifyValue(instance.Req_union, level);
+            instance.Opt_union = ModifyValue(instance.Opt_union, level);
+            instance.Def_union = ModifyValue(instance.Def_union, level);
+
+            instance.Triplesix = ModifyValue(instance.Triplesix);
+        }
+
+        private jack ModifyValue(jack value, int level)
+        {
+            if (++level > 4)
+                return value;
+
+            if (value == null)
+                value = MakeNestedUnion(0);
+            Debug.Assert(value.As_nested_struct != null);
+            ModifyInstance(value.As_nested_struct, level);
+            return value;
+        }
+
+        private RaceDetails ModifyValue(RaceDetails value, int level)
+        {
+            if (++level > 4)
+                return value;
+
+            if (value == null)
+                value = new RaceDetails();
+            ModifyInstance(value,level);
+            return value;
+        }
+
+        private Dictionary<Distance, List<RaceDetails>> ModifyValue(Dictionary<Distance, List<RaceDetails>> value, int level)
+        {
+            if (value == null)
+                value = new Dictionary<Distance, List<RaceDetails>>();
+
+            if (++level > 4)
+                return value;
+
+            var details = new RaceDetails();
+            InitializeInstance(details);
+            value[Distance.foo] = new List<RaceDetails>() { details };
+
+            if (value.TryGetValue(Distance.bar, out var list) && (list.Count > 0))
+            {
+                ModifyInstance(list[0], level);
+                list.Add(null);
+            }
+
+            value[Distance.baz] = null;
+
+            return value;
+        }
+
+        private List<Dictionary<sbyte, THashSet<Distance>>> ModifyValue(List<Dictionary<sbyte, THashSet<Distance>>> value)
+        {
+            if (value == null)
+                value = new List<Dictionary<sbyte, THashSet<Distance>>>();
+
+            if (value.Count == 0)
+                value.Add(new Dictionary<sbyte, THashSet<Distance>>());
+            else
+                value.Add(null);
+
+            sbyte key = (sbyte)(value[0].Count + 10);
+            if (value[0].Count == 0)
+                value[0].Add(key, new THashSet<Distance>());
+            else
+                value[0].Add(key, null);
+
+            foreach (var entry in value)
+            {
+                if (entry != null)
+                {
+                    foreach (var pair in entry)
+                    {
+                        if (pair.Value != null)
+                        {
+                            if (pair.Value.Contains(Distance.baz))
+                                pair.Value.Remove(Distance.baz);
+                            else
+                                pair.Value.Add(Distance.baz);
+                        }
+                    }
+                }
+            }
+
+            return value;
+        }
+
+        private THashSet<List<Distance>> ModifyValue(THashSet<List<Distance>> value)
+        {
+            if (value == null)
+                value = new THashSet<List<Distance>>();
+
+            if (value.Count == 0)
+                value.Add(new List<Distance>());
+            else
+                value.Add(null);
+
+            foreach (var entry in value)
+                if( entry != null)
+                    entry.Add(Distance.baz);
+
+            return value;
+        }
+
+        private Dictionary<Distance, Distance> ModifyValue(Dictionary<Distance, Distance> value)
+        {
+            if (value == null)
+                value = new Dictionary<Distance, Distance>();
+            value[Distance.foo] = value.ContainsKey(Distance.foo) ? ++value[Distance.foo] : Distance.foo;
+            value[Distance.bar] = value.ContainsKey(Distance.bar) ? ++value[Distance.bar] : Distance.bar;
+            value[Distance.baz] = value.ContainsKey(Distance.baz) ? ++value[Distance.baz] : Distance.baz;
+            return value;
+        }
+
+        private THashSet<Distance> ModifyValue(THashSet<Distance> value)
+        {
+            if (value == null)
+                value = new THashSet<Distance>();
+
+            if (value.Contains(Distance.foo))
+                value.Remove(Distance.foo);
+            else
+                value.Add(Distance.foo);
+
+            if (value.Contains(Distance.bar))
+                value.Remove(Distance.bar);
+            else
+                value.Add(Distance.bar);
+
+            if (value.Contains(Distance.baz))
+                value.Remove(Distance.baz);
+            else
+                value.Add(Distance.baz);
+
+            return value;
+        }
+
+        private List<Distance> ModifyValue(List<Distance> value)
+        {
+            if (value == null)
+                value = new List<Distance>();
+            value.Add(Distance.foo);
+            value.Add(Distance.bar);
+            value.Add(Distance.baz);
+            return value;
+        }
+
+        private bool ModifyValue(bool value)
+        {
+            return !value;
+        }
+
+        private Dictionary<sbyte, short> ModifyValue(Dictionary<sbyte, short> value)
+        {
+            if (value == null)
+                value = new Dictionary<sbyte, short>();
+            value.Add((sbyte)(value.Count + 10), (short)value.Count);
+            return value;
+        }
+
+        private THashSet<long> ModifyValue(THashSet<long> value)
+        {
+            if (value == null)
+                value = new THashSet<long>();
+            value.Add(value.Count+100);
+            return value;
+        }
+
+        private List<int> ModifyValue(List<int> value)
+        {
+            if (value == null)
+                value = new List<int>();
+            value.Add(value.Count);
+            return value;
+        }
+
+        private byte[] ModifyValue(byte[] value)
+        {
+            if (value == null)
+                value = new byte[1] { 0 };
+            if (value.Length > 0)
+                value[0] = (value[0] < 0xFF) ? ++value[0] : (byte)0;
+            return value;
+        }
+
+        private string ModifyValue(string value)
+        {
+            return value + "1";
+        }
+
+        private double ModifyValue(double value)
+        {
+            return value + 1.1;
+        }
+
+        private short ModifyValue(short value)
+        {
+            return ++value;
+        }
+
+        private Distance ModifyValue(Distance value)
+        {
+            return ++value;
+        }
+
+        private void VerifyDifferentContent(RaceDetails first, RaceDetails second)
+        {
+            Assert.AreNotEqual(first, second);
+
+            Assert.AreNotEqual(first.Opt_two, second.Opt_two);
+            Assert.AreNotEqual(first.Opt_three, second.Opt_three);
+            Assert.AreNotEqual(first.Opt_four, second.Opt_four);
+            Assert.IsFalse(TCollections.Equals(first.Opt_five, second.Opt_five));
+            Assert.IsFalse(TCollections.Equals(first.Opt_six, second.Opt_six));
+            Assert.IsFalse(TCollections.Equals(first.Opt_seven, second.Opt_seven));
+            Assert.IsFalse(TCollections.Equals(first.Opt_eight, second.Opt_eight));
+
+            Assert.AreNotEqual(first.Req_one, second.Req_one);
+            Assert.AreNotEqual(first.Req_two, second.Req_two);
+            Assert.AreNotEqual(first.Req_three, second.Req_three);
+            Assert.AreNotEqual(first.Req_four, second.Req_four);
+            Assert.IsFalse(TCollections.Equals(first.Req_five, second.Req_five));
+            Assert.IsFalse(TCollections.Equals(first.Req_six, second.Req_six));
+            Assert.IsFalse(TCollections.Equals(first.Req_seven, second.Req_seven));
+            Assert.IsFalse(TCollections.Equals(first.Req_eight, second.Req_eight));
+
+            Assert.AreNotEqual(first.Def_one, second.Def_one);
+            Assert.AreNotEqual(first.Def_two, second.Def_two);
+            Assert.AreNotEqual(first.Def_three, second.Def_three);
+            Assert.AreNotEqual(first.Def_four, second.Def_four);
+            Assert.IsFalse(TCollections.Equals(first.Def_five, second.Def_five));
+            Assert.IsFalse(TCollections.Equals(first.Def_six, second.Def_six));
+            Assert.IsFalse(TCollections.Equals(first.Def_seven, second.Def_seven));
+            Assert.IsFalse(TCollections.Equals(first.Def_eight, second.Def_eight));
+
+            Assert.AreNotEqual(first.Opt_one_with_value, second.Opt_one_with_value);
+            Assert.AreNotEqual(first.Opt_two_with_value, second.Opt_two_with_value);
+            Assert.AreNotEqual(first.Opt_three_with_value, second.Opt_three_with_value);
+            Assert.AreNotEqual(first.Opt_four_with_value, second.Opt_four_with_value);
+            Assert.IsFalse(TCollections.Equals(first.Opt_five_with_value, second.Opt_five_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Opt_six_with_value, second.Opt_six_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Opt_seven_with_value, second.Opt_seven_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Opt_eight_with_value, second.Opt_eight_with_value));
+
+            Assert.AreNotEqual(first.Req_one_with_value, second.Req_one_with_value);
+            Assert.AreNotEqual(first.Req_two_with_value, second.Req_two_with_value);
+            Assert.AreNotEqual(first.Req_three_with_value, second.Req_three_with_value);
+            Assert.AreNotEqual(first.Req_four_with_value, second.Req_four_with_value);
+            Assert.IsFalse(TCollections.Equals(first.Req_five_with_value, second.Req_five_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Req_six_with_value, second.Req_six_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Req_seven_with_value, second.Req_seven_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Req_eight_with_value, second.Req_eight_with_value));
+
+            Assert.AreNotEqual(first.Def_one_with_value, second.Def_one_with_value);
+            Assert.AreNotEqual(first.Def_two_with_value, second.Def_two_with_value);
+            Assert.AreNotEqual(first.Def_three_with_value, second.Def_three_with_value);
+            Assert.AreNotEqual(first.Def_four_with_value, second.Def_four_with_value);
+            Assert.IsFalse(TCollections.Equals(first.Def_five_with_value, second.Def_five_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Def_six_with_value, second.Def_six_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Def_seven_with_value, second.Def_seven_with_value));
+            Assert.IsFalse(TCollections.Equals(first.Def_eight_with_value, second.Def_eight_with_value));
+
+            Assert.AreNotEqual(first.Last_of_the_mohicans, second.Last_of_the_mohicans);
+
+            Assert.IsFalse(TCollections.Equals(first.Far_list, second.Far_list));
+            Assert.IsFalse(TCollections.Equals(first.Far_set, second.Far_set));
+            Assert.IsFalse(TCollections.Equals(first.Far_map, second.Far_map));
+
+            Assert.IsFalse(TCollections.Equals(first.Far_set_list, second.Far_set_list));
+            Assert.IsFalse(TCollections.Equals(first.Far_list_map_set, second.Far_list_map_set));
+            Assert.IsFalse(TCollections.Equals(first.Far_map_dist_to_rds, second.Far_map_dist_to_rds));
+
+            Assert.AreNotEqual(first.Req_nested, second.Req_nested);
+            Assert.AreNotEqual(first.Opt_nested, second.Opt_nested);
+            Assert.AreNotEqual(first.Def_nested, second.Def_nested);
+
+            Assert.AreNotEqual(first.Req_union, second.Req_union);
+            Assert.AreNotEqual(first.Opt_union, second.Opt_union);
+            Assert.AreNotEqual(first.Def_union, second.Def_union);
+
+            Assert.AreNotEqual(first.Triplesix, second.Triplesix);
+        }
+
+        private void VerifyIdenticalContent(RaceDetails first, RaceDetails second)
+        {
+            Assert.AreEqual(first, second);
+
+            Assert.AreEqual(first.Opt_two, second.Opt_two);
+            Assert.AreEqual(first.Opt_three, second.Opt_three);
+            Assert.AreEqual(first.Opt_four, second.Opt_four);
+            Assert.IsTrue(TCollections.Equals(first.Opt_five, second.Opt_five));
+            Assert.IsTrue(TCollections.Equals(first.Opt_six, second.Opt_six));
+            Assert.IsTrue(TCollections.Equals(first.Opt_seven, second.Opt_seven));
+            Assert.IsTrue(TCollections.Equals(first.Opt_eight, second.Opt_eight));
+
+            Assert.AreEqual(first.Req_one, second.Req_one);
+            Assert.AreEqual(first.Req_two, second.Req_two);
+            Assert.AreEqual(first.Req_three, second.Req_three);
+            Assert.AreEqual(first.Req_four, second.Req_four);
+            Assert.IsTrue(TCollections.Equals(first.Req_five, second.Req_five));
+            Assert.IsTrue(TCollections.Equals(first.Req_six, second.Req_six));
+            Assert.IsTrue(TCollections.Equals(first.Req_seven, second.Req_seven));
+            Assert.IsTrue(TCollections.Equals(first.Req_eight, second.Req_eight));
+
+            Assert.AreEqual(first.Def_one, second.Def_one);
+            Assert.AreEqual(first.Def_two, second.Def_two);
+            Assert.AreEqual(first.Def_three, second.Def_three);
+            Assert.AreEqual(first.Def_four, second.Def_four);
+            Assert.IsTrue(TCollections.Equals(first.Def_five, second.Def_five));
+            Assert.IsTrue(TCollections.Equals(first.Def_six, second.Def_six));
+            Assert.IsTrue(TCollections.Equals(first.Def_seven, second.Def_seven));
+            Assert.IsTrue(TCollections.Equals(first.Def_eight, second.Def_eight));
+
+            Assert.AreEqual(first.Opt_one_with_value, second.Opt_one_with_value);
+            Assert.AreEqual(first.Opt_two_with_value, second.Opt_two_with_value);
+            Assert.AreEqual(first.Opt_three_with_value, second.Opt_three_with_value);
+            Assert.AreEqual(first.Opt_four_with_value, second.Opt_four_with_value);
+            Assert.IsTrue(TCollections.Equals(first.Opt_five_with_value, second.Opt_five_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Opt_six_with_value, second.Opt_six_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Opt_seven_with_value, second.Opt_seven_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Opt_eight_with_value, second.Opt_eight_with_value));
+
+            Assert.AreEqual(first.Req_one_with_value, second.Req_one_with_value);
+            Assert.AreEqual(first.Req_two_with_value, second.Req_two_with_value);
+            Assert.AreEqual(first.Req_three_with_value, second.Req_three_with_value);
+            Assert.AreEqual(first.Req_four_with_value, second.Req_four_with_value);
+            Assert.IsTrue(TCollections.Equals(first.Req_five_with_value, second.Req_five_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Req_six_with_value, second.Req_six_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Req_seven_with_value, second.Req_seven_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Req_eight_with_value, second.Req_eight_with_value));
+
+            Assert.AreEqual(first.Def_one_with_value, second.Def_one_with_value);
+            Assert.AreEqual(first.Def_two_with_value, second.Def_two_with_value);
+            Assert.AreEqual(first.Def_three_with_value, second.Def_three_with_value);
+            Assert.AreEqual(first.Def_four_with_value, second.Def_four_with_value);
+            Assert.IsTrue(TCollections.Equals(first.Def_five_with_value, second.Def_five_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Def_six_with_value, second.Def_six_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Def_seven_with_value, second.Def_seven_with_value));
+            Assert.IsTrue(TCollections.Equals(first.Def_eight_with_value, second.Def_eight_with_value));
+
+            Assert.AreEqual(first.Last_of_the_mohicans, second.Last_of_the_mohicans);
+
+            Assert.IsTrue(TCollections.Equals(first.Far_list, second.Far_list));
+            Assert.IsTrue(TCollections.Equals(first.Far_set, second.Far_set));
+            Assert.IsTrue(TCollections.Equals(first.Far_map, second.Far_map));
+
+            Assert.IsTrue(TCollections.Equals(first.Far_set_list, second.Far_set_list));
+            Assert.IsTrue(TCollections.Equals(first.Far_list_map_set, second.Far_list_map_set));
+            Assert.IsTrue(TCollections.Equals(first.Far_map_dist_to_rds, second.Far_map_dist_to_rds));
+
+            Assert.AreEqual(first.Req_nested, second.Req_nested);
+            Assert.AreEqual(first.Opt_nested, second.Opt_nested);
+            Assert.AreEqual(first.Def_nested, second.Def_nested);
+
+            Assert.AreEqual(first.Req_union, second.Req_union);
+            Assert.AreEqual(first.Opt_union, second.Opt_union);
+            Assert.AreEqual(first.Def_union, second.Def_union);
+
+            Assert.AreEqual(first.Triplesix, second.Triplesix);
+        }
+
+    }
+}
diff --git a/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj b/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj
index 20fdfe4..4b39e7d 100644
--- a/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj
+++ b/lib/netstd/Tests/Thrift.Tests/Thrift.Tests.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <!--
     Licensed to the Apache Software Foundation(ASF) under one
     or more contributor license agreements.See the NOTICE file
@@ -29,6 +29,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\Thrift\Thrift.csproj" />
+    <ProjectReference Include="..\Thrift.PublicInterfaces.Compile.Tests\Thrift.PublicInterfaces.Compile.Tests.csproj" />
   </ItemGroup>
   <ItemGroup>
     <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
diff --git a/lib/netstd/Thrift/Collections/TCollections.cs b/lib/netstd/Thrift/Collections/TCollections.cs
index 147bfc7..b386c37 100644
--- a/lib/netstd/Thrift/Collections/TCollections.cs
+++ b/lib/netstd/Thrift/Collections/TCollections.cs
@@ -16,11 +16,12 @@
 // under the License.
 
 using System.Collections;
+using System.Collections.Generic;
 
 namespace Thrift.Collections
 {
     // ReSharper disable once InconsistentNaming
-    public class TCollections
+    public static class TCollections
     {
         /// <summary>
         ///     This will return true if the two collections are value-wise the same.
@@ -38,6 +39,18 @@
                 return false;
             }
 
+            // for dictionaries, we need to compare keys and values separately
+            // because KeyValuePair<K,V>.Equals() will not do what we want
+            var fdict = first as IDictionary;
+            var sdict = second as IDictionary;
+            if ((fdict != null) || (sdict != null))
+            {
+                if ((fdict == null) || (sdict == null))
+                    return false;
+                return TCollections.Equals(fdict.Keys, sdict.Keys)
+                    && TCollections.Equals(fdict.Values, sdict.Values);
+            }
+
             var fiter = first.GetEnumerator();
             var siter = second.GetEnumerator();
 
@@ -91,11 +104,13 @@
 
                 unchecked
                 {
-                    hashcode = (hashcode*397) ^ (objHash);
+                    hashcode = (hashcode * 397) ^ (objHash);
                 }
             }
 
             return hashcode;
         }
+
+
     }
-}
\ No newline at end of file
+}
diff --git a/lib/netstd/Thrift/Collections/THashSet.cs b/lib/netstd/Thrift/Collections/THashSet.cs
index ffab577..8dfb9e3 100644
--- a/lib/netstd/Thrift/Collections/THashSet.cs
+++ b/lib/netstd/Thrift/Collections/THashSet.cs
@@ -60,12 +60,12 @@
             Items.CopyTo(array, arrayIndex);
         }
 
-        public IEnumerator GetEnumerator()
+        IEnumerator IEnumerable.GetEnumerator()
         {
             return Items.GetEnumerator();
         }
 
-        IEnumerator<T> IEnumerable<T>.GetEnumerator()
+        public IEnumerator<T> GetEnumerator()
         {
             return ((IEnumerable<T>) Items).GetEnumerator();
         }
diff --git a/lib/netstd/Thrift/Protocol/TBase.cs b/lib/netstd/Thrift/Protocol/TBase.cs
index b5ef2ae..df9dd34 100644
--- a/lib/netstd/Thrift/Protocol/TBase.cs
+++ b/lib/netstd/Thrift/Protocol/TBase.cs
@@ -22,12 +22,13 @@
 {
     public interface TUnionBase
     {
-        Task WriteAsync(TProtocol tProtocol, CancellationToken cancellationToken = default(CancellationToken));
+        Task WriteAsync(TProtocol tProtocol, CancellationToken cancellationToken = default);
     }
 
     // ReSharper disable once InconsistentNaming
     public interface TBase : TUnionBase
     {
-        Task ReadAsync(TProtocol tProtocol, CancellationToken cancellationToken = default(CancellationToken));
+        Task ReadAsync(TProtocol tProtocol, CancellationToken cancellationToken = default);
     }
+
 }
diff --git a/lib/netstd/Thrift/Collections/ToStringExtension.cs b/lib/netstd/Thrift/Protocol/ToString.cs
similarity index 95%
rename from lib/netstd/Thrift/Collections/ToStringExtension.cs
rename to lib/netstd/Thrift/Protocol/ToString.cs
index 40dd9dd..14fa520 100644
--- a/lib/netstd/Thrift/Collections/ToStringExtension.cs
+++ b/lib/netstd/Thrift/Protocol/ToString.cs
@@ -21,7 +21,7 @@
 using System.Text;
 using Thrift.Protocol;
 
-namespace Thrift.Collections
+namespace Thrift.Protocol
 {
 
 
@@ -73,7 +73,7 @@
             }
             else
             {
-                sb.Append(self.ToString());
+                sb.Append(self != null?  self.ToString() : "<null>");
             }
         }
     }