Interface

Learn how Ice interfaces are mapped to C#

The Ice compiler for C# compiles Ice interface Name into two C# interfaces (IName and INameService) and one C# struct (NameProxy). The identifiers of the generated interfaces and struct are always in Pascal case, per the usual C# naming conventions, even when Name is not in Pascal case.

The metadata directive "cs:identifier" allows you to remap Name to an identifier of your choice.

Interface IName provides the client-side API that allows you to call the remote service that implements the associated Ice interface. It's a minimal interface with an abstract method for each operation defined in this interface.

For example:

ice
module Example
{
interface Widget
{
void spin(int speed);
}
}
C#
namespace Example
{
public partial interface IWidget
{
// One method per operation
Task SpinAsync(
int speed,
IFeatureCollection? features = null,
CancellationToken cancellationToken =
default);
}
}

Ice interface inheritance naturally maps to interface inheritance in C#. For example:

ice
module Draw
{
interface Rectangle extends Shape, Fillable
{
idempotent void resize(int x, int y);
}
}

maps to:

C#
namespace Draw;
public partial interface IRectangle : IShape, IFillable
{
Task ResizeAsync(
int x,
int y,
IFeatureCollection? features = null,
CancellationToken cancellationToken = default);
}

The generated record struct NameProxy implements IName by sending requests to a remote service with IceRPC.

An instance of this struct is a local surrogate for the remote service that implements Name—in other words, a proxy for this service.

In order to call a remote service, you need to construct a proxy struct using one of its "invoker" constructors:

C#
public readonly partial record struct WidgetProxy : IWidget, IIceProxy
{
public WidgetProxy(
IInvoker invoker,
ServiceAddress? serviceAddress = null,
IceEncodeOptions? encodeOptions = null)
{
...
}
public WidgetProxy(
IInvoker invoker,
System.Uri serviceAddressUri,
IceEncodeOptions? encodeOptions = null)
: this(invoker, new ServiceAddress(serviceAddressUri), encodeOptions)
{
}
}

The invoker parameter represents your invocation pipeline, the serviceAddress or serviceAddressUri parameter corresponds to the address of the remote service, and the encodeOptions parameter allows you to customize the encoding of operation parameters. See IceEncodeOptions for details.

A null service address is equivalent to a service address for the ice protocol with the default service path of the associated Ice interface.

The default service path of an Ice interface is / followed by its fully qualified name with :: replaced by .. For example, the default service path of Ice interface VisitorCenter::Greeter is /VisitorCenter.Greeter. It's also available as the constant DefaultServicePath in the generated proxy struct:

C#
public readonly partial record struct WidgetProxy : IWidget, IIceProxy
{
public const string DefaultServicePath = "/Example.Widget";
}

The generated proxy struct also provides a parameterless constructor that initializes the proxy's service address to an ice-protocol service address with the default service path. If you call this constructor directly, you also need to initialize the invoker, for example:

C#
// Calls WidgetProxy's parameterless constructor
var proxy = new WidgetProxy { Invoker = connection };
// The above is equivalent to:
var proxy = new WidgetProxy(connection);

When an Ice interface derives from another interface, its proxy struct provides an implicit conversion operator to the base interface. For example:

ice
module Draw
{
interface Rectangle extends Shape, Fillable
{
idempotent void resize(int x, int y);
}
}

maps to:

C#
namespace Draw
{
public readonly partial record struct RectangleProxy : IRectangle, IIceProxy
{
public static implicit operator ShapeProxy(RectangleProxy proxy)
{
...
}
public static implicit operator FillableProxy(RectangleProxy proxy)
{
...
}
}
}

This way, you can pass a "derived" proxy to a method that expects a "base" proxy, even though there is naturally no inheritance relationship between these proxy structs.

Interface INameService is a server-side helper: it helps you create a service (a C# class) that implements Ice interface Name.

The principle is straightforward: your service class must be a partial class that implements INameService. It must also carry the Service attribute.

The Service attribute instructs the Service Generator to implement interface IDispatcher by directing incoming requests to INameService methods based on the operation names.

The Service Generator is a C# source generator provided by the IceRpc.ServiceGenerator NuGet package.

For example:

ice
module Example
{
interface Widget
{
void spin(int speed);
}
}
C#
namespace Example
{
// Generated code
public partial interface IWidgetService
{
// One method per operation
ValueTask SpinAsync(
int speed,
IFeatureCollection features,
CancellationToken cancellationToken);
}
// Application code
[Service]
internal partial class MyWidget : IWidgetService
{
// implement SpinAsync ...
}
}

Even though INameService is an interface, it's not used as an abstraction: you shouldn't make calls to this interface or create decorators for this interface. It's just a model that your service class must implement.

Note that the same service class can implement any number of Ice interfaces provided their operations have unique names. You can even mix Ice, Slice, and Protobuf. For example:

ice
module Example
{
interface Widget
{
void spin(int speed);
}
interface Counter
{
int getCount();
}
}
C#
// Implements two Ice interfaces
[Service]
internal partial class MyWidget : IWidgetService,
ICounterService
{
// implements SpinAsync and GetCountAsync.
}

Was this page helpful?

CookiesYour privacy
This website uses cookies to analyze traffic and improve your experience.
By clicking "Accept," you consent to the use of these cookies. You can learn more about our cookies policy in our Privacy Policy.