Dictionary types

Learn how to define and use dictionaries in Slice.

A dictionary is a built-in generic type that represents an associative array. All keys of this associative array have the same Slice type, and all values have the same Slice type.

A dictionary is like a sequence of key-value pairs with the following constraints:

  • each key is unique
  • the type of the key is a string, bool, integral type, enum type, custom type, or a compact struct with key-compatible fields

You can construct a dictionary type inline, without giving it a name, for example to specify the type of a parameter or field:

slice
module VisitorCenter
interface Greeter {
greet(name: string) -> string
allPreviousGreetings() -> Dictionary<string, string>
}

A built-in generic type with type arguments, such as a Dictionary<string, string>, is called a constructed type.

You can use any Slice type for the value-type of your dictionary. For example:

A field, an element in a sequence, or a value in another dictionary with type Dictionary<K, V> is mapped to an IDictionary<TKey, TValue>.

TKey resp. TValue is the mapped C# type for the Slice key type resp. value type. For example:

By default, when the generated code decodes a dictionary, it creates a C# Dictionary<TKey, TValue> that is transmitted to you (the application) as an IDictionary<TKey, TValue>. You can safely cast this IDictionary<TKey, TValue> to a Dictionary<TKey, TValue> after decoding.

You can override this default with the cs::type attribute. This attribute only changes the type that the generated code uses during decoding to fill-in the field: the C# field type itself remains an IDictionary<TKey, TValue>.

A dictionary parameter has one mapping when it's sent and a different mapping when it's received. This distinction between incoming and outgoing values makes sending dictionaries more convenient and occasionally faster.

Mapping for outgoing valuesDefault mapping for incoming values
IEnumerable<KeyValuePair<TKey, TValue>>
Dictionary<TKey, TValue>

You can override the default mapping for incoming values with the cs::type attribute; this gives you the C# type you specified for incoming values. cs::type doesn't change the mapping for outgoing values.

You can use the cs::type attribute to customize the mapping of your dictionary. This attribute accepts a single string argument: the name of a type similar to Dictionary<TKey, TValue>.

More specifically, this type must provide a capacity constructor (with an int parameter). It must also implement IDictionary<TKey, TValue> when cs::type is applied to a field; it must implement ICollection<KeyValuePair<TKey, TValue>> when cs::type is applied to a parameter. For example:

slice
interface Greeter {
// List<KeyValuePair<TKey, TValue>> implements
// ICollection<KeyValuePair<TKey, TValue>>;
// it also provides a capacity constructor.
allPreviousGreetings() ->
[cs::type("List<KeyValuePair<string, string>>")] Dictionary<string, string>
}
C#
public partial interface IGreeter
{
Task<List<KeyValuePair<string, string>>>
AllPreviousGreetingsAsync(
IFeatureCollection? features = null,
CancellationToken cancellationToken = default);
}
public partial interface IGreeterService
{
ValueTask<IEnumerable<KeyValuePair<string, string>>>
AllPreviousGreetingsAsync(
IFeatureCollection features,
CancellationToken cancellationToken);
}

Was this page helpful?