Skip to main content
Skip table of contents

Structures

Struct Syntax

Slice supports structures containing one or more named fields of arbitrary type, including user-defined complex types. For example:

SLICE
module M
{
    struct TimeOfDay
    {
        short hour;         // 0 - 23
        short minute;       // 0 - 59
        short second;       // 0 - 59
    }
}

As in C++, this definition introduces a new type called TimeOfDay. Structure definitions form a scope, so the names of the structure fields need to be unique only within their enclosing structure.

Field definitions using a named type are the only construct that can appear inside a structure. It is impossible to, for example, define a structure inside a structure:

SLICE
struct TwoPoints 
{
    struct Point      // Illegal!
    {            
        short x;
        short y;
    }
    Point coord1;
    Point coord2;
}

This rule applies to Slice in general: type definitions cannot be nested (except for modules, which do support nesting). The reason for this rule is that nested type definitions can be difficult to implement for some target languages and, even if implementable, greatly complicate the scope resolution rules. For a specification language, such as Slice, nested type definitions are unnecessary – you can always write the above definitions as follows (which is stylistically cleaner as well):

Slice
SLICE
struct Point
{ 
    short x;
    short y;
}

struct TwoPoints      // Legal (and cleaner!)
{   
    Point coord1;
    Point coord2;
}

Language Mapping

Ice for C# supports two different mappings for Slice structures. By default, Slice structures map to C# record structs if they (recursively) contain only value types. If a Slice structure (recursively) contains a string, proxy, class, sequence, or dictionary field, it maps to a record class. The ”cs:class” metadata directive allows you to force the mapping to a record class for Slice structures that contain only value types.

In addition, for either mapping, you can control whether Slice fields are mapped to fields (the default) or to properties.

Mapping to Record Struct

Consider the following structure:

SLICE
struct Point
{
    ["cs:identifier:X"]
    double x;

    ["cs:identifier:Y"]
    double y;
}

This structure consists of only value types and so, by default, maps to a C# partial record struct:

C#
public partial record struct Point
{
    public double X;
    public double Y;

    partial void ice_initialize();

    public Point(double X, double Y)
    {
        this.X = X;
        this.Y = Y;
        ice_initialize();
    }

    public Point(Ice.InputStream istr)
    {
       this.X = istr.readDouble();
       this.Y = istr.readDouble();
       ice_initialize();
    }
}

For each field in the Slice definition, the C# record struct contains a corresponding public field. This name of this public field is by default the name of the Slice field; here, we remapped the fields using the cs:identifier metadata directive.

The generated record has a primary constructor that allows you to construct and initialize a structure in a single statement:

C#
var p = new Point(5.1, 7.8);

The generated constructor calls the ice_initialize partial method after initializing the fields. You can customize this initialization by providing your own implementation of ice_initialize.

Mapping to Record Class

Here is our Employee structure once more:

SLICE
struct Employee
{
    ["cs:identifier:Number"]
    long number;

    ["cs:identifier:FirstName"]
    string firstName;

    ["cs:identifier:LastName"]
    string lastName;
}

The structure contains two strings, which are reference types, so the Slice-to-C# compiler generates a sealed partial record class for this structure:

C#
public sealed partial record class Employee
{
    public long Number;
    public string FirstName = "";
    public string LastName = "";

    partial void ice_initialize();

    public Employee()
    {
        ice_initialize();
    }

    public Employee(long Number, string FirstName, string LastName)
    {
        this.Number = Number;
        ArgumentNullException.ThrowIfNull(FirstName);
        this.FirstName = FirstName;
        ArgumentNullException.ThrowIfNull(LastName);
        this.LastName = LastName;
        ice_initialize();
     }

    public Employee(Ice.InputStream istr)
    {
        this.Number = istr.readLong();
        this.FirstName = istr.readString();
        this.LastName = istr.readString();
        ice_initialize();
    }
}

The generated record class provides the following constructors:

  • a primary constructor with parameters for all the fields

  • a constructor with parameters for fields with the following Slice types: Sequence, Dictionary, Struct mapped to record class in C#
    This constructor may be parameterless. It initializes string fields to the empty string, and other fields to their default value (typically 0, null or default; see Fields).

  • an “unmarshaling” constructor that unmarshals the record class from an InputStream

Property Mapping

You can instruct the compiler to emit property definitions instead of public fields. For example:

SLICE
["cs:property"] struct Point
{
    ["cs:identifier:X"]
    double x;

    ["cs:identifier:Y"]
    double y;
}

The "cs:property" metadata directive causes the compiler to generate a property for each Slice field:

C#
public partial record struct Point
{ 
    public double X { get; set; }
    public double Y { get; set; }

    // ...
    // same as without cs:property
}
See Also
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.