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

This closes #2155
diff --git a/compiler/cpp/src/thrift/generate/t_netstd_generator.cc b/compiler/cpp/src/thrift/generate/t_netstd_generator.cc
index 8b6389f..e83723c 100644
--- a/compiler/cpp/src/thrift/generate/t_netstd_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_netstd_generator.cc
@@ -51,6 +51,7 @@
     : t_oop_generator(program)
 {
     (void)option_string;
+    suppress_deepcopy = false;
     use_pascal_case_properties = false;
     union_ = false;
     serialize_ = false;
@@ -62,24 +63,23 @@
 
     for (iter = parsed_options.begin(); iter != parsed_options.end(); ++iter)
     {
-        if (iter->first.compare("union") == 0)
-        {
+        if (iter->first.compare("union") == 0) {
             union_ = true;
         }
-        else if (iter->first.compare("serial") == 0)
-        {
+        else if (iter->first.compare("serial") == 0) {
             serialize_ = true;
             wcf_namespace_ = iter->second; // since there can be only one namespace
         }
-        else if (iter->first.compare("wcf") == 0)
-        {
+        else if (iter->first.compare("wcf") == 0) {
             wcf_ = true;
             wcf_namespace_ = iter->second;
         }
-        else if (iter->first.compare("pascal") == 0)
-        {
+        else if (iter->first.compare("pascal") == 0) {
           use_pascal_case_properties = true;
         }
+        else if (iter->first.compare("no_deepcopy") == 0) {
+          suppress_deepcopy = true;
+        }
         else {
           throw "unknown option netstd:" + iter->first;
         }
@@ -132,12 +132,19 @@
 
 static bool field_is_required(t_field* tfield) { return tfield->get_req() == t_field::T_REQUIRED; }
 
-static bool type_can_be_null(t_type* ttype)
+static t_type* resolve_typedef(t_type* ttype)
 {
     while (ttype->is_typedef())
     {
         ttype = static_cast<t_typedef*>(ttype)->get_type();
     }
+    return ttype;
+}
+
+
+static bool type_can_be_null(t_type* ttype)
+{
+    ttype = resolve_typedef(ttype);
 
     return ttype->is_container() || ttype->is_struct() || ttype->is_xception() || ttype->is_string();
 }
@@ -184,10 +191,11 @@
     }
 
     pverbose(".NET Standard options:\n");
-    pverbose("- union ...... %s\n", (is_union_enabled() ? "ON" : "off"));
-    pverbose("- serialize .. %s\n", (is_serialize_enabled() ? "ON" : "off"));
-    pverbose("- wcf ........ %s\n", (is_wcf_enabled() ? "ON" : "off"));
-    pverbose("- pascal ..... %s\n", (use_pascal_case_properties ? "ON" : "off"));
+    pverbose("- union ......... %s\n", (is_union_enabled() ? "ON" : "off"));
+    pverbose("- serialize ..... %s\n", (is_serialize_enabled() ? "ON" : "off"));
+    pverbose("- wcf ........... %s\n", (is_wcf_enabled() ? "ON" : "off"));
+    pverbose("- pascal ........ %s\n", (use_pascal_case_properties ? "ON" : "off"));
+    pverbose("- no_deepcopy ... %s\n", (suppress_deepcopy ? "ON" : "off"));
 }
 
 string t_netstd_generator::normalize_name(string name)
@@ -350,6 +358,7 @@
         "using System.Collections.Generic;\n"
         "using System.Text;\n"
         "using System.IO;\n"
+        "using System.Linq;\n"
         "using System.Threading;\n"
         "using System.Threading.Tasks;\n"
         "using Thrift;\n"
@@ -486,6 +495,7 @@
         const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = value->get_map();
         vector<t_field*>::const_iterator f_iter;
         map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter;
+        collect_extensions_types(static_cast<t_struct*>(type));
         prepare_member_name_mapping(static_cast<t_struct*>(type));
 
         for (v_iter = val.begin(); v_iter != val.end(); ++v_iter)
@@ -569,10 +579,8 @@
 {
     out << indent();
     bool need_static_construction = !in_static;
-    while (type->is_typedef())
-    {
-        type = static_cast<t_typedef*>(type)->get_type();
-    }
+    
+    type = resolve_typedef( type);
 
     if (!defval || needtype)
     {
@@ -622,7 +630,11 @@
         switch (tbase)
         {
         case t_base_type::TYPE_STRING:
-            render << '"' << get_escaped_string(value) << '"';
+            if (type->is_binary()) {
+                render << "System.Text.Encoding.UTF8.GetBytes(\"" << get_escaped_string(value) << "\")";
+            } else {
+                render << '"' << get_escaped_string(value) << '"';
+            } 
             break;
         case t_base_type::TYPE_BOOL:
             render << ((value->get_integer() > 0) ? "true" : "false");
@@ -661,8 +673,194 @@
     return render.str();
 }
 
+void t_netstd_generator::collect_extensions_types(t_struct* tstruct)
+{
+    const vector<t_field*>& members = tstruct->get_members();
+    vector<t_field*>::const_iterator m_iter;
+
+    // make private members with public Properties
+    for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
+    {
+        collect_extensions_types((*m_iter)->get_type());
+    }    
+}
+
+void t_netstd_generator::collect_extensions_types(t_type* ttype)
+{
+    ttype = resolve_typedef( ttype);
+    string key = type_name(ttype);
+    
+    if (ttype->is_struct() || ttype->is_xception())
+    {
+        if( checked_extension_types.find(key) == checked_extension_types.end())
+        {
+            checked_extension_types[key] = ttype;    // prevent recursion
+            
+            t_struct* tstruct = static_cast<t_struct*>(ttype);
+            collect_extensions_types(tstruct);
+        }
+        return;
+    }
+    
+    if (ttype->is_map() || ttype->is_set() || ttype->is_list())
+    {
+        if( collected_extension_types.find(key) == collected_extension_types.end())
+        {
+            collected_extension_types[key] = ttype;   // prevent recursion
+            
+            if( ttype->is_map())
+            {
+                t_map* tmap = static_cast<t_map*>(ttype);
+                collect_extensions_types(tmap->get_key_type());
+                collect_extensions_types(tmap->get_val_type());
+            } 
+            else if (ttype->is_set())
+            {
+                t_set* tset = static_cast<t_set*>(ttype);
+                collect_extensions_types(tset->get_elem_type());
+            } 
+            else if (ttype->is_list())
+            {
+                t_list* tlist = static_cast<t_list*>(ttype);
+                collect_extensions_types(tlist->get_elem_type());
+            }
+        }
+
+        return;
+    }
+}
+
+void t_netstd_generator::generate_extensions_file()
+{
+    if (collected_extension_types.empty())
+    {
+        return;
+    }
+
+    string f_exts_name = namespace_dir_ + '/' + program_name_ + ".Extensions.cs";
+    ofstream_with_content_based_conditional_update f_exts;
+    f_exts.open(f_exts_name.c_str());
+
+    generate_extensions(f_exts, collected_extension_types);
+
+    f_exts.close();
+}
+
+void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*> types)
+{
+    if (types.empty())
+    {
+        return;
+    }
+
+    reset_indent();
+    out << autogen_comment() << netstd_type_usings() << endl;
+
+    start_netstd_namespace(out);
+
+    out << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Extensions" << endl;
+    scope_up(out);
+
+    bool needs_typecast = false;
+    std::map<string,t_type*>::const_iterator iter;
+    for( iter = types.begin(); iter != types.end(); ++iter)
+    {
+        out << indent() << "public static bool Equals(this " << iter->first << " instance, object that)" << endl;
+        scope_up(out);
+        out << indent() << "if (!(that is " << iter->first << " other)) return false;" << endl;
+        out << indent() << "if (ReferenceEquals(instance, other)) return true;" << endl;
+        out << endl;
+        out << indent() << "return TCollections.Equals(instance, other);" << endl;
+        scope_down(out);
+        out << endl << endl;
+        
+        out << indent() << "public static int GetHashCode(this " << iter->first << " instance)" << endl;
+        scope_up(out);
+        out << indent() << "return TCollections.GetHashCode(instance);" << endl;
+        scope_down(out);
+        out << endl << endl;
+
+        if(! suppress_deepcopy) {
+            out << indent() << "public static " << iter->first << " " << DEEP_COPY_METHOD_NAME << "(this " << iter->first << " source)" << endl;
+            scope_up(out);
+            out << indent() << "if (source == null)" << endl;
+            indent_up();
+            out << indent() << "return null;" << endl << endl;
+            indent_down();
+
+            string tmp_instance = tmp("tmp");
+            out << indent() << "var " << tmp_instance << " = new " << iter->first << "(source.Count);" << endl;
+            if( iter->second->is_map())
+            {
+                t_map* tmap = static_cast<t_map*>(iter->second);
+                string copy_key = get_deep_copy_method_call(tmap->get_key_type(), needs_typecast);
+                string copy_val = get_deep_copy_method_call(tmap->get_val_type(), needs_typecast);
+                bool null_key = type_can_be_null(tmap->get_key_type());
+                bool null_val = type_can_be_null(tmap->get_val_type());
+                
+                out << indent() << "foreach (var pair in source)" << endl;
+                indent_up();
+                out << indent() << tmp_instance << ".Add(";
+                if( null_key)
+                {
+                    out << "(pair.Key != null) ? pair.Key" << copy_key << " : null";
+                } else {
+                    out << "pair.Key" << copy_key;
+                }
+                out << ", ";
+                if( null_val)
+                {
+                    out << "(pair.Value != null) ? pair.Value" << copy_val << " : null";
+                } else {
+                    out << "pair.Value" << copy_val;
+                }
+                out << ");" << endl;
+                indent_down();
+                
+            } else if( iter->second->is_set() || iter->second->is_list()) {
+                string copy_elm;
+                bool null_elm = false;
+                if (iter->second->is_set())
+                {
+                    t_set* tset = static_cast<t_set*>(iter->second);
+                    copy_elm = get_deep_copy_method_call(tset->get_elem_type(), needs_typecast);
+                    null_elm = type_can_be_null(tset->get_elem_type());
+                }
+                else // list
+                {
+                    t_list* tlist = static_cast<t_list*>(iter->second);
+                    copy_elm = get_deep_copy_method_call(tlist->get_elem_type(), needs_typecast);
+                    null_elm = type_can_be_null(tlist->get_elem_type());
+                }
+
+                out << indent() << "foreach (var elem in source)" << endl;
+                indent_up();
+                out << indent() << tmp_instance << ".Add(";
+                if( null_elm)
+                {
+                    out << "(elem != null) ? elem" << copy_elm << " : null";
+                } else {
+                    out << "elem" << copy_elm;
+                }
+                out << ");" << endl;
+                indent_down();
+            }
+
+            out << indent() << "return " << tmp_instance << ";" << endl;
+            scope_down(out);
+            out << endl << endl;
+        }
+    }
+
+
+    scope_down(out);
+    end_netstd_namespace(out);
+}
+
 void t_netstd_generator::generate_struct(t_struct* tstruct)
 {
+    collect_extensions_types(tstruct);
+    
     if (is_union_enabled() && tstruct->is_union())
     {
         generate_netstd_union(tstruct);
@@ -707,6 +905,7 @@
     out << endl;
 
     generate_netstd_doc(out, tstruct);
+    collect_extensions_types(tstruct);    
     prepare_member_name_mapping(tstruct);
 
     if ((is_serialize_enabled() || is_wcf_enabled()) && !is_exception)
@@ -753,7 +952,7 @@
         if (is_required)
         {
             has_required_fields = true;
-        }
+    }
         else
         {
             has_non_required_fields = true;
@@ -828,10 +1027,8 @@
     for (m_iter = members.begin(); m_iter != members.end(); ++m_iter)
     {
         t_type* t = (*m_iter)->get_type();
-        while (t->is_typedef())
-        {
-            t = static_cast<t_typedef*>(t)->get_type();
-        }
+        t = resolve_typedef(t);
+
         if ((*m_iter)->get_value() != NULL)
         {
             if (field_is_required((*m_iter)))
@@ -849,6 +1046,7 @@
     indent_down();
     out << indent() << "}" << endl << endl;
 
+    // if we have required fields, we add that CTOR too
     if (has_required_fields)
     {
         out << indent() << "public " << sharp_struct_name << "(";
@@ -884,6 +1082,9 @@
         out << indent() << "}" << endl << endl;
     }
 
+    // DeepCopy()
+    generate_netstd_deepcopy_method(out, tstruct, sharp_struct_name);
+
     generate_netstd_struct_reader(out, tstruct);
     if (is_result)
     {
@@ -913,6 +1114,7 @@
     }
 }
 
+
 void t_netstd_generator::generate_netstd_wcffault(ostream& out, t_struct* tstruct)
 {
     out << endl;
@@ -947,6 +1149,57 @@
     out << indent() << "}" << endl << endl;
 }
 
+void t_netstd_generator::generate_netstd_deepcopy_method(ostream& out, t_struct* tstruct, std::string sharp_struct_name)
+{
+    if( suppress_deepcopy) {
+        return;  // feature disabled
+    }
+        
+    const vector<t_field*>& members = tstruct->get_members();
+    vector<t_field*>::const_iterator m_iter;
+
+    out << indent() << "public " << sharp_struct_name << " " << DEEP_COPY_METHOD_NAME << "()" << endl;
+    out << indent() << "{" << endl;
+    indent_up();
+
+    // return directly if there are only required fields
+    string tmp_instance = tmp("tmp");
+    out << indent() << "var " << tmp_instance << " = new " << sharp_struct_name << "();" << endl;
+
+    for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+        bool needs_typecast = false;
+        t_type* ttype = (*m_iter)->get_type();
+        string copy_op = get_deep_copy_method_call(ttype, needs_typecast);
+        
+        bool have_indent = false;
+        if (!field_is_required(*m_iter)) {
+            out << indent() << "if( this.__isset." << normalize_name((*m_iter)->get_name()) << ")" << endl;
+            indent_up();
+            have_indent = true;
+        } 
+        else if( type_can_be_null(ttype)) {
+            out << indent() << "if( this." << prop_name(*m_iter) << " != null)" << endl;
+            indent_up();
+            have_indent = true;
+        } 
+
+        out << indent() << tmp_instance << "." << prop_name(*m_iter) << " = ";
+        if( needs_typecast) {
+            out << "(" << type_name(ttype) << ")";
+        }
+        out << "this." << prop_name(*m_iter) << copy_op << ";" << endl;
+
+        if (have_indent) {
+            indent_down();
+        }
+    }
+
+    out << indent() << "return " << tmp_instance << ";" << endl;
+    
+    indent_down();
+    out << indent() << "}" << endl << endl;
+}
+
 void t_netstd_generator::generate_netstd_struct_reader(ostream& out, t_struct* tstruct)
 {
     out << indent() << "public async Task ReadAsync(TProtocol iprot, CancellationToken cancellationToken)" << endl
@@ -1298,8 +1551,8 @@
     // Let's define the class first
     start_netstd_namespace(out);
 
-    out << indent() << "public abstract partial class " << tunion->get_name() << " : TUnionBase" << endl
-        << indent() << "{" << endl;
+    out << indent() << "public abstract partial class " << tunion->get_name() << " : TUnionBase" << endl;
+    out << indent() << "{" << endl;
     indent_up();
 
     out << indent() << "public abstract Task WriteAsync(TProtocol tProtocol, CancellationToken cancellationToken);" << endl
@@ -1312,13 +1565,106 @@
     indent_down();
     out << indent() << "}" << endl << endl;
 
-    out << indent() << "public class ___undefined : " << tunion->get_name() << endl
-        << indent() << "{" << endl;
+    const vector<t_field*>& fields = tunion->get_members();
+    vector<t_field*>::const_iterator f_iter;
+
+    out << indent() << "public override bool Equals(object that)" << endl;
+    scope_up(out);
+    out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl;
+    out << indent() << "if (ReferenceEquals(this, other)) return true;" << endl;
+    out << endl;
+    out << indent() << "if(this.Isset != other.Isset) return false;" << endl;
+    out << endl;
+    out << indent() << "switch (Isset)" << endl;
+    scope_up(out);
+    for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+    {
+        bool needs_typecast = false;
+        string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), needs_typecast);
+        out << indent() << "case " << (*f_iter)->get_key() << ":" << endl;
+        indent_up();
+        out << indent() << "return Equals(As_" << (*f_iter)->get_name() << ", other.As_" << (*f_iter)->get_name() << ");" << endl;
+        indent_down();
+    }                
+    out << indent() << "default:" << endl;
+    indent_up();
+    out << indent() << "return true;" << endl;
+    indent_down();
+    indent_down();
+    scope_down(out);
+    scope_down(out);
+    out << endl;
+
+    out << indent() << "public override int GetHashCode()" << endl;
+    out << indent() << "{" << endl;
+    indent_up();
+    out << indent() << "switch (Isset)" << endl;
+    out << indent() << "{" << endl;
+    indent_up();    
+    for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+    {
+        bool needs_typecast = false;
+        string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), needs_typecast);
+        out << indent() << "case " << (*f_iter)->get_key() << ":" << endl;
+        indent_up();
+        out << indent() << "return As_" << (*f_iter)->get_name() << ".GetHashCode();" << endl;
+        indent_down();
+    }                
+    out << indent() << "default:" << endl;
+    indent_up();
+    out << indent() << "return (new ___undefined()).GetHashCode();" << endl;
+    indent_down();
+    indent_down();
+    out << indent() << "}" << endl;
+    indent_down();
+    out << indent() << "}" << endl << endl;
+
+    if( ! suppress_deepcopy) {
+        out << indent() << "public " << tunion->get_name() << " DeepCopy()" << endl;
+        out << indent() << "{" << endl;
+        indent_up();
+        out << indent() << "switch (Isset)" << endl;
+        out << indent() << "{" << endl;
+        indent_up();    
+        for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+        {
+            bool needs_typecast = false;
+            string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), needs_typecast);
+            out << indent() << "case " << (*f_iter)->get_key() << ":" << endl;
+            indent_up();
+            out << indent() << "return new " << (*f_iter)->get_name() << "(As_" << (*f_iter)->get_name() << copy_op << ");" << endl;
+            indent_down();
+        }                
+        out << indent() << "default:" << endl;
+        indent_up();
+        out << indent() << "return new ___undefined();" << endl;
+        indent_down();
+        indent_down();
+        out << indent() << "}" << endl;
+        indent_down();
+        out << indent() << "}" << endl << endl;
+    }
+
+    out << indent() << "public class ___undefined : " << tunion->get_name() << endl;
+    out << indent() << "{" << endl;
     indent_up();
 
     out << indent() << "public override object Data { get { return null; } }" << endl
         << indent() << "public ___undefined() : base(0) {}" << endl << endl;
+        
+    if( ! suppress_deepcopy) {
+        out << indent() << "public new ___undefined DeepCopy()" << endl;
+        out << indent() << "{" << endl;
+        indent_up();
+        out << indent() << "return new ___undefined();" << endl;
+        indent_down();
+        out << indent() << "}" << endl << endl;
+    }
 
+    t_struct undefined_struct(program_,"___undefined");
+    generate_netstd_struct_equals(out, &undefined_struct);
+    generate_netstd_struct_hashcode(out, &undefined_struct);
+    
     out << indent() << "public override Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken)" << endl
         << indent() << "{" << endl;
     indent_up();
@@ -1328,9 +1674,6 @@
     indent_down();
     out << indent() << "}" << endl << endl;
 
-    const vector<t_field*>& fields = tunion->get_members();
-    vector<t_field*>::const_iterator f_iter;
-
     for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
     {
         generate_netstd_union_class(out, tunion, (*f_iter));
@@ -1360,8 +1703,8 @@
         << endl;
     
     
-    out << indent() << "public class " << tfield->get_name() << " : " << tunion->get_name() << endl
-        << indent() << "{" << endl;
+    out << indent() << "public class " << tfield->get_name() << " : " << tunion->get_name() << endl;
+    out << indent() << "{" << endl;
     indent_up();
 
     out << indent() << "private " << type_name(tfield->get_type()) << " _data;" << endl
@@ -1373,6 +1716,34 @@
     indent_down();
     out << indent() << "}" << endl;
 
+    if( ! suppress_deepcopy) {
+        out << indent() << "public new " << tfield->get_name() << " DeepCopy()" << endl;
+        out << indent() << "{" << endl;
+        indent_up();
+        bool needs_typecast = false;
+        string copy_op = get_deep_copy_method_call(tfield->get_type(), needs_typecast);
+        out << indent() << "return new " << tfield->get_name() << "(_data" << copy_op << ");" << endl;
+        indent_down();
+        out << indent() << "}" << endl << endl;
+    }
+
+    out << indent() << "public override bool Equals(object that)" << endl;
+    out << indent() << "{" << endl;
+    indent_up();
+    out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl;
+    out << indent() << "if (ReferenceEquals(this, other)) return true;" << endl;
+    out << endl;
+    out << indent() << "return Equals( _data, other.As_" << tfield->get_name() << ");" << endl;
+    indent_down();
+    out << indent() << "}" << endl << endl;
+
+    out << indent() << "public override int GetHashCode()" << endl;
+    out << indent() << "{" << endl;
+    indent_up();
+    out << indent() << "return _data.GetHashCode();" << endl;
+    indent_down();
+    out << indent() << "}" << endl << endl;
+
     out << indent() << "public override async Task WriteAsync(TProtocol oprot, CancellationToken cancellationToken) {" << endl;
     indent_up();
 
@@ -1403,6 +1774,7 @@
     out << indent() << "oprot.DecrementRecursionDepth();" << endl;
     indent_down();
     out << indent() << "}" << endl;
+    indent_down();
     out << indent() << "}" << endl;
     indent_down();
     out << indent() << "}" << endl << endl;
@@ -1544,6 +1916,9 @@
     f_service.close();
 
     indent_validate(ic, "generate_service.");
+
+    // right at the end, after everything else
+    generate_extensions_file();
 }
 
 void t_netstd_generator::generate_service_interface(ostream& out, t_service* tservice)
@@ -1602,6 +1977,7 @@
     for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter)
     {
         t_struct* ts = (*f_iter)->get_arglist();
+        collect_extensions_types(ts);
         generate_netstd_struct_definition(out, ts, false, true);
         generate_function_helpers(out, *f_iter);
     }
@@ -1659,6 +2035,7 @@
         indent_up();
 
         t_struct* arg_struct = (*functions_iterator)->get_arglist();
+        collect_extensions_types(arg_struct);
         prepare_member_name_mapping(arg_struct);
         const vector<t_field*>& fields = arg_struct->get_members();
         vector<t_field*>::const_iterator fld_iter;
@@ -1682,6 +2059,7 @@
             string resultname = (*functions_iterator)->get_name() + "Result";
             t_struct noargs(program_);
             t_struct* xs = (*functions_iterator)->get_xceptions();
+            collect_extensions_types(xs);
             prepare_member_name_mapping(xs, xs->get_members(), resultname);
 
             out << indent() << endl
@@ -1894,6 +2272,7 @@
         result.append(*f_iter);
     }
 
+    collect_extensions_types(&result);
     generate_netstd_struct_definition(out, &result, false, true, true);
 }
 
@@ -1944,6 +2323,7 @@
     out << "await _iAsync." << normalize_name(tfunction->get_name()) << "Async(";
 
     bool first = true;
+    collect_extensions_types(arg_struct);
     prepare_member_name_mapping(arg_struct);
     for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
     {
@@ -1970,6 +2350,7 @@
 
     vector<t_field*>::const_iterator x_iter;
 
+    collect_extensions_types(xs);
     prepare_member_name_mapping(xs, xs->get_members(), resultname);
     if (xceptions.size() > 0)
     {
@@ -2114,10 +2495,7 @@
 void t_netstd_generator::generate_deserialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless)
 {
     t_type* type = tfield->get_type();
-    while (type->is_typedef())
-    {
-        type = static_cast<t_typedef*>(type)->get_type();
-    }
+    type = resolve_typedef( type);
 
     if (type->is_void())
     {
@@ -2326,10 +2704,7 @@
 void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless)
 {
     t_type* type = tfield->get_type();
-    while (type->is_typedef())
-    {
-        type = static_cast<t_typedef*>(type)->get_type();
-    }
+    type = resolve_typedef( type);
 
     string name = prefix + (is_propertyless ? "" : prop_name(tfield));
 
@@ -2718,10 +3093,7 @@
 
 string t_netstd_generator::type_name(t_type* ttype)
 {
-    while (ttype->is_typedef())
-    {
-        ttype = static_cast<t_typedef*>(ttype)->get_type();
-    }
+    ttype = resolve_typedef(ttype);
 
     if (ttype->is_base_type())
     {
@@ -2768,11 +3140,10 @@
     case t_base_type::TYPE_VOID:
         return "void";
     case t_base_type::TYPE_STRING:
+        if (tbase->is_binary())
         {
-            if (tbase->is_binary())
-            {
-                return "byte[]";
-            }
+            return "byte[]";
+        } else {
             return "string";
         }
     case t_base_type::TYPE_BOOL:
@@ -2792,16 +3163,46 @@
     }
 }
 
+string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool& needs_typecast)
+{
+    ttype = resolve_typedef(ttype);
+
+    needs_typecast = false;
+    if (ttype->is_base_type())
+    {
+        t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base();
+        switch (tbase)
+        {
+        case t_base_type::TYPE_STRING:
+            if (ttype->is_binary())
+            {
+                return ".ToArray()";
+            } else {
+                return "";  // simple assignment will do, strings are immutable in C#
+            }
+            break;
+        default:
+            return "";  // simple assignment will do
+        }
+    }
+    else if (ttype->is_enum())
+    {
+        return "";  // simple assignment will do
+    }
+    else 
+    {
+        needs_typecast = (! ttype->is_container());
+        return "." + DEEP_COPY_METHOD_NAME + "()";
+    }
+}
+
 string t_netstd_generator::declare_field(t_field* tfield, bool init, string prefix)
 {
     string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name();
     if (init)
     {
         t_type* ttype = tfield->get_type();
-        while (ttype->is_typedef())
-        {
-            ttype = static_cast<t_typedef*>(ttype)->get_type();
-        }
+        ttype = resolve_typedef(ttype);
         if (ttype->is_base_type() && field_has_default(tfield))
         {
             std::ofstream dummy;
@@ -2897,10 +3298,7 @@
 
 string t_netstd_generator::type_to_enum(t_type* type)
 {
-    while (type->is_typedef())
-    {
-        type = static_cast<t_typedef*>(type)->get_type();
-    }
+    type = resolve_typedef( type);
 
     if (type->is_base_type())
     {
@@ -3051,4 +3449,5 @@
     "    serial:          Add serialization support to generated classes.\n"
     "    union:           Use new union typing, which includes a static read function for union types.\n"
     "    pascal:          Generate Pascal Case property names according to Microsoft naming convention.\n"
+    "    no_deepcopy:     Suppress generation of DeepCopy() method.\n"
 )
diff --git a/compiler/cpp/src/thrift/generate/t_netstd_generator.h b/compiler/cpp/src/thrift/generate/t_netstd_generator.h
index 30082e8..ccbd902 100644
--- a/compiler/cpp/src/thrift/generate/t_netstd_generator.h
+++ b/compiler/cpp/src/thrift/generate/t_netstd_generator.h
@@ -45,6 +45,8 @@
 
 static const string endl = "\n"; // avoid ostream << std::endl flushes
 
+static const string DEEP_COPY_METHOD_NAME = "DeepCopy";
+
 class t_netstd_generator : public t_oop_generator
 {
 
@@ -60,7 +62,6 @@
   t_netstd_generator(t_program* program, const map<string, string>& parsed_options, const string& option_string);
 
   bool is_wcf_enabled() const;
-  bool is_nullable_enabled() const;
   bool is_hashcode_enabled() const;
   bool is_serialize_enabled() const;
   bool is_union_enabled() const;
@@ -78,6 +79,9 @@
   void generate_xception(t_struct* txception);
   void generate_service(t_service* tservice);
 
+  // additional files
+  void generate_extensions_file();
+
   void generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset);
   void generate_netstd_property(ostream& out, t_field* tfield, bool isPublic, bool includeIsset = true, string fieldPrefix = "");
   bool print_const_value(ostream& out, string name, t_type* type, t_const_value* value, bool in_static, bool defval = false, bool needtype = false);
@@ -90,6 +94,7 @@
   void generate_netstd_union_definition(ostream& out, t_struct* tunion);
   void generate_netstd_union_class(ostream& out, t_struct* tunion, t_field* tfield);
   void generate_netstd_wcffault(ostream& out, t_struct* tstruct);
+  void generate_netstd_deepcopy_method(ostream& out, t_struct* tstruct, std::string sharp_struct_name);
   void generate_netstd_struct_reader(ostream& out, t_struct* tstruct);
   void generate_netstd_struct_result_writer(ostream& out, t_struct* tstruct);
   void generate_netstd_struct_writer(ostream& out, t_struct* tstruct);
@@ -141,17 +146,19 @@
   string namespace_name_;
   string namespace_dir_;
 
-  bool nullable_;
   bool union_;
   bool hashcode_;
   bool serialize_;
   bool wcf_;
   bool use_pascal_case_properties;
+  bool suppress_deepcopy;
 
   string wcf_namespace_;
   map<string, int> netstd_keywords;
   vector<member_mapping_scope> member_mapping_scopes;
-
+  map<string, t_type*> collected_extension_types;
+  map<string, t_type*> checked_extension_types;
+  
   void init_keywords();
   string normalize_name(string name);
   string make_valid_csharp_identifier(string const& fromName);
@@ -159,5 +166,9 @@
   void prepare_member_name_mapping(void* scope, const vector<t_field*>& members, const string& structname);
   void cleanup_member_name_mapping(void* scope);
   string get_mapped_member_name(string oldname);
+  string get_deep_copy_method_call(t_type* ttype, bool& needs_typecast);
+  void collect_extensions_types(t_struct* tstruct);
+  void collect_extensions_types(t_type* ttype);
+  void generate_extensions(ostream& out, map<string, t_type*> types);
   void reset_indent();
 };
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>");
             }
         }
     }