C-Sharp Mapping for Classes

On this page:

Basic C# Mapping for Classes

A Slice class is mapped to a C# class with the same name. By default, the generated class contains a public data member for each Slice data member (just as for structures and exceptions). Alternatively, you can use the property mapping by specifying the "clr:property" metadata directive, which generates classes with virtual properties instead of data members.

Consider the following class definition:

Slice
class TimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
    string tz;          // e.g. GMT, PST, EDT...
}

The Slice compiler generates the following code for this definition:

C#
public partial class TimeOfDay : Ice.Value
{
    public short hour;
    public short minute;
    public short second;
    public string tz;

    partial void ice_initialize();

    public TimeOfDay()
    {
        ...
        ice_initialize();
    }

    public TimeOfDay(short hour, short minute, short second, string tz)
    {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        this.tz = tz;
        ice_initialize();
    }

    public static new string ice_staticId() { ... }
    public override string ice_id() { ... }
}

There are a number of things to note about the generated code:

  1. The generated class TimeOfDay inherits from Ice.Value. This means that all classes implicitly inherit from Value, which is the ultimate ancestor of all classes. 
  2. The generated class contains a public member for each Slice data member.
  3. The generated class has a constructor that takes one argument for each data member, as well as a default constructor.

There is quite a bit to discuss here, so we will look at each item in turn.

Inheritance from Ice.Value in C#

Classes implicitly inherit from a common base class, Ice.ValueValue is a very simple base class with just a few methods:

C#
namespace Ice
{
    [Serializable]
    public abstract class Value : ICloneable
    {
        public static string ice_staticId() { ... }
        public virtual string ice_id() { ... }
        public virtual void ice_preMarshal() {}
        public virtual void ice_postUnmarshal() {}
		public virtual void ice_getSlicedData() {}
        public object Clone() { ... }
    }
}

The Value methods behave as follows:

  • ice_id
    This function returns the actual run-time type ID for a class. If you call ice_id through a reference to a base instance, the returned type id is the actual (possibly more derived) type ID of the instance.
  • ice_preMarshal
    The Ice run time invokes this function prior to marshaling the object's state, providing the opportunity for a subclass to validate its declared data members.
  • ice_postUnmarshal
    The Ice run time invokes this function after unmarshaling an object's state. A subclass typically overrides this function when it needs to perform additional initialization using the values of its declared data members.
  • ice_getSlicedData
    This functions returns the SlicedData object if the value has been sliced during un-marshaling or null otherwise.
  • Clone
    This method returns a shallow member-wise copy of the value.

Note that the generated class does not override GetHashCode and Equals. This means that classes are compared using shallow reference equality, not value equality (as is used for structures).

Class Data Members in C#

By default, data members of classes are mapped exactly as for structures and exceptions: for each data member in the Slice definition, the generated class contains a corresponding public data member. Optional data members are mapped to instances of the Ice.Optional type.

If you wish to restrict access to a data member, you can modify its visibility using the protected metadata directive. The presence of this directive causes the Slice compiler to generate the data member with protected visibility. As a result, the member can be accessed only by the class itself or by one of its subclasses. For example, the TimeOfDay class shown below has the protected metadata directive applied to each of its data members:

Slice
class TimeOfDay
{
    ["protected"] short hour;   // 0 - 23
    ["protected"] short minute; // 0 - 59
    ["protected"] short second; // 0 - 59
    ["protected"] string tz;    // e.g. GMT, PST, EDT...
}

The Slice compiler produces the following generated code for this definition:

C#
public partial class TimeOfDay : ...
{
    protected short hour;
    protected short minute;
    protected short second;
    protected string tz;

    partial void ice_initialize();
    public TimeOfDay()
    {
        ice_initialize();
    }

    public TimeOfDay(short hour, short minute, short second, string tz)
    {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        this.tz = tz;
        ice_initialize();
    }

    // ...
}

For a class in which all of the data members are protected, the metadata directive can be applied to the class itself rather than to each member individually. For example, we can rewrite the TimeOfDay class as follows:

Slice
["protected"] class TimeOfDay
{
    short hour;   // 0 - 23
    short minute; // 0 - 59
    short second; // 0 - 59
    string tz;    // e.g. GMT, PST, EDT...
}

If a protected data member also has the clr:property directive, the generated property has protected visibility. Consider the TimeOfDay class once again:

Slice
["protected", "clr:property"] class TimeOfDay
{
    short hour;   // 0 - 23
    short minute; // 0 - 59
    short second; // 0 - 59
    string tz;    // e.g. GMT, PST, EDT...
}

The effects of combining these two metadata directives are shown in the generated code below:

C#
public partial class TimeOfDay : ...
{
    private short hour_prop;
    protected short hour
    {
        get
        {
            return hour_prop;
        }
        set
        {
            hour_prop = value;
        }
    }

    // ...
}

Refer to the structure mapping for more information on the property mapping for data members.

Class Operations in C#

Operations on classes are deprecated as of Ice 3.7. Skip this section unless you need to communicate with old applications that rely on this feature.

Operations in classes are not mapped at all into the corresponding C# class. The generated C# class is the same whether the Slice class has operations or not. 

For a class that defines or inherits operations, the Slice-to-C# compiler generates instead a separate <class-name>Disp_ class that can be used to implement an Ice object with these operations. Let's change our example to add a class operation:

Slice
class FormattedTimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
    string tz;          // e.g. GMT, PST, EDT...
    string format();
}

The Slice compiler generates the following code:

C#
public partial class FormattedTimeOfDay : Ice.Value
{
    // ... operation format() not mapped at all here
}
 
public interface FormattedTimeOfDayOperations_
{
    string format(Ice.Current current = null);
}
 
// Disp class for servant implementation
public abstract class FormattedTimeOfDayDisp_ : Ice.ObjectImpl, FormattedTimeOfDayOperations_
{
    public abstract string format(Ice.Current current = null);
    ...
}

This Disp class is the skeleton class for this Slice class. Skeleton classes are described in the Server-Side C-Sharp Mapping for Interfaces.

 

Value Factories in C#

While value factories are necessary when using classes with operations (a now deprecated feature), value factories may be used for any kind of class and are not deprecated.

Value factories allow you to create classes derived from the C# classes generated by the Slice compiler, and tell the Ice run time to create instances of these classes when unmarshaling. For example, with the following simple interface:

Slice
interface Time
{
    TimeOfDay get();
}

The default behavior of the Ice run time will create and return an instance of the generated TimeOfDay class.

If you wish, you can create your own custom derived class, and tell Ice to create and return these instances instead. For example:

C#
public class CustomTimeOfDay : TimeOfDay
{
    public string format() { ... prints formatted data members ... }
}

You then create and register a value factory for your custom class with your Ice communicator:

C#
Communicator communicator = ...;
communicator.getValueFactoryManager().add((string type) => {
        Debug.Assert(type.Equals(TimeOfDay.ice_staticId()));
        return new CustomTimeOfDay();
    },               
    TimeOfDay.ice_staticId());

Class Constructors in C#

A class has a default constructor that initializes data members as follows:

Data Member TypeDefault Value
stringEmpty string
enumZero
structDefault-constructed value
NumericZero
boolFalse
sequenceNull
dictionaryNull
class/interfaceNull

The constructor won't explicitly initialize a data member if the default C# behavior for that type produces the desired results.

If you wish to ensure that data members of primitive and enumerated types are initialized to specific values, you can declare default values in your Slice definition. The default constructor initializes each of these data members to its declared value instead.

A class also provides a constructor that accepts one argument for each member of the class. This allows you to create and initialize a class in a single statement, for example:

C#
TimeOfDay tod = new TimeOfDay(14, 45, 00, "PST"); // 2:45pm

For a derived class, the constructor requires one argument for every member of the class, including inherited members. For example, consider the the definition from Class Inheritance once more:

Slice
class TimeOfDay
{
    short hour;         // 0 - 23
    short minute;       // 0 - 59
    short second;       // 0 - 59
}

class DateTime extends TimeOfDay
{
    short day;          // 1 - 31
    short month;        // 1 - 12
    short year;         // 1753 onwards
}

The Slice compiler generates the following constructors for these types:

C#
public partial class TimeOfDay : ...
{
    partial void ice_initialize();
    public TimeOfDay()
    {
        ice_initialize();
    }

    public TimeOfDay(short hour, short minute, short second)
    {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
        ice_initialize();
    }

    // ...
}

public partial class DateTime : TimeOfDay
{
    public DateTime() : base() {}

    public DateTime(short hour,
                    short minute,
                    short second,
                    short day,
                    short month,
                    short year) : base(hour, minute, second)
    {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    // ...
}

If you want to instantiate and initialize a DateTime instance, you must either use the default constructor or provide values for all of the data members of the instance, including data members of its base class.

For each optional data member of a class, the constructor accepts an Ice.Optional parameter of the appropriate type. You can pass the value Ice.Util.None as the value of any optional data member to initialize it to an unset condition.

All generated constructors call the ice_initialize partial method after initializing the data members. You can customize class initialization by providing your own implementation of ice_initialize.

See Also