You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by je...@apache.org on 2022/02/08 16:46:10 UTC

[thrift] branch master updated: THRIFT-5511 Full support for the new net6 "nullability" semantics Client: netstd Patch: Jens Geyer

This is an automated email from the ASF dual-hosted git repository.

jensg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/thrift.git


The following commit(s) were added to refs/heads/master by this push:
     new 3cac320  THRIFT-5511 Full support for the new net6 "nullability" semantics Client: netstd Patch: Jens Geyer
3cac320 is described below

commit 3cac3204519bbdfe02beb9d863e9b873cdaf9d07
Author: Jens Geyer <je...@apache.org>
AuthorDate: Mon Jan 31 18:04:35 2022 +0100

    THRIFT-5511 Full support for the new net6 "nullability" semantics
    Client: netstd
    Patch: Jens Geyer
    
    This closes #2516
---
 .../cpp/src/thrift/generate/t_netstd_generator.cc  | 641 +++++++++++++++------
 .../cpp/src/thrift/generate/t_netstd_generator.h   |  36 +-
 .../Impl/Thrift5253/MyService.cs                   |  24 +-
 .../Thrift.PublicInterfaces.Compile.Tests.csproj   |  16 +-
 .../Thrift.Tests/Collections/TCollectionsTests.cs  |  18 +-
 .../Thrift.Tests/Collections/THashSetTests.cs      |   6 +-
 .../Tests/Thrift.Tests/DataModel/DeepCopy.cs       |  91 +--
 .../Tests/Thrift.Tests/DataModel/NullValuesSet.cs  |  14 +-
 lib/netstd/Thrift/Collections/THashSet.cs          |  60 +-
 test/netstd/Client/Client.csproj                   |   6 +-
 test/netstd/Client/Performance/TestDataFactory.cs  |  24 +-
 test/netstd/Client/TestClient.cs                   |  13 +-
 test/netstd/Server/Server.csproj                   |   6 +-
 test/netstd/Server/TestServer.cs                   | 153 ++---
 tutorial/netstd/Interfaces/Interfaces.csproj       |   6 +-
 tutorial/netstd/Server/Program.cs                  |   2 +-
 16 files changed, 713 insertions(+), 403 deletions(-)

diff --git a/compiler/cpp/src/thrift/generate/t_netstd_generator.cc b/compiler/cpp/src/thrift/generate/t_netstd_generator.cc
index bfe3c07..d3f3a1b 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_netstd_generator::t_netstd_generator(t_program* program, const map<string, str
     : t_oop_generator(program)
 {
     (void)option_string;
+    use_net6_features = false;
     suppress_deepcopy = false;
     add_async_postfix = false;
     use_pascal_case_properties = false;
@@ -81,6 +82,9 @@ t_netstd_generator::t_netstd_generator(t_program* program, const map<string, str
         else if (iter->first.compare("no_deepcopy") == 0) {
           suppress_deepcopy = true;
         }
+        else if (iter->first.compare("net6") == 0) {
+          use_net6_features = true;
+        }
         else if (iter->first.compare("async_postfix") == 0) {
           add_async_postfix = true;
         }
@@ -159,6 +163,7 @@ void t_netstd_generator::init_generator()
     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("- net6 ............ %s\n", (use_net6_features ? "ON" : "off"));
     pverbose("- no_deepcopy ..... %s\n", (suppress_deepcopy ? "ON" : "off"));
     pverbose("- async_postfix ... %s\n", (add_async_postfix ? "ON" : "off"));
 }
@@ -179,7 +184,7 @@ string t_netstd_generator::normalize_name(string name, bool is_arg_name)
     {
         return "@" + name;
     }
-    
+
     // no changes necessary
     return name;
 }
@@ -302,14 +307,31 @@ void t_netstd_generator::reset_indent() {
 }
 
 
-void t_netstd_generator::start_netstd_namespace(ostream& out)
+void t_netstd_generator::pragmas_and_directives(ostream& out)
 {
-    out << "#nullable disable                // suppress C# 8.0 nullable contexts (we still support earlier versions)" << endl
-        << "#pragma warning disable IDE0079  // remove unnecessary pragmas" << endl
+    if( use_net6_features) {
+      out << "#nullable enable                 // requires C# 8.0" << endl;
+    } else {
+      out << "#nullable disable                // suppress C# 8.0 nullable contexts (we still support earlier versions)" << endl;
+    }
+
+    // this one must be first
+    out << "#pragma warning disable IDE0079  // remove unnecessary pragmas" << endl;
+
+    out << "#pragma warning disable IDE0017  // object init can be simplified" << endl
+        << "#pragma warning disable IDE0028  // collection init can be simplified" << endl
         << "#pragma warning disable IDE1006  // parts of the code use IDL spelling" << endl
-        << "#pragma warning disable IDE0083  // pattern matching \"that is not SomeType\" requires net5.0 but we still support earlier versions" << endl
-        << endl;
+        << "#pragma warning disable CA1822   // empty " << DEEP_COPY_METHOD_NAME << "() methods still non-static" << endl;
+
+    if( ! use_net6_features) {
+        out << "#pragma warning disable IDE0083  // pattern matching \"that is not SomeType\" requires net5.0 but we still support earlier versions" << endl;
+    }
+    out << endl;
+}
+
 
+void t_netstd_generator::start_netstd_namespace(ostream& out)
+{
     if (!namespace_name_.empty())
     {
         out << "namespace " << namespace_name_ << endl;
@@ -346,7 +368,7 @@ string t_netstd_generator::netstd_type_usings() const
         namespaces += "using System.Runtime.Serialization;\n";
     }
 
-    return namespaces + endl;
+    return namespaces;
 }
 
 string t_netstd_generator::netstd_thrift_usings() const
@@ -360,7 +382,7 @@ string t_netstd_generator::netstd_thrift_usings() const
         "using Thrift.Transport.Server;\n"
         "using Thrift.Processor;\n";
 
-    return namespaces + endl;
+    return namespaces;
 }
 
 void t_netstd_generator::close_generator()
@@ -393,7 +415,8 @@ void t_netstd_generator::generate_enum(ostream& out, t_enum* tenum)
     reset_indent();
     out << autogen_comment() << endl;
 
-    start_netstd_namespace(out);    
+    pragmas_and_directives(out);
+    start_netstd_namespace(out);
     generate_netstd_doc(out, tenum);
 
     out << indent() << "public enum " << type_name(tenum,false) << endl;
@@ -437,8 +460,9 @@ void t_netstd_generator::generate_consts(ostream& out, vector<t_const*> consts)
     }
 
     reset_indent();
-    out << autogen_comment() << netstd_type_usings() << endl;
+    out << autogen_comment() << netstd_type_usings() << endl << endl;
 
+    pragmas_and_directives(out);
     start_netstd_namespace(out);
 
     out << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Constants" << endl;
@@ -680,7 +704,7 @@ void t_netstd_generator::collect_extensions_types(t_type* ttype)
         return;
     }
 
-    if (ttype->is_map() || ttype->is_set() || ttype->is_list())
+    if (ttype->is_container())
     {
         if( collected_extension_types.find(key) == collected_extension_types.end())
         {
@@ -702,6 +726,10 @@ void t_netstd_generator::collect_extensions_types(t_type* ttype)
                 t_list* tlist = static_cast<t_list*>(ttype);
                 collect_extensions_types(tlist->get_elem_type());
             }
+            else
+            {
+                throw "compiler error: unhandled container type " + ttype->get_name();
+            }
         }
 
         return;
@@ -732,8 +760,11 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*>
     }
 
     reset_indent();
-    out << autogen_comment() << netstd_type_usings() << endl;
+    out << autogen_comment() << netstd_type_usings()
+        << "using Thrift.Protocol;" << endl
+        << endl << endl;
 
+    pragmas_and_directives(out);
     start_netstd_namespace(out);
 
     out << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Extensions" << endl;
@@ -745,7 +776,11 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*>
     {
         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;
+        if( use_net6_features) {
+            out << indent() << "if (that is not " << iter->first << " other) return false;" << endl;
+        } else {
+            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;
@@ -759,38 +794,42 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*>
         out << endl << endl;
 
         if(! suppress_deepcopy) {
-            out << indent() << "public static " << iter->first << " " << DEEP_COPY_METHOD_NAME << "(this " << iter->first << " source)" << endl;
+            out << indent() << "public static " << iter->first << nullable_field_suffix(iter->second) << " " << DEEP_COPY_METHOD_NAME << "(this " << iter->first << nullable_field_suffix(iter->second) << " source)" << endl;
             scope_up(out);
             out << indent() << "if (source == null)" << endl;
             indent_up();
             out << indent() << "return null;" << endl << endl;
             indent_down();
 
+            string suffix("");
             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);
+                string copy_key = get_deep_copy_method_call(tmap->get_key_type(), true, needs_typecast, suffix);
+                string copy_val = get_deep_copy_method_call(tmap->get_val_type(), true, needs_typecast, suffix);
                 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";
+                if( use_net6_features) {
+                    out << indent() << tmp_instance << ".Add(pair.Key" << copy_key;
+                    out << ", pair.Value" << copy_val;
                 } 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 << 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();
@@ -801,24 +840,28 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*>
                 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);
+                    copy_elm = get_deep_copy_method_call(tset->get_elem_type(), true, needs_typecast, suffix);
                     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);
+                    copy_elm = get_deep_copy_method_call(tlist->get_elem_type(), true, needs_typecast, suffix);
                     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";
+                if( use_net6_features) {
+                    out << indent() << tmp_instance << ".Add(elem" << copy_elm;
                 } else {
-                    out << "elem" << copy_elm;
+                    out << indent() << tmp_instance << ".Add(";
+                    if( null_elm)
+                    {
+                        out << "(elem != null) ? elem" << copy_elm << " : null";
+                    } else {
+                        out << "elem" << copy_elm;
+                    }
                 }
                 out << ");" << endl;
                 indent_down();
@@ -864,8 +907,9 @@ void t_netstd_generator::generate_netstd_struct(t_struct* tstruct, bool is_excep
     f_struct.open(f_struct_name.c_str());
 
     reset_indent();
-    f_struct << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl;
+    f_struct << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl << endl;
 
+    pragmas_and_directives(f_struct);
     generate_netstd_struct_definition(f_struct, tstruct, is_exception);
 
     f_struct.close();
@@ -915,7 +959,7 @@ void t_netstd_generator::generate_netstd_struct_definition(ostream& out, t_struc
         // if the field is required, then we use auto-properties
         if (!field_is_required((*m_iter)))
         {
-            out << indent() << "private " << declare_field(*m_iter, false, "_") << endl;
+            out << indent() << "private " << declare_field(*m_iter, false, true, "_") << endl;
         }
     }
     out << endl;
@@ -930,7 +974,7 @@ void t_netstd_generator::generate_netstd_struct_definition(ostream& out, t_struc
         if (is_required)
         {
             has_required_fields = true;
-    }
+        }
         else
         {
             has_non_required_fields = true;
@@ -1041,7 +1085,7 @@ void t_netstd_generator::generate_netstd_struct_definition(ostream& out, t_struc
                 {
                     out << ", ";
                 }
-                out << type_name((*m_iter)->get_type()) << " " << normalize_name((*m_iter)->get_name());
+                out << type_name((*m_iter)->get_type()) << nullable_field_suffix(*m_iter) << " " << normalize_name((*m_iter)->get_name());
             }
         }
         out << ") : this()" << endl
@@ -1113,7 +1157,7 @@ void t_netstd_generator::generate_netstd_wcffault(ostream& out, t_struct* tstruc
         // if the field is required, then we use auto-properties
         if (!field_is_required((*m_iter)))
         {
-            out << indent() << "private " << declare_field(*m_iter, false, "_") << endl;
+            out << indent() << "private " << declare_field(*m_iter, false, true, "_") << endl;
         }
     }
     out << endl;
@@ -1146,8 +1190,9 @@ void t_netstd_generator::generate_netstd_deepcopy_method(ostream& out, t_struct*
 
     for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
         bool needs_typecast = false;
+        string suffix("");
         t_type* ttype = (*m_iter)->get_type();
-        string copy_op = get_deep_copy_method_call(ttype, needs_typecast);
+        string copy_op = get_deep_copy_method_call(ttype, true, needs_typecast, suffix);
 
         bool is_required = field_is_required(*m_iter);
         generate_null_check_begin( out, *m_iter);
@@ -1511,8 +1556,9 @@ void t_netstd_generator::generate_netstd_union(t_struct* tunion)
     f_union.open(f_union_name.c_str());
 
     reset_indent();
-    f_union << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl;
+    f_union << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl << endl;
 
+    pragmas_and_directives(f_union);
     generate_netstd_union_definition(f_union, tunion);
 
     f_union.close();
@@ -1531,7 +1577,7 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct
 
     out << indent() << "public abstract global::System.Threading.Tasks.Task WriteAsync(TProtocol tProtocol, CancellationToken " << CANCELLATION_TOKEN_NAME << ");" << endl
         << indent() << "public readonly int Isset;" << endl
-        << indent() << "public abstract object Data { get; }" << endl
+        << indent() << "public abstract object" << nullable_suffix() <<" Data { get; }" << endl
         << indent() << "protected " << tunion->get_name() << "(int isset)" << endl
         << indent() << "{" << endl;
     indent_up();
@@ -1542,78 +1588,136 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct
     const vector<t_field*>& fields = tunion->get_members();
     vector<t_field*>::const_iterator f_iter;
 
-    out << indent() << "public override bool Equals(object that)" << endl;
+    out << indent() << "public override bool Equals(object" << nullable_suffix() << " that)" << endl;
     scope_up(out);
-    out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl;
+    if( use_net6_features) {
+        out << indent() << "if (that is not " << tunion->get_name() << " other) return false;" << endl;
+    } else {
+        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;
+    if(use_net6_features) {
+        out << indent() << "return Isset switch" << endl;
+        scope_up(out);
+        for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+        {
+            bool needs_typecast = false;
+            string suffix("");
+            get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix);
+            out << indent() << (*f_iter)->get_key() << " => Equals(As_" << (*f_iter)->get_name() << ", other.As_" << (*f_iter)->get_name() << ")," << endl;
+        }
+        out << indent() << "_ => true," << endl;
+        indent_down();
+        out << indent() << "};" << endl;
+    } else {
+        out << indent() << "switch (Isset)" << endl;
+        scope_up(out);
+        for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+        {
+            bool needs_typecast = false;
+            string suffix("");
+            get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix);
+            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 Equals(As_" << (*f_iter)->get_name() << ", other.As_" << (*f_iter)->get_name() << ");" << endl;
+        out << indent() << "return true;" << endl;
         indent_down();
+        scope_down(out);
     }
-    out << indent() << "default:" << endl;
-    indent_up();
-    out << indent() << "return true;" << endl;
-    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;
+    if(use_net6_features) {
+        out << indent() << "return Isset switch" << endl;
         out << indent() << "{" << endl;
         indent_up();
+        for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+        {
+            string null_coalesce(is_nullable_type((*f_iter)->get_type()) ? "?" : "");  
+            out << indent() << (*f_iter)->get_key() << " => As_" << (*f_iter)->get_name() << null_coalesce << ".GetHashCode()";
+            if( null_coalesce.size() > 0) {
+              out << " ?? 0";
+            }
+            out << "," << endl;
+        }
+        out << indent() << "_ =>  (new ___undefined()).GetHashCode()" << endl;
+        indent_down();
+        out << indent() << "};" << endl;
+    } else {
         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);
+            string null_coalesce(is_nullable_type((*f_iter)->get_type()) ? "?" : "");  
             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;
+            out << indent() << "return As_" << (*f_iter)->get_name() << null_coalesce << ".GetHashCode()";
+            if( null_coalesce.size() > 0) {
+              out << " ?? 0";
+            }
+            out << ";" << endl;
             indent_down();
         }
         out << indent() << "default:" << endl;
         indent_up();
-        out << indent() << "return new ___undefined();" << endl;
+        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() << " " << DEEP_COPY_METHOD_NAME << "()" << endl;
+        out << indent() << "{" << endl;
+        indent_up();
+        if(use_net6_features) {
+            out << indent() << "return Isset switch" << endl;
+            out << indent() << "{" << endl;
+            indent_up();
+            for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter)
+            {
+                bool needs_typecast = false;
+                string suffix("");
+                string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix);
+                out << indent() << (*f_iter)->get_key() << " => new " << (*f_iter)->get_name() << "(As_" << (*f_iter)->get_name() << suffix << copy_op << ")," << endl;
+            }
+            out << indent() << "_ => new ___undefined()" << endl;
+            indent_down();
+            out << indent() << "};" << endl;
+        } else {
+            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 suffix("");
+                string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix);
+                out << indent() << "case " << (*f_iter)->get_key() << ":" << endl;
+                indent_up();
+                out << indent() << "return new " << (*f_iter)->get_name() << "(As_" << (*f_iter)->get_name() << suffix << 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;
     }
@@ -1622,11 +1726,11 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct
     out << indent() << "{" << endl;
     indent_up();
 
-    out << indent() << "public override object Data { get { return null; } }" << endl
+    out << indent() << "public override object" << nullable_suffix() <<" Data { get { return null; } }" << endl
         << indent() << "public ___undefined() : base(0) {}" << endl << endl;
 
     if( ! suppress_deepcopy) {
-        out << indent() << "public new ___undefined DeepCopy()" << endl;
+        out << indent() << "public new ___undefined " << DEEP_COPY_METHOD_NAME << "()" << endl;
         out << indent() << "{" << endl;
         indent_up();
         out << indent() << "return new ___undefined();" << endl;
@@ -1662,13 +1766,17 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct
 
 void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tunion, t_field* tfield)
 {
-    out << indent() << "public " << type_name(tfield->get_type()) << " As_" << tfield->get_name() << endl;
+    out << indent() << "public " << type_name(tfield->get_type()) << nullable_field_suffix(tfield) << " As_" << tfield->get_name() << endl;
     out << indent() << "{" << endl;
     indent_up();
     out << indent() << "get" << endl;
     out << indent() << "{" << endl;
     indent_up();
-    out << indent() << "return (" << tfield->get_key() << " == Isset) ? (" << type_name(tfield->get_type()) << ")Data : default(" << type_name(tfield->get_type()) << ");" << endl;
+    out << indent() << "return (" << tfield->get_key() << " == Isset) && (Data != null)"
+        << " ? (" << type_name(tfield->get_type()) << nullable_field_suffix(tfield) << ")Data"
+        << " : default"
+        << (use_net6_features ? "" : ("(" + type_name(tfield->get_type()) + ")"))
+        << ";" << endl;
     indent_down();
     out << indent() << "}" << endl;
     indent_down();
@@ -1680,8 +1788,8 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun
     out << indent() << "{" << endl;
     indent_up();
 
-    out << indent() << "private " << type_name(tfield->get_type()) << " _data;" << endl
-        << indent() << "public override object Data { get { return _data; } }" << endl
+    out << indent() << "private readonly " << type_name(tfield->get_type()) << " _data;" << endl
+        << indent() << "public override object" << nullable_suffix() <<" Data { get { return _data; } }" << endl
         << indent() << "public " << tfield->get_name() << "(" << type_name(tfield->get_type()) << " data) : base("<< tfield->get_key() <<")" << endl
         << indent() << "{" << endl;
     indent_up();
@@ -1690,20 +1798,25 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun
     out << indent() << "}" << endl;
 
     if( ! suppress_deepcopy) {
-        out << indent() << "public new " << tfield->get_name() << " DeepCopy()" << endl;
+        out << indent() << "public new " << tfield->get_name() << " " << DEEP_COPY_METHOD_NAME << "()" << endl;
         out << indent() << "{" << endl;
         indent_up();
         bool needs_typecast = false;
-        string copy_op = get_deep_copy_method_call(tfield->get_type(), needs_typecast);
+        string suffix("");
+        string copy_op = get_deep_copy_method_call(tfield->get_type(), true, needs_typecast, suffix);
         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() << "public override bool Equals(object" << nullable_suffix() << " that)" << endl;
     out << indent() << "{" << endl;
     indent_up();
-    out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl;
+    if(use_net6_features) {
+        out << indent() << "if (that is not " << tunion->get_name() << " other) return false;" << endl;
+    } else {
+        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;
@@ -1734,7 +1847,7 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun
         << indent() << "field.ID = " << tfield->get_key() << ";" << endl
         << indent() << "await oprot.WriteFieldBeginAsync(field, " << CANCELLATION_TOKEN_NAME << ");" << endl;
 
-    generate_serialize_field(out, tfield, "_data", true);
+    generate_serialize_field(out, tfield, "_data", true, false);
 
     out << indent() << "await oprot.WriteFieldEndAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl
         << indent() << "await oprot.WriteFieldStopAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl
@@ -1755,11 +1868,15 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun
 
 void t_netstd_generator::generate_netstd_struct_equals(ostream& out, t_struct* tstruct)
 {
-    out << indent() << "public override bool Equals(object that)" << endl
+    out << indent() << "public override bool Equals(object" << nullable_suffix() << " that)" << endl
         << indent() << "{" << endl;
     indent_up();
-    out << indent() << "if (!(that is " << type_name(tstruct,false) << " other)) return false;" << endl
-        << indent() << "if (ReferenceEquals(this, other)) return true;" << endl;
+    if(use_net6_features) {
+        out << indent() << "if (that is not " << type_name(tstruct,false) << " other) return false;" << endl;
+    } else {
+        out << indent() << "if (!(that is " << type_name(tstruct,false) << " other)) return false;" << endl;
+    }
+    out << indent() << "if (ReferenceEquals(this, other)) return true;" << endl;
 
 
     const vector<t_field*>& fields = tstruct->get_members();
@@ -1837,7 +1954,7 @@ void t_netstd_generator::generate_netstd_struct_hashcode(ostream& out, t_struct*
             out << "TCollections.GetHashCode(" << prop_name((*f_iter)) << ")";
         }
         else {
-            out << prop_name((*f_iter)) << ".GetHashCode()";
+            out << prop_name(*f_iter) << ".GetHashCode()";
         }
         out << ";" << endl;
 
@@ -1861,8 +1978,9 @@ void t_netstd_generator::generate_service(t_service* tservice)
     f_service.open(f_service_name.c_str());
 
     reset_indent();
-    f_service << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl;
+    f_service << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl << endl;
 
+    pragmas_and_directives(f_service);
     start_netstd_namespace(f_service);
 
     f_service << indent() << "public partial class " << normalize_name(service_name_) << endl
@@ -2105,7 +2223,8 @@ void t_netstd_generator::generate_service_client(ostream& out, t_service* tservi
                 out << indent() << "if (" << tmpvar << ".__isset.success)" << endl
                     << indent() << "{" << endl;
                 indent_up();
-                out << indent() << "return " << tmpvar << ".Success;" << endl;
+                string nullable_value = nullable_value_access((*functions_iterator)->get_returntype());
+                out << indent() << "return " << tmpvar << ".Success" << nullable_value << ";" << endl;
                 indent_down();
                 out << indent() << "}" << endl;
             }
@@ -2117,7 +2236,7 @@ void t_netstd_generator::generate_service_client(ostream& out, t_service* tservi
                 out << indent() << "if (" << tmpvar << ".__isset." << get_isset_name(normalize_name((*x_iter)->get_name())) << ")" << endl
                     << indent() << "{" << endl;
                 indent_up();
-                out << indent() << "throw " << tmpvar << "." << prop_name(*x_iter) << ";" << endl;
+                out << indent() << "throw " << tmpvar << "." << prop_name(*x_iter) << nullable_value_access((*x_iter)->get_type()) << ";" << endl;
                 indent_down();
                 out << indent() << "}" << endl;
             }
@@ -2161,9 +2280,9 @@ void t_netstd_generator::generate_service_server(ostream& out, t_service* tservi
     indent_up();
 
     out << indent() << "private readonly IAsync _iAsync;" << endl
-        << indent() << "private readonly ILogger<AsyncProcessor> _logger;" << endl
+        << indent() << "private readonly ILogger<AsyncProcessor>" << nullable_suffix() << " _logger;" << endl
         << endl
-        << indent() << "public AsyncProcessor(IAsync iAsync, ILogger<AsyncProcessor> logger = default)";
+        << indent() << "public AsyncProcessor(IAsync iAsync, ILogger<AsyncProcessor>" << nullable_suffix() << " logger = default)";
 
     if (!extends.empty())
     {
@@ -2193,7 +2312,9 @@ void t_netstd_generator::generate_service_server(ostream& out, t_service* tservi
 
     if (extends.empty())
     {
-        out << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new Dictionary<string, ProcessFunction>();" << endl;
+        out << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new"
+            << (use_net6_features ? "" : " Dictionary<string, ProcessFunction>")  // Simplify new expression (IDE0090)
+            << "();" << endl;
     }
 
     out << endl;
@@ -2228,7 +2349,7 @@ void t_netstd_generator::generate_service_server(ostream& out, t_service* tservi
     indent_up();
     out << indent() << "var msg = await iprot.ReadMessageBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl
         << endl
-        << indent() << "processMap_.TryGetValue(msg.Name, out ProcessFunction fn);" << endl
+        << indent() << "processMap_.TryGetValue(msg.Name, out var fn);" << endl
         << endl
         << indent() << "if (fn == null)" << endl
         << indent() << "{" << endl;
@@ -2653,15 +2774,15 @@ void t_netstd_generator::generate_deserialize_container(ostream& out, t_type* tt
 
     if (ttype->is_map())
     {
-        out << indent() << "TMap " << obj << " = await iprot.ReadMapBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl;
+        out << indent() << "var " << obj << " = await iprot.ReadMapBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl;
     }
     else if (ttype->is_set())
     {
-        out << indent() << "TSet " << obj << " = await iprot.ReadSetBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl;
+        out << indent() << "var " << obj << " = await iprot.ReadSetBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl;
     }
     else if (ttype->is_list())
     {
-        out << indent() << "TList " << obj << " = await iprot.ReadListBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl;
+        out << indent() << "var " << obj << " = await iprot.ReadListBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl;
     }
 
     out << indent() << prefix << " = new " << type_name(ttype) << "(" << obj << ".Count);" << endl;
@@ -2711,8 +2832,8 @@ void t_netstd_generator::generate_deserialize_map_element(ostream& out, t_map* t
     t_field fkey(tmap->get_key_type(), key);
     t_field fval(tmap->get_val_type(), val);
 
-    out << indent() << declare_field(&fkey) << endl;
-    out << indent() << declare_field(&fval) << endl;
+    out << indent() << declare_field(&fkey, false, false) << endl;
+    out << indent() << declare_field(&fval, false, false) << endl;
 
     generate_deserialize_field(out, &fkey);
     generate_deserialize_field(out, &fval);
@@ -2725,7 +2846,7 @@ void t_netstd_generator::generate_deserialize_set_element(ostream& out, t_set* t
     string elem = tmp("_elem");
     t_field felem(tset->get_elem_type(), elem);
 
-    out << indent() << declare_field(&felem) << endl;
+    out << indent() << declare_field(&felem, false, false) << endl;
 
     generate_deserialize_field(out, &felem);
 
@@ -2737,19 +2858,20 @@ void t_netstd_generator::generate_deserialize_list_element(ostream& out, t_list*
     string elem = tmp("_elem");
     t_field felem(tlist->get_elem_type(), elem);
 
-    out << indent() << declare_field(&felem) << endl;
+    out << indent() << declare_field(&felem, false, false) << endl;
 
     generate_deserialize_field(out, &felem);
 
     out << indent() << prefix << ".Add(" << elem << ");" << endl;
 }
 
-void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless)
+void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless, bool allow_nullable)
 {
     t_type* type = tfield->get_type();
     type = resolve_typedef( type);
 
     string name = prefix + (is_propertyless ? "" : prop_name(tfield));
+    string nullable_name = name + (allow_nullable ? nullable_value_access(type) : "");
 
     if (type->is_void())
     {
@@ -2768,8 +2890,6 @@ void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield,
     {
         out << indent() << "await oprot.";
 
-        string nullable_name = name;
-
         if (type->is_base_type())
         {
             t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base();
@@ -2812,7 +2932,7 @@ void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield,
         }
         else if (type->is_enum())
         {
-            out << "WriteI32Async((int)" << nullable_name << ", " << CANCELLATION_TOKEN_NAME << ");";
+            out << "WriteI32Async((int)" << name << ", " << CANCELLATION_TOKEN_NAME << ");";
         }
         out << endl;
     }
@@ -2902,21 +3022,21 @@ void t_netstd_generator::generate_serialize_container(ostream& out, t_type* ttyp
 void t_netstd_generator::generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map)
 {
     t_field kfield(tmap->get_key_type(), iter);
-    generate_serialize_field(out, &kfield, "");
+    generate_serialize_field(out, &kfield, "", false, false);
     t_field vfield(tmap->get_val_type(), map + "[" + iter + "]");
-    generate_serialize_field(out, &vfield, "");
+    generate_serialize_field(out, &vfield, "", false, false);
 }
 
 void t_netstd_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter)
 {
     t_field efield(tset->get_elem_type(), iter);
-    generate_serialize_field(out, &efield, "");
+    generate_serialize_field(out, &efield, "", false, false);
 }
 
 void t_netstd_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter)
 {
     t_field efield(tlist->get_elem_type(), iter);
-    generate_serialize_field(out, &efield, "");
+    generate_serialize_field(out, &efield, "", false, false);
 }
 
 void t_netstd_generator::generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset)
@@ -2930,12 +3050,23 @@ void t_netstd_generator::generate_netstd_property(ostream& out, t_field* tfield,
     {
         out << indent() << "[DataMember(Order = 0)]" << endl;
     }
-    out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type()) << " " << prop_name(tfield);
+
+    out << indent()
+        << (isPublic ? "public " : "private ")
+        << type_name(tfield->get_type())
+        << nullable_field_suffix(tfield)
+        << " "
+        << prop_name(tfield)
+        ;
 
     bool is_required = field_is_required(tfield);
     if (is_required)
     {
-        out << " { get; set; }" << endl;
+        out << " { get; set; }";
+        if( use_net6_features && (!force_member_nullable(tfield))) {
+            out << initialize_field(tfield) << ";";
+        }
+        out << endl;
     }
     else
     {
@@ -3207,6 +3338,105 @@ string t_netstd_generator::func_name(std::string fname, bool suppress_mapping) {
   return get_mapped_member_name(fname);
 }
 
+bool t_netstd_generator::is_nullable_type(t_type* ttype) {
+  ttype = resolve_typedef(ttype);
+
+  if (ttype->is_enum()) {
+    return 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:
+      return true;  // both binary and string
+    default:
+      return false;
+    }
+  }
+
+  return true;
+}
+
+
+string t_netstd_generator::nullable_suffix() {
+  if(use_net6_features) {
+    return "?";
+  } else {
+    return "";
+  }
+}
+
+
+string t_netstd_generator::nullable_field_suffix(t_field* tfield) {
+  if(field_is_required(tfield) && (!force_member_nullable(tfield)))
+    return "";
+  else
+    return nullable_field_suffix(tfield->get_type());
+}
+  
+
+string t_netstd_generator::nullable_field_suffix(t_type* ttype) {
+  if( ! use_net6_features) {
+    return "";
+  }
+
+  ttype = resolve_typedef(ttype);
+
+  if (ttype->is_enum()) {
+    return "";
+  }
+
+  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:
+      return nullable_suffix();
+    default:
+      return "";
+    }
+  }
+
+  return nullable_suffix();
+}
+
+string t_netstd_generator::nullable_value_access(t_type* ttype) {
+  if( ! use_net6_features)
+    return "";
+
+  ttype = resolve_typedef(ttype);
+
+  // this code uses the null-forgiving operator and therefore assumes that the variable
+  // has been properly checked against an isset guard or null
+  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:
+      return "!";
+    default:
+      return "";
+    }
+  }
+
+  if (ttype->is_container() || ttype->is_struct() || ttype->is_xception()) {
+    return "!";
+  }
+
+  return "";
+}
+
+bool t_netstd_generator::force_member_nullable(t_field* tfield) {
+  // IMPORTANT: 
+  // If tfield is a struct that contains a required field of the same type (directly or indirectly),
+  // auto-initializing such a member field would immediately produce an OOM, or at least unexpectedly 
+  // allocate potentially large amounts of memory -> ALWAYS leave containers and struct members nullable
+  t_type* ttype = resolve_typedef(tfield->get_type());
+  return ttype->is_struct() || ttype->is_container();
+}
+
 string t_netstd_generator::type_name(t_type* ttype, bool with_namespace)
 {
     ttype = resolve_typedef(ttype);
@@ -3225,7 +3455,7 @@ string t_netstd_generator::type_name(t_type* ttype, bool with_namespace)
     if (ttype->is_set())
     {
         t_set* tset = static_cast<t_set*>(ttype);
-        return "THashSet<" + type_name(tset->get_elem_type()) + ">";
+		return "HashSet<" + type_name(tset->get_elem_type()) + ">";
     }
 
     if (ttype->is_list())
@@ -3281,10 +3511,14 @@ string t_netstd_generator::base_type_name(t_base_type* tbase)
     }
 }
 
-string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool& needs_typecast)
+string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool is_not_null, bool& needs_typecast, string& suffix)
 {
     ttype = resolve_typedef(ttype);
 
+    // if is_not_null is set, then the surrounding code already explicitly tests against != null
+    string null_check("");
+
+    suffix = "";
     needs_typecast = false;
     if (ttype->is_base_type())
     {
@@ -3293,9 +3527,16 @@ string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool& needs_
         {
         case t_base_type::TYPE_STRING:
             if (ttype->is_binary()) {
-                return ".ToArray()";
+                suffix = nullable_suffix();
+                if( use_net6_features) {
+                    null_check = is_not_null ? "!" : " ?? Array.Empty<byte>()";
+                }
+                return ".ToArray()" + null_check;
             } else {
-                return "";  // simple assignment will do, strings are immutable in C#
+                if( use_net6_features) {
+                    null_check = is_not_null ? "!" : " ?? string.Empty";
+                }
+                return null_check;  // simple assignment will do, strings are immutable in C#
             }
             break;
         default:
@@ -3306,63 +3547,116 @@ string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool& needs_
     {
         return "";  // simple assignment will do
     }
+    else if (is_union_enabled() && ttype->is_struct() && static_cast<t_struct*>(ttype)->is_union())
+    {
+        needs_typecast = (! ttype->is_container());        
+        suffix = nullable_suffix();
+        if( use_net6_features) {
+            null_check = is_not_null ? "!" : " ?? new "+ttype->get_name() +".___undefined()";
+        }        
+        return "." + DEEP_COPY_METHOD_NAME + "()" + null_check;
+    }
     else
     {
-        needs_typecast = (! ttype->is_container());
-        return "." + DEEP_COPY_METHOD_NAME + "()";
+        needs_typecast = (! ttype->is_container());        
+        suffix = nullable_suffix();
+        if( use_net6_features) {
+            null_check = is_not_null ? "!" : " ?? new()";
+        }        
+        return "." + DEEP_COPY_METHOD_NAME + "()" + null_check;
     }
+    
+    throw "UNEXPECTED TYPE IN get_deep_copy_method_call: " + ttype->get_name();
 }
 
-string t_netstd_generator::declare_field(t_field* tfield, bool init, string prefix)
+string t_netstd_generator::declare_field(t_field* tfield, bool init, bool allow_nullable, string prefix)
 {
-    string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name();
+    string result = type_name(tfield->get_type())
+                  + (allow_nullable ? nullable_field_suffix(tfield) : "")
+                  + " "
+                  + prefix + tfield->get_name()
+                  ;
     if (init)
     {
-        t_type* ttype = tfield->get_type();
-        ttype = resolve_typedef(ttype);
-        if (ttype->is_base_type() && field_has_default(tfield))
-        {
-            std::ofstream dummy;
-            result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value());
-        }
-        else if (ttype->is_base_type())
+        result += initialize_field(tfield);
+    }
+
+    return result + ";";
+}
+
+string t_netstd_generator::initialize_field(t_field* tfield)
+{
+    t_type* ttype = tfield->get_type();
+    ttype = resolve_typedef(ttype);    
+    
+    if (ttype->is_base_type() && field_has_default(tfield))
+    {
+        std::ofstream dummy;
+        return " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value());
+    }
+    else if (force_member_nullable(tfield))
+    {
+        return "";  // see force_member_nullable() why this is necessary
+    }
+    else if (ttype->is_base_type())
+    {
+        t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base();
+        switch (tbase)
         {
-            t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base();
-            switch (tbase)
-            {
-            case t_base_type::TYPE_VOID:
-                throw "NO T_VOID CONSTRUCT";
-            case t_base_type::TYPE_STRING:
-                result += " = null";
-                break;
-            case t_base_type::TYPE_BOOL:
-                result += " = false";
-                break;
-            case t_base_type::TYPE_I8:
-            case t_base_type::TYPE_I16:
-            case t_base_type::TYPE_I32:
-            case t_base_type::TYPE_I64:
-                result += " = 0";
-                break;
-            case t_base_type::TYPE_DOUBLE:
-                result += " = (double)0";
-                break;
+        case t_base_type::TYPE_VOID:
+            throw "NO T_VOID CONSTRUCT";
+        case t_base_type::TYPE_STRING:
+            if(use_net6_features && field_is_required(tfield)) {
+                if (ttype->is_binary()) {
+                    return " = Array.Empty<byte>()";
+                } else {
+                    return " = string.Empty";
+                }
+            } else {
+                return " = null";
             }
+            break;
+        case t_base_type::TYPE_BOOL:
+            return " = false";
+            break;
+        case t_base_type::TYPE_I8:
+        case t_base_type::TYPE_I16:
+        case t_base_type::TYPE_I32:
+        case t_base_type::TYPE_I64:
+            return " = 0";
+            break;
+        case t_base_type::TYPE_DOUBLE:
+            return " = 0.0";
+            break;
         }
-        else if (ttype->is_enum())
-        {
-            result += " = (" + type_name(ttype) + ")0";
-        }
-        else if (ttype->is_container())
-        {
-            result += " = new " + type_name(ttype) + "()";
+    }
+    else if (ttype->is_enum())
+    {
+        return " = default";
+    }
+    else if (ttype->is_container())
+    {
+        if(use_net6_features) {
+            return " = new()";
+        } else {
+            return " = new " + type_name(ttype) + "()";
         }
-        else
-        {
-            result += " = new " + type_name(ttype) + "()";
+    }
+    else if (ttype->is_struct())
+    {
+        t_struct* tstruct = static_cast<t_struct*>(ttype);        
+        if(use_net6_features) {
+            if(tstruct->is_union()) {
+                return " = new " + type_name(ttype) + ".___undefined()";
+            } else {
+                return " = new()";
+            }
+        } else {
+            return " = new " + type_name(ttype) + "()";
         }
     }
-    return result + ";";
+    
+    throw "UNEXPECTED TYPE IN initialize_field: " + ttype->get_name();
 }
 
 string t_netstd_generator::function_signature(t_function* tfunction, string prefix)
@@ -3412,7 +3706,7 @@ string t_netstd_generator::argument_list(t_struct* tstruct, bool with_types)
         }
 
         if( with_types) {
-            result += type_name((*f_iter)->get_type()) + " ";
+            result += type_name((*f_iter)->get_type()) + nullable_field_suffix(*f_iter) + " ";
         }
 
         result += normalize_name((*f_iter)->get_name(),true);
@@ -3573,6 +3867,7 @@ THRIFT_REGISTER_GENERATOR(
     "    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"
+    "    net6:            Enable features that require net6 and C# 8 or higher.\n"
+    "    no_deepcopy:     Suppress generation of " + DEEP_COPY_METHOD_NAME + "() method.\n"
     "    async_postfix:   Append \"Async\" to all service methods (maintains compatibility with existing code).\n"
 )
diff --git a/compiler/cpp/src/thrift/generate/t_netstd_generator.h b/compiler/cpp/src/thrift/generate/t_netstd_generator.h
index 8896075..982cd26 100644
--- a/compiler/cpp/src/thrift/generate/t_netstd_generator.h
+++ b/compiler/cpp/src/thrift/generate/t_netstd_generator.h
@@ -116,7 +116,7 @@ public:
   void generate_deserialize_set_element(ostream& out, t_set* tset, string prefix = "");
   void generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix = "");
   void generate_deserialize_list_element(ostream& out, t_list* list, string prefix = "");
-  void generate_serialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_propertyless = false);
+  void generate_serialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_propertyless = false, bool allow_nullable = true);
   void generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix = "");
   void generate_serialize_container(ostream& out, t_type* ttype, string prefix = "");
   void generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map);
@@ -139,7 +139,7 @@ public:
 
   string type_name(t_type* ttype, bool with_namespace = true);
   string base_type_name(t_base_type* tbase);
-  string declare_field(t_field* tfield, bool init = false, string prefix = "");
+  string declare_field(t_field* tfield, bool init = false, bool allow_nullable = true, string prefix = "");
   string function_signature_async(t_function* tfunction, string prefix = "", int mode = MODE_FULL_DECL);
   string function_signature(t_function* tfunction, string prefix = "");
   string argument_list(t_struct* tstruct, bool with_types = true);
@@ -152,15 +152,19 @@ public:
 
 protected:
   std::string autogen_comment() override {
-    return std::string("/**\n") 
-                      + " * <auto-generated>\n"
-                      + " * " + autogen_summary() + "\n" 
-                      + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n"
-                      + " * </auto-generated>\n"
-                      " */\n"
-                      ;
+    string comment = "/**\n";
+    if( ! use_net6_features) {
+        comment += " * <auto-generated>\n";
+    }
+    comment += " * " + autogen_summary() + "\n";
+    comment += " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n";
+    if( ! use_net6_features) {
+        comment += " * </auto-generated>\n";
+    }
+    comment += " */\n";
+    return comment;
   }
-
+  
 
 private:
   string namespace_name_;
@@ -172,6 +176,7 @@ private:
   bool wcf_;
   bool use_pascal_case_properties;
   bool suppress_deepcopy;
+  bool use_net6_features;
   bool add_async_postfix;
 
   string wcf_namespace_;
@@ -191,11 +196,20 @@ private:
   void cleanup_member_name_mapping(void* scope);
   string get_mapped_member_name(string oldname);
   string get_isset_name(const string& str);
-  string get_deep_copy_method_call(t_type* ttype, bool& needs_typecast);
+  string get_deep_copy_method_call(t_type* ttype, bool is_not_null, bool& needs_typecast, string& suffix);
   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();
   void generate_null_check_begin(ostream& out, t_field* tfield);
   void generate_null_check_end(ostream& out, t_field* tfield);
+  string initialize_field(t_field* tfield);
+
+  void pragmas_and_directives(ostream& out);
+  bool is_nullable_type(t_type* ttype);
+  bool force_member_nullable(t_field* tfield);  // see there
+  string nullable_suffix();                     // unconditionally
+  string nullable_field_suffix(t_field* tfield);  // depends on field type
+  string nullable_field_suffix(t_type* ttype);  // depends on field type
+  string nullable_value_access(t_type* ttype);  // depends on field type
 };
diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs
index 660b2b7..f423376 100644
--- a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs
+++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs
@@ -26,30 +26,30 @@ namespace Thrift.PublicInterfaces.Compile.Tests.Impl.Thrift5253
 {
     class MyServiceImpl : MyService.IAsync
     {
-        public Task<AsyncProcessor> AsyncProcessor_(AsyncProcessor input, CancellationToken cancellationToken = default)
+        public Task<AsyncProcessor> AsyncProcessor_(AsyncProcessor? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new AsyncProcessor() { Foo = input.Foo });
+            return Task.FromResult(new AsyncProcessor() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<BrokenResult> Broken(BrokenArgs input, CancellationToken cancellationToken = default)
+        public Task<BrokenResult> Broken(BrokenArgs? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new BrokenResult() { Foo = input.Foo });
+            return Task.FromResult(new BrokenResult() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<Client> Client_(Client input, CancellationToken cancellationToken = default)
+        public Task<Client> Client_(Client? input, CancellationToken cancellationToken = default)
         {
             _ = cancellationToken;
-            return Task.FromResult(new Client() { Foo = input.Foo });
+            return Task.FromResult(new Client() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<IAsync> IAsync_(IAsync input, CancellationToken cancellationToken = default)
+        public Task<IAsync> IAsync_(IAsync? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new IAsync() { Foo = input.Foo });
+            return Task.FromResult(new IAsync() { Foo = input?.Foo ?? 0 });
         }
 
-        public Task<InternalStructs> InternalStructs_(InternalStructs input, CancellationToken cancellationToken = default)
+        public Task<InternalStructs> InternalStructs_(InternalStructs? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new InternalStructs() { Foo = input.Foo });
+            return Task.FromResult(new InternalStructs() { Foo = input?.Foo ?? 0 });
         }
 
         public Task TestAsync(CancellationToken cancellationToken = default)
@@ -62,9 +62,9 @@ namespace Thrift.PublicInterfaces.Compile.Tests.Impl.Thrift5253
             return Task.CompletedTask;
         }
 
-        public Task<WorksRslt> Works(WorksArrrgs input, CancellationToken cancellationToken = default)
+        public Task<WorksRslt> Works(WorksArrrgs? input, CancellationToken cancellationToken = default)
         {
-            return Task.FromResult(new WorksRslt() { Foo = input.Foo });
+            return Task.FromResult(new WorksRslt() { Foo = input?.Foo ?? 0 });
         }
     }
 }
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 176e734..42a139c 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
@@ -68,14 +68,14 @@
     <Error Condition="$('$(ThriftBinaryVersion)'::StartsWith('$(ThriftVersionOutput)')) == true" Text="Thrift version returned: '$(ThriftBinaryVersion)' is not equal to the projects version '$(ThriftVersionOutput)'." />
     <Message Importance="high" Text="Generating tests with thrift binary: '$(PathToThrift)'" />
     <!-- Generate the thrift test files -->
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./CassandraTest.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./optional_required_default.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./name_conflicts.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../test/ThriftTest.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../contrib/fb303/if/fb303.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5253.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5320.thrift" />
-    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5382.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./CassandraTest.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./optional_required_default.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./name_conflicts.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./../../../../test/ThriftTest.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./../../../../contrib/fb303/if/fb303.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5253.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5320.thrift" />
+    <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5382.thrift" />
   </Target>
 
 </Project>
diff --git a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
index 778d24c..49108d1 100644
--- a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
+++ b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs
@@ -83,8 +83,8 @@ namespace Thrift.Tests.Collections
         [TestMethod]
         public void TCollection_Set_Equals_Primitive_Test()
         {
-            var collection1 = new THashSet<int> {1,2,3};
-            var collection2 = new THashSet<int> {1,2,3};
+            var collection1 = new HashSet<int> {1,2,3};
+            var collection2 = new HashSet<int> {1,2,3};
             Assert.IsTrue(TCollections.Equals(collection1, collection2));
             Assert.IsTrue(collection1.SequenceEqual(collection2));
         }
@@ -92,8 +92,8 @@ namespace Thrift.Tests.Collections
         [TestMethod]
         public void TCollection_Set_Equals_Primitive_Different_Test()
         {
-            var collection1 = new THashSet<int> { 1, 2, 3 };
-            var collection2 = new THashSet<int> { 1, 2 };
+            var collection1 = new HashSet<int> { 1, 2, 3 };
+            var collection2 = new HashSet<int> { 1, 2 };
             Assert.IsFalse(TCollections.Equals(collection1, collection2));
             Assert.IsFalse(collection1.SequenceEqual(collection2));
 
@@ -105,8 +105,8 @@ namespace Thrift.Tests.Collections
         [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 } };
+            var collection1 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            var collection2 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
             Assert.IsTrue(TCollections.Equals(collection1, collection2));
             Assert.IsTrue(collection1.SequenceEqual(collection2));
         }
@@ -114,8 +114,8 @@ namespace Thrift.Tests.Collections
         [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 } } };
+            var collection1 = new HashSet<HashSet<ExampleClass>> { new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } };
+            var collection2 = new HashSet<HashSet<ExampleClass>> { new HashSet<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()
         }
@@ -123,7 +123,7 @@ namespace Thrift.Tests.Collections
         [TestMethod]
         public void TCollection_Set_Equals_OneAndTheSameObject_Test()
         {
-            var collection1 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } };
+            var collection1 = new HashSet<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));
diff --git a/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs b/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs
index 8de573e..73921ea 100644
--- a/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs
+++ b/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.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
@@ -22,6 +22,8 @@ using System.Text;
 using Microsoft.VisualStudio.TestTools.UnitTesting;
 using Thrift.Collections;
 
+#pragma warning disable IDE0063  // simplify using 
+
 namespace Thrift.Tests.Collections
 {
     // ReSharper disable once InconsistentNaming
@@ -33,7 +35,7 @@ namespace Thrift.Tests.Collections
         {
             const int value = 1;
 
-            var hashSet = new THashSet<int> {value};
+            var hashSet = new HashSet<int> {value};
             
             Assert.IsTrue(hashSet.Contains(value));
 
diff --git a/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
index 84fcab8..afffed5 100644
--- a/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
+++ b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs
@@ -24,8 +24,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
 using OptReqDefTest;
 using Thrift.Collections;
 
-#nullable disable  // this is just test code, we leave it at that
-
 namespace Thrift.Tests.DataModel
 {
     // ReSharper disable once InconsistentNaming
@@ -51,7 +49,7 @@ namespace Thrift.Tests.DataModel
             VerifyIdenticalContent(first, InitializeInstance(new RaceDetails()));
         }
 
-        private RaceDetails MakeNestedRaceDetails(int nesting)
+        private RaceDetails? MakeNestedRaceDetails(int nesting)
         {
             if (++nesting > 1)
                 return null;
@@ -61,7 +59,7 @@ namespace Thrift.Tests.DataModel
             return instance;
         }
 
-        private jack MakeNestedUnion(int nesting)
+        private jack? MakeNestedUnion(int nesting)
         {
             if (++nesting > 1)
                 return null;
@@ -88,11 +86,11 @@ namespace Thrift.Tests.DataModel
             instance.Req_one = default;
             instance.Req_two = default;
             instance.Req_three = default;
-            instance.Req_four = default;
-            instance.Req_five = default;
+            Assert.IsNotNull(instance.Req_four);
+            Assert.IsNotNull(instance.Req_five);
             instance.Req_six = default; 
             instance.Req_seven = default;;
-            instance.Req_eight = default; 
+            instance.Req_eight = default;
 
             // leave non-required fields unset again
             Assert.IsFalse(instance.__isset.def_one);
@@ -119,15 +117,15 @@ namespace Thrift.Tests.DataModel
             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.AreEqual("five", Encoding.UTF8.GetString(instance.Req_five_with_value!));
 
-            Assert.IsTrue(instance.Req_six_with_value.Count == 1);
+            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!.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!.Count == 1);
             Assert.IsTrue(instance.Req_eight_with_value[8] == 8);
 
             Assert.IsTrue(instance.__isset.def_one_with_value);
@@ -144,12 +142,16 @@ namespace Thrift.Tests.DataModel
             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_set = new HashSet<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.Far_set_list = new HashSet<List<Distance>>() { new List<Distance>() { Distance.foo } };
+                instance.Far_list_map_set = new List<Dictionary<sbyte, HashSet<Distance>>>() { new Dictionary<sbyte, HashSet<Distance>>() { [1] = new HashSet<Distance>() { Distance.baz } } };
+
+                instance.Far_map_dist_to_rds = new Dictionary<Distance, List<RaceDetails>>() { [Distance.bar] = new List<RaceDetails>()  };
+                var details = MakeNestedRaceDetails(nesting);
+                if (details != null)
+                    instance.Far_map_dist_to_rds[Distance.bar].Add(details);
 
                 instance.Req_nested = MakeNestedRaceDetails(nesting);
                 Assert.IsFalse(instance.__isset.opt_nested);
@@ -245,19 +247,19 @@ namespace Thrift.Tests.DataModel
             instance.Triplesix = ModifyValue(instance.Triplesix);
         }
 
-        private jack ModifyValue(jack value, int level)
+        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);
+            Debug.Assert(value?.As_nested_struct != null);
             ModifyInstance(value.As_nested_struct, level);
             return value;
         }
 
-        private RaceDetails ModifyValue(RaceDetails value, int level)
+        private RaceDetails? ModifyValue(RaceDetails? value, int level)
         {
             if (++level > 4)
                 return value;
@@ -268,7 +270,7 @@ namespace Thrift.Tests.DataModel
             return value;
         }
 
-        private Dictionary<Distance, List<RaceDetails>> ModifyValue(Dictionary<Distance, List<RaceDetails>> value, int level)
+        private Dictionary<Distance, List<RaceDetails>> ModifyValue(Dictionary<Distance, List<RaceDetails>>? value, int level)
         {
             if (value == null)
                 value = new Dictionary<Distance, List<RaceDetails>>();
@@ -283,29 +285,30 @@ namespace Thrift.Tests.DataModel
             if (value.TryGetValue(Distance.bar, out var list) && (list.Count > 0))
             {
                 ModifyInstance(list[0], level);
-                list.Add(null);
+                //list.Add(null);  -- Thrift does not allow null values in containers
             }
 
-            value[Distance.baz] = null;
+            // Thrift does not allow null values in containers
+            //value[Distance.baz] = null;
 
             return value;
         }
 
-        private static List<Dictionary<sbyte, THashSet<Distance>>> ModifyValue(List<Dictionary<sbyte, THashSet<Distance>>> value)
+        private static List<Dictionary<sbyte, HashSet<Distance>>> ModifyValue(List<Dictionary<sbyte, HashSet<Distance>>>? value)
         {
             if (value == null)
-                value = new List<Dictionary<sbyte, THashSet<Distance>>>();
+                value = new List<Dictionary<sbyte, HashSet<Distance>>>();
 
             if (value.Count == 0)
-                value.Add(new Dictionary<sbyte, THashSet<Distance>>());
-            else
-                value.Add(null);
+                value.Add(new Dictionary<sbyte, HashSet<Distance>>());
+            //else
+            //value.Add(null); --Thrift does not allow null values in containers
 
             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);
+                value[0].Add(key, new HashSet<Distance>());
+            //else
+            //value[0].Add(key, null); --Thrift does not allow null values in containers
 
             foreach (var entry in value)
             {
@@ -327,15 +330,15 @@ namespace Thrift.Tests.DataModel
             return value;
         }
 
-        private static THashSet<List<Distance>> ModifyValue(THashSet<List<Distance>> value)
+        private static HashSet<List<Distance>> ModifyValue(HashSet<List<Distance>>? value)
         {
             if (value == null)
-                value = new THashSet<List<Distance>>();
+                value = new HashSet<List<Distance>>();
 
             if (value.Count == 0)
                 value.Add(new List<Distance>());
-            else
-                value.Add(null);
+            //else
+            //value.Add(null); -- Thrift does not allow null values in containers
 
             foreach (var entry in value)
                 if( entry != null)
@@ -344,7 +347,7 @@ namespace Thrift.Tests.DataModel
             return value;
         }
 
-        private static Dictionary<Distance, Distance> ModifyValue(Dictionary<Distance, Distance> value)
+        private static Dictionary<Distance, Distance> ModifyValue(Dictionary<Distance, Distance>? value)
         {
             if (value == null)
                 value = new Dictionary<Distance, Distance>();
@@ -354,10 +357,10 @@ namespace Thrift.Tests.DataModel
             return value;
         }
 
-        private static THashSet<Distance> ModifyValue(THashSet<Distance> value)
+        private static HashSet<Distance> ModifyValue(HashSet<Distance>? value)
         {
             if (value == null)
-                value = new THashSet<Distance>();
+                value = new HashSet<Distance>();
 
             if (value.Contains(Distance.foo))
                 value.Remove(Distance.foo);
@@ -377,7 +380,7 @@ namespace Thrift.Tests.DataModel
             return value;
         }
 
-        private static List<Distance> ModifyValue(List<Distance> value)
+        private static List<Distance> ModifyValue(List<Distance>? value)
         {
             if (value == null)
                 value = new List<Distance>();
@@ -392,7 +395,7 @@ namespace Thrift.Tests.DataModel
             return !value;
         }
 
-        private static Dictionary<sbyte, short> ModifyValue(Dictionary<sbyte, short> value)
+        private static Dictionary<sbyte, short> ModifyValue(Dictionary<sbyte, short>? value)
         {
             if (value == null)
                 value = new Dictionary<sbyte, short>();
@@ -400,15 +403,15 @@ namespace Thrift.Tests.DataModel
             return value;
         }
 
-        private static THashSet<long> ModifyValue(THashSet<long> value)
+        private static HashSet<long> ModifyValue(HashSet<long>? value)
         {
             if (value == null)
-                value = new THashSet<long>();
+                value = new HashSet<long>();
             value.Add(value.Count+100);
             return value;
         }
 
-        private static List<int> ModifyValue(List<int> value)
+        private static List<int> ModifyValue(List<int>? value)
         {
             if (value == null)
                 value = new List<int>();
@@ -416,16 +419,18 @@ namespace Thrift.Tests.DataModel
             return value;
         }
 
-        private static byte[] ModifyValue(byte[] value)
+        private static 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;
+            else
+                value = new byte[1] { 0 };
             return value;
         }
 
-        private static string ModifyValue(string value)
+        private static string ModifyValue(string? value)
         {
             return value + "1";
         }
diff --git a/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs b/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs
index 693b68e..ebc1717 100644
--- a/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs
+++ b/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs
@@ -25,13 +25,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
 using OptReqDefTest;
 using Thrift.Collections;
 
+#pragma warning disable IDE0017  // init can be simplified - we don't want that here
+
 namespace Thrift.Tests.DataModel
 {
     // ReSharper disable once InconsistentNaming
     [TestClass]
     public class Thrift_5238
     {
-        private void CheckInstance(RaceDetails instance)
+        private static void CheckInstance(RaceDetails instance)
         {
             // object
             Assert.IsTrue(instance.__isset.def_nested);
@@ -42,14 +44,14 @@ namespace Thrift.Tests.DataModel
             // string
             Assert.IsTrue(instance.__isset.def_four);
             Assert.IsTrue(instance.__isset.opt_four);
-            Assert.IsNull(instance.Req_four);
+            Assert.IsTrue(string.IsNullOrEmpty(instance.Req_four));
             Assert.IsNull(instance.Def_four);
             Assert.IsNull(instance.Opt_four);
 
             // byte[]
             Assert.IsTrue(instance.__isset.def_five);
             Assert.IsTrue(instance.__isset.opt_five);
-            Assert.IsNull(instance.Req_five);
+            Assert.IsTrue((instance.Req_five == null) || (instance.Req_five.Length == 0));
             Assert.IsNull(instance.Def_five);
             Assert.IsNull(instance.Opt_five);
 
@@ -66,6 +68,9 @@ namespace Thrift.Tests.DataModel
         {
             var instance = new OptReqDefTest.RaceDetails();
 
+            // the following code INTENTIONALLY assigns null to non.nullable reftypes
+            #pragma warning disable CS8625
+
             // object
             instance.Def_nested = null;
             instance.Opt_nested = null;
@@ -85,6 +90,9 @@ namespace Thrift.Tests.DataModel
             instance.Opt_six = null;
             instance.Def_six = null;
 
+            // back to normal
+            #pragma warning restore CS8625
+
             // test the setup
             CheckInstance(instance);
 
diff --git a/lib/netstd/Thrift/Collections/THashSet.cs b/lib/netstd/Thrift/Collections/THashSet.cs
index 1c060e5..fc2a507 100644
--- a/lib/netstd/Thrift/Collections/THashSet.cs
+++ b/lib/netstd/Thrift/Collections/THashSet.cs
@@ -15,69 +15,37 @@
 // specific language governing permissions and limitations
 // under the License.
 
+using System;
 using System.Collections;
 using System.Collections.Generic;
 
 namespace Thrift.Collections
 {
     // ReSharper disable once InconsistentNaming
-    public class THashSet<T> : ICollection<T>
+	[Obsolete("deprecated, use HashSet<T> instead")]
+    public class THashSet<T> : System.Collections.Generic.HashSet<T>
     {
-        private readonly HashSet<T> Items;
-
         public THashSet()
+            : base()
         {
-            Items = new HashSet<T>();
         }
 
         public THashSet(int capacity)
+#if NET5_0_OR_GREATER
+            : base(capacity)
+#elif NETFRAMEWORK || NETSTANDARD
+            : base(/*capacity not supported*/)
+#else
+#error Unknown platform
+#endif
         {
-            #if NET5_0_OR_GREATER
-            Items = new HashSet<T>(capacity);
-            #elif NETFRAMEWORK || NETSTANDARD
-            Items = new HashSet<T>(/*capacity not supported*/);
-            #else
-            #error Unknown platform
-            #endif
-        }
-
-        public int Count => Items.Count;
-
-        public bool IsReadOnly => false;
-
-        public void Add(T item)
-        {
-            Items.Add(item);
         }
 
-        public void Clear()
+        public THashSet(IEnumerable<T> collection)
+            : base(collection)
         {
-            Items.Clear();
         }
 
-        public bool Contains(T item)
-        {
-            return Items.Contains(item);
-        }
-
-        public void CopyTo(T[] array, int arrayIndex)
-        {
-            Items.CopyTo(array, arrayIndex);
-        }
-
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return Items.GetEnumerator();
-        }
-
-        public IEnumerator<T> GetEnumerator()
-        {
-            return ((IEnumerable<T>) Items).GetEnumerator();
-        }
-
-        public bool Remove(T item)
-        {
-            return Items.Remove(item);
-        }
     }
 }
+
diff --git a/test/netstd/Client/Client.csproj b/test/netstd/Client/Client.csproj
index e312990..9d4ab48 100644
--- a/test/netstd/Client/Client.csproj
+++ b/test/netstd/Client/Client.csproj
@@ -49,8 +49,8 @@
     <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true">
       <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" />
     </Exec>
-    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
   </Target>
 </Project>
diff --git a/test/netstd/Client/Performance/TestDataFactory.cs b/test/netstd/Client/Performance/TestDataFactory.cs
index 833947c..8dec3f3 100644
--- a/test/netstd/Client/Performance/TestDataFactory.cs
+++ b/test/netstd/Client/Performance/TestDataFactory.cs
@@ -40,9 +40,9 @@ namespace Client.Tests
             };
         }
 
-        private static THashSet<Insanity> CreateSetField(int count)
+        private static HashSet<Insanity> CreateSetField(int count)
         {
-            var retval = new THashSet<Insanity>();
+            var retval = new HashSet<Insanity>();
             for (var i = 0; i < count; ++i)
                 retval.Add(CreateInsanity(count));
             return retval;
@@ -90,41 +90,41 @@ namespace Client.Tests
             return retval;
         }
 
-        private static List<Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>> CreateListField(int count)
+        private static List<Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>> CreateListField(int count)
         {
-            var retval = new List<Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>>();
+            var retval = new List<Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add(CreateListFieldData(count));
             return retval;
         }
 
-        private static Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>> CreateListFieldData(int count)
+        private static Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>> CreateListFieldData(int count)
         {
-            var retval = new Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>();
+            var retval = new Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add( CreateIntHashSet(count), CreateListFieldDataDict(count));
             return retval;
         }
 
-        private static THashSet<int> CreateIntHashSet(int count)
+        private static HashSet<int> CreateIntHashSet(int count)
         {
-            var retval = new THashSet<int>();
+            var retval = new HashSet<int>();
             for (var i = 0; i < count; ++i)
                 retval.Add(i);
             return retval;
         }
 
-        private static Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>> CreateListFieldDataDict(int count)
+        private static Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>> CreateListFieldDataDict(int count)
         {
-            var retval = new Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>();
+            var retval = new Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add(i, CreateListFieldDataDictValue(count));
             return retval;
         }
 
-        private static THashSet<List<Dictionary<Insanity, string>>> CreateListFieldDataDictValue(int count)
+        private static HashSet<List<Dictionary<Insanity, string>>> CreateListFieldDataDictValue(int count)
         {
-            var retval = new THashSet<List<Dictionary<Insanity, string>>>();
+            var retval = new HashSet<List<Dictionary<Insanity, string>>>();
             for (var i = 0; i < count; ++i)
                 retval.Add( CreateListFieldDataDictValueList(count));
             return retval;
diff --git a/test/netstd/Client/TestClient.cs b/test/netstd/Client/TestClient.cs
index 0a7fa00..0c80b9c 100644
--- a/test/netstd/Client/TestClient.cs
+++ b/test/netstd/Client/TestClient.cs
@@ -644,9 +644,14 @@ namespace ThriftTest
                 Struct_thing = o,
                 I32_thing = 5
             };
-            var i2 = await client.testNest(o2, MakeTimeoutToken());
+            Xtruct2 i2 = await client.testNest(o2, MakeTimeoutToken());
             i = i2.Struct_thing;
-            Console.WriteLine(" = {" + i2.Byte_thing + ", {\"" + i.String_thing + "\", " + i.Byte_thing + ", " + i.I32_thing + ", " + i.I64_thing + "}, " + i2.I32_thing + "}");
+            Console.WriteLine(" = {" + i2.Byte_thing + ", {\""
+                            + (i?.String_thing ?? "<null>") + "\", "
+                            + (i?.Byte_thing ?? 0) + ", "
+                            + (i?.I32_thing ?? 0) + ", "
+                            + (i?.I64_thing ?? 0) + "}, "
+                            + i2.I32_thing + "}");
 
             var mapout = new Dictionary<int, int>();
             for (var j = 0; j < 5; j++)
@@ -681,7 +686,7 @@ namespace ThriftTest
 
             //set
             // TODO: Validate received message
-            var setout = new THashSet<int>();
+            var setout = new HashSet<int>();
             for (var j = -2; j < 3; j++)
             {
                 setout.Add(j);
@@ -937,7 +942,7 @@ namespace ThriftTest
             }
             catch (Xception2 ex)
             {
-                if (ex.ErrorCode != 2002 || ex.Struct_thing.String_thing != "This is an Xception2")
+                if (ex.ErrorCode != 2002 || ex.Struct_thing?.String_thing != "This is an Xception2")
                 {
                     Console.WriteLine("*** FAILED ***");
                     returnCode |= ErrorExceptions;
diff --git a/test/netstd/Server/Server.csproj b/test/netstd/Server/Server.csproj
index 546d0e2..439e5c1 100644
--- a/test/netstd/Server/Server.csproj
+++ b/test/netstd/Server/Server.csproj
@@ -51,8 +51,8 @@
     <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true">
       <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" />
     </Exec>
-    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
-    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(PathToThrift)')" Command="&quot;$(PathToThrift)&quot; -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
+    <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" />
   </Target>
 </Project>
diff --git a/test/netstd/Server/TestServer.cs b/test/netstd/Server/TestServer.cs
index 515a299..86072b0 100644
--- a/test/netstd/Server/TestServer.cs
+++ b/test/netstd/Server/TestServer.cs
@@ -229,10 +229,10 @@ namespace ThriftTest
                 return Task.CompletedTask;
             }
 
-            public Task<string> testString(string thing, CancellationToken cancellationToken)
+            public Task<string> testString(string? thing, CancellationToken cancellationToken)
             {
-                logger.Invoke("testString({0})", thing);
-                return Task.FromResult(thing);
+                logger.Invoke("testString({0})", thing ?? "<null>");
+                return Task.FromResult(thing ?? string.Empty);
             }
 
             public Task<bool> testBool(bool thing, CancellationToken cancellationToken)
@@ -265,117 +265,129 @@ namespace ThriftTest
                 return Task.FromResult(thing);
             }
 
-            public Task<byte[]> testBinary(byte[] thing, CancellationToken cancellationToken)
+            public Task<byte[]> testBinary(byte[]? thing, CancellationToken cancellationToken)
             {
-                logger.Invoke("testBinary({0} bytes)", thing.Length);
-                return Task.FromResult(thing);
+                logger.Invoke("testBinary({0} bytes)", thing?.Length ?? 0);
+                return Task.FromResult(thing ?? Array.Empty<byte>());
             }
 
-            public Task<Xtruct> testStruct(Xtruct thing, CancellationToken cancellationToken)
+            public Task<Xtruct> testStruct(Xtruct? thing, CancellationToken cancellationToken)
             {
-                logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing);
-                return Task.FromResult(thing);
+                logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing?.String_thing ?? "<null>", thing?.Byte_thing ?? 0, thing?.I32_thing ?? 0, thing?.I64_thing ?? 0);
+                return Task.FromResult(thing ?? new Xtruct());   // null returns are not allowed in Thrift
             }
 
-            public Task<Xtruct2> testNest(Xtruct2 nest, CancellationToken cancellationToken)
+            public Task<Xtruct2> testNest(Xtruct2? nest, CancellationToken cancellationToken)
             {
-                var thing = nest.Struct_thing;
+                var thing = nest?.Struct_thing;
                 logger.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})",
-                    nest.Byte_thing,
-                    thing.String_thing,
-                    thing.Byte_thing,
-                    thing.I32_thing,
-                    thing.I64_thing,
-                    nest.I32_thing);
-                return Task.FromResult(nest);
+                    nest?.Byte_thing ?? 0,
+                    thing?.String_thing ?? "<null>",
+                    thing?.Byte_thing ?? 0,
+                    thing?.I32_thing ?? 0,
+                    thing?.I64_thing ?? 0,
+                    nest?.I32_thing ?? 0);
+                return Task.FromResult(nest ?? new Xtruct2());   // null returns are not allowed in Thrift
             }
 
-            public Task<Dictionary<int, int>> testMap(Dictionary<int, int> thing, CancellationToken cancellationToken)
+            public Task<Dictionary<int, int>> testMap(Dictionary<int, int>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testMap({{");
-                var first = true;
-                foreach (var key in thing.Keys)
+                if (thing != null)
                 {
-                    if (first)
-                    {
-                        first = false;
-                    }
-                    else
+                    var first = true;
+                    foreach (var key in thing.Keys)
                     {
-                        sb.Append(", ");
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0} => {1}", key, thing[key]);
                     }
-                    sb.AppendFormat("{0} => {1}", key, thing[key]);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new Dictionary<int, int>());   // null returns are not allowed in Thrift
             }
 
-            public Task<Dictionary<string, string>> testStringMap(Dictionary<string, string> thing, CancellationToken cancellationToken)
+            public Task<Dictionary<string, string>> testStringMap(Dictionary<string, string>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testStringMap({{");
-                var first = true;
-                foreach (var key in thing.Keys)
+                if (thing != null)
                 {
-                    if (first)
-                    {
-                        first = false;
-                    }
-                    else
+                    var first = true;
+                    foreach (var key in thing.Keys)
                     {
-                        sb.Append(", ");
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0} => {1}", key, thing[key]);
                     }
-                    sb.AppendFormat("{0} => {1}", key, thing[key]);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new Dictionary<string, string>());   // null returns are not allowed in Thrift
             }
 
-            public Task<THashSet<int>> testSet(THashSet<int> thing, CancellationToken cancellationToken)
+            public Task<HashSet<int>> testSet(HashSet<int>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testSet({{");
-                var first = true;
-                foreach (int elem in thing)
+                if (thing != null)
                 {
-                    if (first)
-                    {
-                        first = false;
-                    }
-                    else
+                    var first = true;
+                    foreach (int elem in thing)
                     {
-                        sb.Append(", ");
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0}", elem);
                     }
-                    sb.AppendFormat("{0}", elem);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new HashSet<int>());   // null returns are not allowed in Thrift
             }
 
-            public Task<List<int>> testList(List<int> thing, CancellationToken cancellationToken)
+            public Task<List<int>> testList(List<int>? thing, CancellationToken cancellationToken)
             {
                 sb.Clear();
                 sb.Append("testList({{");
-                var first = true;
-                foreach (var elem in thing)
+                if (thing != null)
                 {
-                    if (first)
+                    var first = true;
+                    foreach (var elem in thing)
                     {
-                        first = false;
+                        if (first)
+                        {
+                            first = false;
+                        }
+                        else
+                        {
+                            sb.Append(", ");
+                        }
+                        sb.AppendFormat("{0}", elem);
                     }
-                    else
-                    {
-                        sb.Append(", ");
-                    }
-                    sb.AppendFormat("{0}", elem);
                 }
                 sb.Append("}})");
                 logger.Invoke(sb.ToString());
-                return Task.FromResult(thing);
+                return Task.FromResult(thing ?? new List<int>());   // null returns are not allowed in Thrift
             }
 
             public Task<Numberz> testEnum(Numberz thing, CancellationToken cancellationToken)
@@ -409,7 +421,7 @@ namespace ThriftTest
                 return Task.FromResult(mapmap);
             }
 
-            public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanity(Insanity argument, CancellationToken cancellationToken)
+            public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanity(Insanity? argument, CancellationToken cancellationToken)
             {
                 logger.Invoke("testInsanity()");
 
@@ -428,8 +440,9 @@ namespace ThriftTest
                 var first_map = new Dictionary<Numberz, Insanity>();
                 var second_map = new Dictionary<Numberz, Insanity>(); ;
 
-                first_map[Numberz.TWO] = argument;
-                first_map[Numberz.THREE] = argument;
+                // null dict keys/values are not allowed in Thrift
+                first_map[Numberz.TWO] = argument ?? new Insanity();
+                first_map[Numberz.THREE] = argument ?? new Insanity();
 
                 second_map[Numberz.SIX] = new Insanity();
 
@@ -442,7 +455,7 @@ namespace ThriftTest
                 return Task.FromResult(insane);
             }
 
-            public Task<Xtruct> testMulti(sbyte arg0, int arg1, long arg2, Dictionary<short, string> arg3, Numberz arg4, long arg5,
+            public Task<Xtruct> testMulti(sbyte arg0, int arg1, long arg2, Dictionary<short, string>? arg3, Numberz arg4, long arg5,
                 CancellationToken cancellationToken)
             {
                 logger.Invoke("testMulti()");
@@ -455,9 +468,9 @@ namespace ThriftTest
                 return Task.FromResult(hello);
             }
 
-            public Task testException(string arg, CancellationToken cancellationToken)
+            public Task testException(string? arg, CancellationToken cancellationToken)
             {
-                logger.Invoke("testException({0})", arg);
+                logger.Invoke("testException({0})", arg ?? "<null>");
                 if (arg == "Xception")
                 {
                     var x = new Xception
@@ -474,9 +487,9 @@ namespace ThriftTest
                 return Task.CompletedTask;
             }
 
-            public Task<Xtruct> testMultiException(string arg0, string arg1, CancellationToken cancellationToken)
+            public Task<Xtruct> testMultiException(string? arg0, string? arg1, CancellationToken cancellationToken)
             {
-                logger.Invoke("testMultiException({0}, {1})", arg0, arg1);
+                logger.Invoke("testMultiException({0}, {1})", arg0 ?? "<null>", arg1 ?? "<null>");
                 if (arg0 == "Xception")
                 {
                     var x = new Xception
diff --git a/tutorial/netstd/Interfaces/Interfaces.csproj b/tutorial/netstd/Interfaces/Interfaces.csproj
index f81c88d..d04b243 100644
--- a/tutorial/netstd/Interfaces/Interfaces.csproj
+++ b/tutorial/netstd/Interfaces/Interfaces.csproj
@@ -41,8 +41,8 @@
     <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true">
       <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" />
     </Exec>
-    <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../tutorial.thrift" />
-    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../tutorial.thrift" />
-    <Exec Condition="Exists('./../../../compiler/cpp/thrift')" Command="./../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../tutorial.thrift" />
+    <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../tutorial.thrift" />
+    <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../tutorial.thrift" />
+    <Exec Condition="Exists('./../../../compiler/cpp/thrift')" Command="./../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../tutorial.thrift" />
   </Target>
 </Project>
diff --git a/tutorial/netstd/Server/Program.cs b/tutorial/netstd/Server/Program.cs
index d9c9dbf..29b21d0 100644
--- a/tutorial/netstd/Server/Program.cs
+++ b/tutorial/netstd/Server/Program.cs
@@ -131,7 +131,7 @@ Sample:
             if (selectedTransport == Transport.Http)
             {
                 if (multiplex)
-                    throw new Exception("This tutorial semple code does not yet allow multiplex over http (although Thrift itself of course does)");
+                    throw new Exception("This tutorial sample code does not yet allow multiplex over http (although Thrift itself of course does)");
                 new HttpServerSample().Run(cancellationToken);
             }
             else