ice icon indicating copy to clipboard operation
ice copied to clipboard

Remove parameterless constructor from mapped struct/class/exception in C#

Open bernardnormier opened this issue 1 year ago • 0 comments

In Ice 3.7, a generated C# class mapped from a Slice struct/class/exception has a parameterless constructor, described as a "default constructor" in the Ice Manual (default constructor is the C++ terminology):

https://doc.zeroc.com/ice/3.7/language-mappings/c-sharp-mapping/client-side-slice-to-c-sharp-mapping/c-sharp-mapping-for-structures#id-.CSharpMappingforStructuresv3.7-ClassMappingforStructuresinC#

https://doc.zeroc.com/ice/3.7/language-mappings/c-sharp-mapping/client-side-slice-to-c-sharp-mapping/c-sharp-mapping-for-classes#id-.CSharpMappingforClassesv3.7-ClassConstructorsinC#

https://doc.zeroc.com/ice/3.7/language-mappings/c-sharp-mapping/client-side-slice-to-c-sharp-mapping/c-sharp-mapping-for-exceptions#id-.CSharpMappingforExceptionsv3.7-C#DefaultConstructorsforUserExceptions

As documented above, each mapped field is initialized to a specific value by this parameterless constructor, e.g. proxies and collections are initialized to null, structs-mapped-as-classes are initialized to new "empty" instances, strings are initialized to "".

What's the issue caused by this parameterless constructor?

Modern C# code uses "nullable reference types": https://learn.microsoft.com/en-us/dotnet/csharp/nullable-migration-strategies

And only a handful of field types in Slice have nullable semantics, namely proxies, classes and optional (aka tagged) types. Strings, collections, structs don't have nullable semantics.

So the most logical mapping for a Slice struct/class/exception that has a string/collection/struct field is the non-nullable mapped C# type.

Example 1:

struct Identity { string name; string category; }
// Assuming nullable reference types are enabled
public sealed partial class Identity : IEquatable<Identity>
{
    public string name; // not string?
    public string category; // not string?
  
    public Identity(string name, string category) { ... } // not string?/string?
}

Example 2:

class Widget 
{ 
    Identity identity;
    StringSeq nicknames;
 }
// Assuming nullable reference types are enabled
public partial class Widget
{
    public Identity identity; // not Identity?
    public string[] nicknames; // not string[]?

    public Widget(Identity identity, string[] nicknames) { ... } // not Identity? / string[]?
}

It does not make sense for a public/documented parameterless constructor to initialize non-nullable collection fields/properties to null.

The proposed solution

Don't generate a parameterless constructor. For mapped exceptions, this removes a few additional "default" constructors.

In practice, for mapped classes and exceptions, we would still generate a hidden parameterless constructor for decoding, just like slicec-cs.

A consequence of this proposal is Slice field default values would be completely ignored in C#.

bernardnormier avatar May 05 '24 20:05 bernardnormier