enum Fruit { Apple, Pear, Orange };
enum Fruit { Apple, Pear, Orange };
Ice for .NET supports two different mappings for structures. By default, Slice structures map to C# structures if they (recursively) contain only value types. If a Slice structure (recursively) contains a string, proxy, class, sequence, or dictionary member, it maps to a C# class. A metadata directive (see
Section 4.17) allows you to force the mapping to a C# class for Slice structures that contain only value types.
struct Point {
double x;
double y;
};
public partial struct Point
{
public double x;
public double y;
public Point(double x, double y);
public override int GetHashCode();
public override bool Equals(object other);
public static bool operator==(Point lhs, Point rhs);
public static bool operator!=(Point lhs, Point rhs);
}
The generated constructor accepts one argument for each structure member, in the order in which they are defined in the Slice definition. This allows you to construct and initialize a structure in a single statement:
Point p = new Point(5.1, 7.8);
The structure overrides the GetHashCode and
Equals methods to allow you to use it as the key type of a dictionary. (Note that the static two-argument version of
Equals is inherited from
System.Object.) Two structures are equal if (recursively) all their data members are equal. Otherwise, they are not equal. For structures that contain reference types,
Equals performs a deep comparison; that is, reference types are compared for value equality, not reference equality.
The mapping for Slice structures to C# structures provides value semantics. Usually, this is appropriate, but there are situations where you may want to change this:
To allow you to choose the correct performance and functionality trade-off, the Slice-to-C# compiler provides an alternative mapping of structures to classes, for example:
The "clr:class" metadata directive instructs the Slice-to-C# compiler to generate a mapping to a C# partial class for this structure. The generated code is almost identical, except that the keyword
struct is replaced by the keyword
class1 and that the class has a default constructor and inherits from
ICloneable:
public partial class Point : _System.ICloneable
{
public double x;
public double y;
public Point();
public Point(double x, double y);
public object Clone();
public override int GetHashCode();
public override bool Equals(object other);
public static bool operator==(Point lhs, Point rhs);
public static bool operator!=(Point lhs, Point rhs);
}
The class has a default constructor that default-constructs each data member. This means members of primitive type are initialized to the equivalent of zero, and members of reference type are initialized to null. Note that applications must always explicitly initialize a member whose type is a class-mapped structure because the Ice run time does not accept null as a legal value for these types.
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 (see
Section 4.9.2). The default constructor initializes each of these data members to its declared value.
The class also provides a second constructor that has one parameter for each data member. This allows you to construct and initialize a class instance in a single statement:
Point p = new Point(5.1, 7.8);
The Clone method performs a shallow memberwise copy, and the comparison methods have the usual semantics (they perform value comparison).
Note that you can influence the mapping for structures only at the point of definition of a structure, that is, for a particular structure type, you must decide whether you want to use the structure or the class mapping. (You cannot override the structure mapping elsewhere, for example, for individual structure members or operation parameters.)
As we mentioned previously, if a Slice structure (recursively) contains a member of reference type, it is automatically mapped to a C# class. (The compiler behaves as if you had explicitly specified the
“clr:class” metadata directive for the structure.)
Here is our Employee structure from
Section 4.9.4 once more:
struct Employee {
long number;
string firstName;
string lastName;
};
public partial class Employee : _System.ICloneable
{
public long number;
public string firstName;
public string lastName;
public Employee();
public Employee(long number,
string firstName,
string lastName);
public object Clone();
public override int GetHashCode();
public override bool Equals(object other);
public static bool operator==(Employee lhs, Employee rhs);
public static bool operator!=(Employee lhs, Employee rhs);
}
The “clr:property” metadata directive causes the compiler to generate a property for each Slice data member:
public partial struct Point
{
private double x_prop;
public double x {
get {
return x_prop;
}
set {
x_prop = value;
}
}
private double y_prop;
public double y {
get {
return y_prop;
}
set {
y_prop = value;
}
}
// Other methods here...
}
Note that the properties are non-virtual because C# structures cannot have virtual properties. However, if you apply the
“clr:property” directive to a structure that contains a member of reference type, or if you combine the
“clr:property” and
“clr:class” directives, the generated properties are virtual. For example:
public partial class Point : System.ICloneable
{
private double x_prop;
public virtual double x {
get {
return x_prop;
}
set {
x_prop = value;
}
}
private double y_prop;
public virtual double y {
get {
return y_prop;
}
set {
y_prop = value;
}
}
// Other methods here...
}
Ice for .NET supports several different mappings for sequences. By default, sequences are mapped to arrays. You can use metadata directives (see
Section 4.17) to map sequences to a number of alternative types:
•
Types derived from Ice.CollectionBase (which is a drop-in replacement for
System.Collections.CollectionBase)
2
By default, the Slice-to-C# compiler maps sequences to arrays. Interestingly, no code is generated in this case; you simply define an array of elements to model the Slice sequence. For example:
Fruit[] fp = { Fruit.Apple, Fruit.Orange };
Fruit fp[] = new Fruit[2];
fp[0] = Fruit.Apple;
fp[1] = Fruit.Orange;
The array mapping for sequences is both simple and efficient, especially for sequences that do not need to provide insertion or deletion other than at the end of the sequence.
["clr:generic:List"] sequence<string> StringSeq;
["clr:generic:LinkedList"] sequence<Fruit> FruitSeq;
["clr:generic:Queue"] sequence<int> IntQueue;
["clr:generic:Stack"] sequence<double> DoubleStack;
The "clr:generic:<type>" metadata directive causes the
slice2cs compiler to the map the corresponding sequence to one of the containers in the
System.Collections.Generic namespace. For example, the
Queue sequence maps to
System.Collections.Generic.Queue<int> due to its metadata directive.
The predefined containers allow you to select an appropriate space–performance trade-off, depending on how your application uses a sequence. In addition, if a sequence contains value types, such as
int, the generic containers do not incur the cost of boxing and unboxing and so are quite efficient. (For example,
System.Collections.Generic.List<int> performs within a few percentage points of an integer array for insertion and deletion at the end of the sequence, but has the advantage of providing a richer set of operations.)
If the array mapping and the predefined containers are unsuitable for your application (for example, because may need a priority queue, which does not come with .NET), you can implement your own custom containers and direct
slice2cs to map sequences to these custom containers. For example:
This metadata directive causes the Slice Queue sequence to be mapped to the type
MyTypes.PriorityQueue. You must specify the fully-qualified name of your custom type following the
clr:generic: prefix. This is because the generated code prepends a
global:: qualifier to the type name you provide; for the preceding example, the generated code refers to your custom type as
global::MyTypes.PriorityQueue<int>.
public class PriorityQueue<T> : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator();
public int Count
get;
public void Add(T elmt);
public T this[int index] // Needed for class elements only.
set;
// Other methods and data members here...
}
The CollectionBase mapping is provided mainly for compatibility with Ice versions prior to 3.3. Internally,
CollectionBase is implemented using
System.Collections.Generic.List<T>, so it offers the same performance trade-offs as
List<T>. (For value types,
Ice.CollectionBase is considerably faster than
System.Collections.CollectionBase, however.)
Ice.CollectionBase is not as type-safe as
List<T> because, in order to remain source code compatible with
System.Collections.CollectionBase, it provides methods that accept elements of type
object. This means that, if you pass an element of the wrong type, the problem will be diagnosed only at run time, instead of at compile time. For this reason, we suggest that you do not use the
CollectionBase mapping for new code.
To enable the CollectionBase mapping, you must use the
"clr:collection" metadata directive:
With this directive, slice2cs generates a type that derives from
Ice.CollectionBase:
public class FruitPlatter : Ice.CollectionBase<M.Fruit>,
System.ICloneable
{
public FruitPlatter();
public FruitPlatter(int capacity);
public FruitPlatter(Fruit[] a);
public FruitPlatter(
System.Collections.Generic.IEnumerable<Fruit> l);
public static implicit operator
_System.Collections.Generic.List<Fruit>(FruitPlatter s);
public virtual FruitPlatter GetRange(int index, int count);
public static FruitPlatter Repeat(Fruit value, int count);
public object Clone();
}
The generated FruitPlatter class provides the following methods:
•
FruitPlatter();
FruitPlatter(int capacity);
FruitPlatter(Fruit[] a);
FruitPlatter(IEnumerable<Fruit> l);
The Clone method returns a shallow copy of the source sequence.
The remaining methods are provided by the generic Ice.CollectionBase base class. This class provides the following methods:
•
CollectionBase();
CollectionBase(int capacity);
CollectionBase(T[] a);
CollectionBase(IEnumerable<T> l);
These methods append value at the end of the sequence. They return the index at which the element is inserted (which always is the value of
Count prior the call to
Add.)
•
void CopyTo(T[] a);
void CopyTo(T[] a, int i);
void CopyTo(int i, T[] a, int ai, int c);
void CopyTo(System.Array a, int i);
The ToArray method returns the contents of the sequence as an array.
The AddRange methods append the contents of a sequence or an array to the current sequence, respectively.
•
virtual void Sort();
virtual void Sort(System.Collections.IComparer
comparer);
virtual void Sort(int index, int count,
System.Collections.IComparer comparer);
•
virtual int BinarySearch(T value);
virtual int BinarySearch(T value,
System.Collections.IComparer comparer);
virtual int BinarySearch(int index, int count,
T value,
System.Collections.IComparer comparer);
Note that for all methods that return sequences, these methods perform a shallow copy, that is, if you have a sequence whose elements have reference type, what is copied are the references, not the objects denoted by those references.
Ice.CollectionBase also provides the usual
GetHashCode and
Equals methods, as well as the comparison operators for equality and inequality. (Two sequences are equal if they have the same number of elements and all elements in corresponding positions are equal, as determined by the
Equals method of the elements.)
Ice.CollectionBase also implements the inherited
IsFixedSize,
IsReadOnly, and
IsSynchronized properties (which return false), and the inherited
SyncRoot property (which returns
this).
FruitPlatter fp = new FruitPlatter();
fp.Add(Fruit.Apple);
fp.Add(Fruit.Orange);
enum Fruit { Apple, Orange, Pear };
["clr:generic:List"] sequence<Fruit> FruitPlatter;
["clr:generic:LinkedList"] sequence<FruitPlatter> Cornucopia;
enum Fruit { Apple, Orange, Pear };
sequence<Fruit> FruitPlatter;
["clr:LinkedList"] sequence<FruitPlatter> Cornucopia;
As you can see, the generated code now no longer mentions the type FruitPlatter anywhere and deals with the outer sequence elements as an array of
Fruit instead.
Ice for .NET supports three different mappings for dictionaries. By default, dictionaries are mapped to
System.Collections.Generic.Dictionary<T>. You can use metadata directives (see
Section 4.17) to map dictionaries to two other types:
•
Types derived from Ice.DictionaryBase (which is a drop-in replacement for
System.Collections.DictionaryBase)
3
You can use the "clr:generic:SortedDictionary" metadata directive to change the mapping to a sorted dictionary:
The DictionaryBase mapping is provided mainly for compatibility with Ice versions prior to 3.3. Internally,
DictionaryBase is implemented using
System.Collections.Generic.Dictionary<T>, so it offers the same performance trade-offs as
Dictionary<T>. (For value types,
Ice.DictionaryBase is considerably faster than
System.Collections.DictionaryBase, however.)
Ice.DictionaryBase is not as type-safe as
Dictionary<T> because, in order to remain source code compatible with
System.Collections.DictionaryBase, it provides methods that accept elements of type
object. This means that, if you pass an element of the wrong type, the problem will be diagnosed only at run time, instead of at compile time. For this reason, we suggest that you do not use the
DictionaryBase mapping for new code.
To enable the DictionaryBase mapping, you must use the
"clr:collection" metadata directive:
With this directive, slice2cs generates a type that derives from
Ice.CollectionBase:
public class EmployeeMap : Ice.DictionaryBase<long, Employee>,
System.ICloneable
{
public void AddRange(EmployeeMap m);
public object Clone();
}
Note that the generated EmployeeMap class derives from
Ice.DictionaryBase, which provides a super-set of the interface of the .NET
System.Collections.DictionaryBase class. Apart from methods inherited from
DictionaryBase, the class provides a
Clone method and an
AddRange method that allows you to append the contents of one dictionary to another. If the target dictionary contains a key that is also in the source dictionary, the target dictionary’s value is preserved. For example:
Employee e1 = new Employee();
e1.number = 42;
e1.firstName = "Herb";
e1.lastName = "Sutter";
EmployeeMap em1 = new EmployeeMap();
em[42] = e;
Employee e2 = new Employee();
e2.number = 42;
e2.firstName = "Stan";
e2.lastName = "Lipmann";
EmployeeMap em2 = new EmployeeMap();
em[42] = e2;
// Add contents of em2 to em1
//
em1.AddRange(em2);
// Equal keys preserve the original value
//
Debug.Assert(em1[42].firstName.Equals("Herb"));
The DictionaryBase class provides the following methods:
public abstract class DictionaryBase<KT, VT>
: System.Collections.IDictionary
{
public DictionaryBase();
public int Count { get; }
public void Add(KT key, VT value);
public void Add(object key, object value);
public void CopyTo(System.Array a, int index);
public void Remove(KT key);
public void Remove(object key);
public void Clear();
public System.Collections.ICollection Keys { get; }
public System.Collections.ICollection Values { get; }
public VT this[KT key] { get; set; }
public object this[object key] { get; set; }
public bool Contains(KT key);
public bool Contains(object key);
public override int GetHashCode();
public override bool Equals(object other);
public static bool operator==(DictionaryBase<KT, VT> lhs,
DictionaryBase<KT, VT> rhs);
public static bool operator!=(DictionaryBase<KT, VT> lhs,
DictionaryBase<KT, VT> rhs);
public System.Collections.IEnumerator GetEnumerator();
public bool IsFixedSize { get; }
public bool IsReadOnly { get; }
public bool IsSynchronized { get; }
public object SyncRoot { get; }
}
The methods have the same semantics as the corresponding methods in the .NET Framework. The
Equals method returns true if two dictionaries contain the same number of entries and, for each entry, the key and value are the same (as determined by their
Equals methods).
The Clone method performs a shallow copy.
The class also implements the inherited IsFixedSize,
IsReadOnly, and
IsSynchronized properties (which return false), and the
SyncRoot property (which returns
this).
A utility class, Ice.CollectionComparer allows you to compare collections for equality:
public class CollectionComparer {
public static bool
Equals(System.Collections.IDictionary d1,
System.Collections.IDictionary d2);
public static bool
Equals(System.Collections.ICollection c1,
System.Collections.ICollection c2);
public static bool
Equals(System.Collections.IEnumerable c1,
System.Collections.IEnumerable c2);
}
Two collections that derive from ICollection or
IEnumerable are equal if they contain the same number of entries and entries compare equal. Note that order is significant, so corresponding entries must not only be equal but must also appear in the same position.