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:
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:
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:
- The generated class
TimeOfDay
inherits fromIce.Value
. This means that all classes implicitly inherit fromValue
, which is the ultimate ancestor of all classes. - The generated class contains a public member for each Slice data member.
- 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.Value
. Value
is a very simple base class with just a few methods:
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 callice_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 theSlicedData
object if the value has been sliced during un-marshaling ornull
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:
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:
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:
["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:
["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:
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:
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:
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:
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:
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:
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 Type | Default Value |
---|---|
string | Empty string |
enum | Zero |
struct | Default-constructed value |
Numeric | Zero |
bool | False |
sequence | Null |
dictionary | Null |
class /interface | Null |
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:
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:
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:
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
.