> ## Documentation Index
> Fetch the complete documentation index at: https://repost.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# C# Code Generation

> What the generated C# client contains, the generator block, build-time MSBuild generation, the CI drift check, and the descriptor version handshake.

export const RepostHighlight = () => {
  useEffect(() => {
    const colors = {
      comment: ['#116329', '#6A9955'],
      constant: ['#0550AE', '#9CDCFE'],
      keyword: ['#CF222E', '#569CD6'],
      name: ['#953800', '#4EC9B0'],
      string: ['#0A3069', '#CE9178'],
      type: ['#0550AE', '#4EC9B0'],
      variable: ['#1F2328', '#9CDCFE']
    };
    const tokenPattern = /"[^"\n]*"|@[A-Za-z_]\w*|\b(?:String|Int|Float|Boolean|DateTime|Json)\b|\b(?:true|false)\b|\b\d+(?:\.\d+)?\b|\b(?:now|uuid|cuid)(?=\s*\()|\b[A-Z]\w*\b|\b[A-Za-z_]\w*\b/g;
    const append = (line, text, color) => {
      if (!text) return;
      if (!color) {
        line.append(text);
        return;
      }
      const span = document.createElement('span');
      span.textContent = text;
      span.style.color = color[0];
      span.style.setProperty('--shiki-dark', color[1]);
      line.append(span);
    };
    const commentStart = text => {
      let quoted = false;
      for (let index = 0; index < text.length - 1; index += 1) {
        if (text[index] === '"' && text[index - 1] !== '\\') quoted = !quoted;
        if (!quoted && text[index] === '/' && text[index + 1] === '/') return index;
      }
      return -1;
    };
    const highlightCode = code => {
      if (code.dataset.repostHighlighted !== undefined || code.querySelector('span[style*="--shiki-dark"]')) {
        return;
      }
      code.dataset.repostHighlighted = '';
      const source = code.textContent ?? '';
      const fragment = document.createDocumentFragment();
      for (const text of source.split('\n')) {
        const line = document.createElement('span');
        line.className = 'line';
        const declaration = text.match(/^(\s*)(generator|model|enum|type|event)(\s+)([A-Za-z_]\w*)(.*)$/);
        if (declaration) {
          append(line, declaration[1]);
          append(line, declaration[2], colors.keyword);
          append(line, declaration[3]);
          append(line, declaration[4], colors.name);
          append(line, declaration[5]);
        } else {
          const start = commentStart(text);
          const codeText = start === -1 ? text : text.slice(0, start);
          const leading = codeText.match(/^(\s*)([A-Za-z_]\w*)(?=\s+(?:@|[A-Z]))/);
          const bare = codeText.match(/^(\s*)([A-Za-z_]\w*)(\s*)$/);
          let offset = 0;
          for (const match of codeText.matchAll(tokenPattern)) {
            append(line, codeText.slice(offset, match.index));
            const token = match[0];
            let color = colors.constant;
            if (token.startsWith('"')) color = colors.string; else if (token.startsWith('@') || (/^(now|uuid|cuid)$/).test(token)) color = colors.name; else if ((/^(String|Int|Float|Boolean|DateTime|Json)$/).test(token)) color = colors.type; else if (leading && match.index === leading[1].length) color = colors.variable; else if (bare && match.index === bare[1].length) color = colors.constant; else if ((/^[A-Z]/).test(token)) color = colors.name; else if ((/^[A-Za-z_]/).test(token)) color = colors.variable;
            append(line, token, color);
            offset = match.index + token.length;
          }
          append(line, codeText.slice(offset));
          if (start !== -1) append(line, text.slice(start), colors.comment);
        }
        fragment.append(line, '\n');
      }
      code.replaceChildren(fragment);
    };
    const highlightRepostBlocks = () => {
      for (const block of document.querySelectorAll('.code-block')) {
        const code = block.querySelector('pre code');
        const filename = block.querySelector('[data-component-part="code-block-header-filename"] [title$=".repost"]');
        const looksLikeRepost = (/^\s*(generator|model|enum|type|event)\s+[A-Za-z_]\w*\s*\{/m).test(code?.textContent ?? '');
        if (code && (filename || looksLikeRepost)) highlightCode(code);
      }
    };
    let frame;
    const schedule = () => {
      if (frame !== undefined) return;
      frame = requestAnimationFrame(() => {
        frame = undefined;
        highlightRepostBlocks();
      });
    };
    const observer = new MutationObserver(schedule);
    observer.observe(document.documentElement, {
      childList: true,
      subtree: true
    });
    schedule();
    return () => {
      observer.disconnect();
      if (frame !== undefined) cancelAnimationFrame(frame);
    };
  }, []);
  return null;
};

<RepostHighlight />

Generation emits plain C# source into a folder you choose. Unlike the TypeScript client, which is generated into `node_modules`, the C# client is regular source that compiles through your project's default glob — commit it or gitignore it, your choice. Serialization, retries, and transport live in the `Repost.Client` runtime package; the generated code is data and types only.

## The generator block

A C# `generator` block sets three things the other languages don't need:

```repost repost/schema.repost theme={"languages":{"custom":["/languages/repost.json"]}}
generator sdk {
  language   = "csharp"
  output     = "../Generated"
  namespace  = "Contoso.Events"
  clientName = "RepostClient"
}
```

| Field        | What it owns                                                                                                                                         |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `output`     | The source root, resolved relative to the schema file. Repost owns this tree — it replaces its own files atomically and never touches anything else. |
| `namespace`  | The C# namespace for the generated models, the `Webhooks` tree, and the client.                                                                      |
| `clientName` | The generated client class name (`RepostClient` above). Pick a distinct name per schema when one project hosts more than one.                        |

## What gets emitted

| File                  | Purpose                                                                                                                                       |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `Client.cs`           | The client class and the `Webhooks` method tree, plus one input class per event (for example `BookCreatedInput`).                             |
| `Models.cs`           | Model classes and enums. Optional fields use `Optional<T>`; enums become C# enums.                                                            |
| `Descriptors.cs`      | The serialization descriptor set handed to the runtime at construction, declaring the descriptor-format version this package was emitted for. |
| `schema.repost`       | A verbatim copy of the schema this client was generated from.                                                                                 |
| `schemas.json`        | The JSON Schema catalog for your event types.                                                                                                 |
| `.repost-client.json` | The generation marker: format version, engine version, schema hash, and the file manifest.                                                    |

Models become classes, enums become C# enums, and optional fields use the tri-state `Optional<T>` container so *absent*, *explicit null*, and *present* are never conflated:

```csharp theme={"languages":{"custom":["/languages/repost.json"]}}
public enum Currency
{
    Usd,
    Eur,
}

public sealed partial class Author
{
    public string Name { get; set; } = null!;

    public Optional<string> Email { get; set; }
}
```

The classes are `partial`, so you can add your own members in a separate file without touching the generated code.

## Build-time generation with MSBuild

For teams that would rather not commit generated source, the `Repost.Client.MSBuild` package runs the schema engine as an MSBuild task before compilation — the Grpc.Tools idiom:

```xml csproj theme={"languages":{"custom":["/languages/repost.json"]}}
<PackageReference Include="Repost.Client" Version="1.0.0" />
<PackageReference Include="Repost.Client.MSBuild" Version="1.0.0" PrivateAssets="all" />
```

With a schema at `repost/schema.repost`, a normal `dotnet build` validates the schema, writes the client to `Generated/`, and compiles it. A second build with an unchanged schema and engine is a pure incremental skip, so design-time builds in Visual Studio no-op instantly. The package carries signed, platform-specific engine binaries and checksum-verifies the resolved binary before every run.

## Regenerating and the CI drift check

The `repost` CLI generates the identical tree from the same schema, so a teammate without the MSBuild package can regenerate too:

```bash theme={"languages":{"custom":["/languages/repost.json"]}}
repost schema generate
```

`repost schema migrate dev` also regenerates as part of recording a migration, and `repost schema generate --check` verifies in CI that nothing has drifted without writing to your source tree.

## The version handshake

The generated `Descriptors.cs` declares descriptor format version 2, checked by the client constructor before any send:

* Generated code older than the runtime: the error tells you to re-run `repost schema generate` with the current engine.
* Generated code newer than the runtime: the error tells you to upgrade `Repost.Client`.

A real mismatch throws `RepostDescriptorVersionException` at construction, never silently at send time.

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Configuration" icon="sliders-horizontal" href="/docs/send/csharp/configuration" cta="Configure" arrow="true">
    Client options, credential precedence, and transport tuning.
  </Card>

  <Card title="Polyglot monorepos" icon="git-merge" href="/docs/send/monorepos" cta="Monorepos" arrow="true">
    Generate this client alongside other languages from one schema.
  </Card>

  <Card title="Schema workflow" icon="terminal" href="/docs/cli/schema" cta="Commands" arrow="true">
    The commands behind init, generate, and migrate.
  </Card>
</Columns>
