Skip to content

C# Code Generation

The dotnet generator produces multi-file C# output with models, clients, server executors, and formatters.

Output Structure

For a project named MyProject, the generator creates:

MyProject.Generated/

├── MyProject.Generated.csproj

├── moduleInit.cs — typedefs, formatter registration

├── models/

├── User.cs — message classes

├── Status.cs — enum types

└── Shape.cs — union interfaces + cases

├── client/

└── UserService.client.cs — client proxy

├── server/

├── IUserService.cs — service interface

└── UserService.executor.cs — server executor

└── formatters/

├── User.formatter.cs

└── UserService.formatter.cs

Model Generation

Each msg generates a sealed class:

// From: msg User { id: u4; name: string; email: string?; }

public sealed class User
{
    public uint id { get; set; }
    public string name { get; set; }
    public IonMaybe<string> email { get; set; }
}

Service Interface

Each service generates an interface:

// From: service UserService(spaceId: u4) { GetUser(userId: u4): User; }

public interface IUserService : IIonService
{
    Task<User> GetUser(
        uint spaceId,
        uint userId,
        CancellationToken ct = default);
}

Base arguments become the first parameters. CancellationToken is always appended.

Client Proxy

Generated client proxies handle serialization and HTTP/WebSocket transport:

// Generated client proxy (simplified)
internal sealed class Ion_UserService_ClientImpl : IUserService
{
    private readonly IonClient _client;
    private readonly uint _spaceId;

    public async Task<User> GetUser(uint spaceId, uint userId, CancellationToken ct)
    {
        var request = new IonRequest("UserService", "GetUser");
        // Serialize [spaceId, userId] to CBOR
        // POST to /ion/UserService/GetUser.unary
        // Deserialize CBOR response to User
        return result;
    }
}

Server Executor

The executor sits between the network layer and your service implementation:

// Generated executor (simplified)
internal sealed class Ion_UserService_ServiceExecutor
    : IServiceExecutorRouter
{
    public async Task Execute(
        string method,
        ReadOnlyMemory<byte> payload,
        IIonRequestTerminator terminator)
    {
        // 1. Deserialize args from CBOR
        // 2. Resolve IUserService from DI
        // 3. Call service.GetUser(args...)
        // 4. Serialize result to CBOR
        // 5. Write response via terminator
    }
}

Module Initialization

moduleInit.cs runs at application startup and registers all formatters, descriptors, and type aliases:

// moduleInit.cs (auto-generated)
global using UserId = uint;

[ModuleInitializer]
internal static void Initialize()
{
    // Register all formatters
    IonFormatterStorage<User>.Serialize = ...;
    IonFormatterStorage<User>.Deserialize = ...;

    // Register service descriptors
    IonDescriptorStorage.Register("UserService", ...);
}

.csproj Generation

By default, the compiler also generates a .csproj file with the correct NuGet references. Use --no-emit-csproj to skip this if you manage the project file manually.