expect/actual declarations
Kotlin Multiplatform's expect/actual pair is invisible in the generated C#: only the actual body is exported, and it looks exactly like an ordinary, non-expect declaration of the same shape. The resulting class, function, or object otherwise follows its usual mapping page (Classes and objects, Objects and companions, Top-level declarations); this page covers only what differs because of expect/actual itself.
Kotlin | C# | Note |
|---|---|---|
| ordinary | gets a public constructor even when the |
| static member, | the containing static class is named after the expect's file, never the actual's |
| ordinary | |
|
|
|
Example
PlatformApi.kt (shared):
PlatformApiMacos.kt (macOS actual; a mingwX64 actual supplies the same surface with different values):
Each packaged binary runs the one target's actual bodies it was built from. In C#, the top-level function and object land on a static class named PlatformApi, after the expect's file (PlatformApiMacos/PlatformApiMingw never exist as C# types), and every position typed Clock resolves to SystemClock:
Constructor default parameters
Kotlin forbids an actual from restating a default value, so the default lives only on the expect declaration's primary constructor. The generator still produces the omitting overload for a class's primary constructor:
A secondary constructor on an expect/actual class gets no synthesized default-parameter overload; pass every parameter explicitly. See Constructor default parameters for the general mechanism.
Function default parameters on a top-level expect function
The same restatement rule applies to an ordinary function, and the generator resolves the default from the matching expect fun, but only for a top-level function. A class method, object member, companion member, or extension declared on an expect class gets no synthesized overload, even though the equivalent constructor case above does; call those with every parameter explicit.
When a top-level expect fun is overloaded, each overload's default is resolved from its own expect (matched by parameter count, name, and type), never from a namesake's. See Function default parameters for the general mechanism.
Sealed, interface, enum, and value class actuals
expect sealed class, expect interface, expect enum class, and expect value class all resolve to their actual declaration and then follow the same mapping an ordinary, non-expect declaration of that shape would: a sealed class's subclasses are read off the actual (they can live in the actual's source set or, for a common-side declaration, anywhere on the dependency path to it), and a per-target implementing class for an expect interface that stays internal never reaches C#. Nothing about discrimination, enum ordinals, or value-class unwrapping changes; see Classes and objects for those mechanics.
Limitations
actual typealiasto a generic target (actual typealias Bag<T> = Crate<T>) or to a stdlib type the forward direction can't export (actual typealias Failure = kotlin.IllegalStateException) is not supported. A member that mentions it is skipped from the generated C# rather than emitting a type that was never generated; redirect to a type you declare and export instead.Cross-module (klib)
expect/actualisn't supported: theexpectand itsactualmust share a module.Annotations declared on the
expectside don't reach the generated C#. KDoc does: see Documentation comments.expect annotation classnever binds; there is no C# projection of a Kotlin annotation class regardless ofexpect/actual.Two packaged targets can legitimately produce different C# if their actuals diverge beyond the expect's contract, but a package ships only one target's generated API surface; nothing diffs the two.