kotlin-native-nuget Help

Supported features

A scannable index of what maps to what, and in which direction. Look here to find out whether a Kotlin or C# shape crosses the bridge at all, and what it becomes on the other side.

This page carries neither the how nor the why. How to use a mapping lives in the topic page linked in the Docs column; why it maps that way lives in the architecture decision records.

Glyph

Meaning

→

Kotlin → C#: a Kotlin feature you author, surfaced as C# (forward, code generation)

←

C# → Kotlin: a feature from a consumed C# NuGet package, surfaced as Kotlin (reverse)

⇄

both directions

⇸

Kotlin ⇸ C#: a Kotlin feature with no C# projection; the row says why and what the processor does with it

⇄ is the target state. Where a row is one way, the Notes say whether the gap is fundamental (the reverse direction reads a compiled assembly you do not own, so some source-only Kotlin concepts cannot round-trip) or simply not built yet: see ROADMAP.md.

Primitives

Primitive types follow the standard Kotlin/Native C interop mappings.

Kotlin

⇄

C#

Notes

Docs

Byte/Short/Int/Long

⇄

sbyte/short/int/long

Primitives and strings

UByte/UShort/UInt/ULong

⇄

byte/ushort/uint/ulong

Primitives and strings

Float/Double

⇄

float/double

Primitives and strings

Boolean

⇄

bool

Primitives and strings

Char

→

char

A UTF-16 code unit, at a property, parameter, method return and List/Map/Set component. A lone surrogate does not round-trip and is out of scope.

Primitives and strings

String

⇄

string

UTF-8 marshalling.

Primitives and strings

T? (nullable primitive)

→

T?

Property getters use a two-call has-value pair; top-level, method and extension returns use one call. Char? and Boolean? bind at every ordinary position. The reverse direction is not built yet.

Primitives and strings

String?

⇄

string?

→ single call, null rides a null pointer and a nullable parameter carries ?· ← nullability read from NullableAttribute; oblivious metadata binds non-null, with a warning

Primitives and strings

exported class type, nullable (Foo?), as a parameter

→

Foo?

null rides IntPtr.Zero, so no has-value pair is needed. Bound on the class-method, constructor and top-level-function routes.

Classes and objects

kotlin.time.Instant/Instant?

→

System.DateTimeOffset/DateTimeOffset?

Property, parameter, return. Kotlin to C# truncates below 100ns and throws outside years 0001-9999. Also a collection component and a suspend/Flow parameter or element.

Primitives and strings

kotlin.uuid.Uuid/Uuid?

→

System.Guid/Guid?

Crosses as RFC 9562 text on the String wire, so Uuid? rides a null pointer. Exact both ways, Guid.Empty is Uuid.NIL. Also a collection component and a suspend/Flow parameter or element.

Primitives and strings

kotlin.time.Duration/Duration?

→

System.TimeSpan/TimeSpan?

Ticks. Kotlin to C# truncates below 100ns and throws for infinite or out-of-range values; C# to Kotlin is exact within ±146y. Also a collection component and a suspend/Flow parameter or element.

Primitives and strings

ByteArray/ByteArray?

→

byte[]/byte[]?

Copied on every crossing, never a view. Binds at a property, parameter, return, List element, Map value, suspend parameter and return, and Flow parameter and element.

Primitives and strings

Any/Any?

→

object/object?

At every position, the runtime C# value (boxed primitive, wrapper you dispose, struct, collection); Box<*> is Box<object?>; a C# object Kotlin has no type for stays opaque.

Primitives and strings

OOP constructs

Kotlin

⇄

C#

Notes

Docs

class

⇄

class

→ IDisposable over an opaque handle; nested types become real C# nested types; a supertype outside the export set is dropped, its members re-homed · ← AutoCloseable over a handle

Classes and objects· Objects and handles

wrapper dropped without Dispose()

→

released when the .NET GC finalizes it

Eventually, on the finalizer thread, never at process exit; Dispose() stays prompt. A discarded AddX subscription keeps delivering until its owner is released.

Classes and objects

dependency-module type (cross-module export)

→

same class, generated into this module's package

A type your API exposes but a dependency declares is admitted by include/rootPackage or by an additive admit(...); whatever is left out skips, named.

NuGet DSL

constructor

⇄

new Foo(...)

→ defaults widen to one nullable signature, null meaning unset; a class with no bridgeable constructor is factory-only and says so · ← each public .ctor becomes a secondary constructor

Classes and objects· Instance members

data class

→

class

ToString, Equals and Copy are generated.

Data classes

interface

⇄

interface

→ I-prefixed, base inherited, var gets a setter, async members declared (IAsyncDisposable); a return backs a wrapper, a C# impl passes back · ← handle-backed interface a class implements

Interfaces, abstract and sealed· The bridgeable subset

abstract class

→

abstract class

Subclasses inherit the handle; an unimplemented member renders public abstract, an unbridgeable one skips, named. A value typed as the class is an internal subclass, not the Kotlin one.

Interfaces, abstract and sealed

sealed class

→

abstract class

Arms are nested/sibling classes via FromHandle; suspend/Flow members bind on the base, which owns the scope (a generic base: suspend/Flow on each arm); an unexported supertype re-homes.

Interfaces, abstract and sealed

sealed interface

→

abstract class, or interface I<Name> when an arm has a class base or a second sealed parent

Binds at every sealed position; a returned value is the concrete arm. A superclass on one arm flips Pulse to IPulse; implementing I<Name> in C# does not compile.

Interfaces, abstract and sealed

object

⇄

static class

→ singleton; methods and its own val/var properties bind as statics, a const val becomes a real const· ← a consumed static class becomes a Kotlin object

Objects and companions· Static classes and methods

data object (in sealed)

→

sealed subclass

Binds Equals, GetHashCode and ToString like a data class arm, plus its own properties and declared methods, taking the handle receiver rather than going static.

Interfaces, abstract and sealed

enum class

⇄

enum

→ extension methods; companion members are statics on {Enum}Extensions; Mood? binds Nullable<Mood>· ← acronym runs stay whole (HTTPStatus → HTTP_STATUS)

Enums· The bridgeable subset

data class

←

struct (readonly record struct, etc.)

Reverse only: the value decomposes onto the wire, so there is no handle and no close(). Components come from a state-covering constructor, else from declaration order.

Structs

companion object

→

static members

Objects and companions

annotation class

⇸

Fundamental: there is no C# projection of a Kotlin annotation. A public one skips once, named, and using it on an exported declaration changes nothing.

Publishing Kotlin to C#

kotlin.sequences.Sequence<T>

⇸

No eager-copy story exists for a lazy sequence, so a parameter, return or property carrying one skips, named, and the C# declaration that lost it says so in <remarks>.

Collections

declaration marked with an author's @RequiresOptIn marker

⇸

A marked type is never declared; a marked member skips while its owner still exports. publish { exportMarkers(...) } waives a named marker so its declarations keep exporting.

Publishing Kotlin to C#

expect/actual class/interface/enum class/value class, fun, val, object

→

ordinary declaration

The actual is the export root, indistinguishable from a non-expect declaration once exported. An actual typealias erases to its target.

Expect and actual

Properties and top-level declarations

Kotlin

⇄

C#

Notes

Docs

member property (get/set)

⇄

property (get/set)

→ nullable and handle-typed properties; an inherited unimplemented member renders abstract; no public setter, or an override of a base with none, binds get-only · ← val read-only, var settable

Classes and objects· Instance members

@CSharpName("X") on a fun or property

→

member named X

Sets one fun or property's exact C# name, verbatim with no Async suffix. Overrides inherit it; a keyword is @-escaped.

Instance members

instance method return (object, T?, List/Map/Set, enum, Char, String?, Int?, …)

→

matching C# return type

Same plan cascade as a property getter, except a nullable numeric return uses a single call: a method may have side effects and cannot be invoked twice.

Classes and objects

top-level function

→

static class method, PascalCase

One static class per source file. A file whose every declaration skips emits no class at all, and a name colliding with a same-file top-level property fails generation.

Top-level declarations

top-level property

→

static property

get and set, including nullable.

Top-level declarations

const val

→

const

Value is the compiler's evaluated constant, so an expression or a dependency const binds; hex/underscore literal spelling isn't kept.

Top-level declarations

extension function

→

static method

A real C# extension method on every receiver a parameter binds as, generic and bare T included; Int? binds as this int?, so a bare int call is CS1929.

Extensions

extension property

→

C# 14 extension property (cat.IsKitten)

Same receivers as a function, generic ones as extension<T>(...); Int? is extension(int? receiver). A shadowing member or same-named function skips it, named.

Extensions

bare T extension receiver (fun <T : Pet> T.f())

→

F<T>(this T) where T : IPet

Homes on the first bound's {Bound}Extensions; an unbounded T lands on ObjectExtensions and shows on every C# type in that namespace.

Extensions

star-projected extension receiver (Bindle<*>, List<*>)

→

IsPacked<TBindle>(this Bindle<TBindle>)

A synthesized type parameter named after the class; a projection stays refused at every other position.

Extensions

member extension (class G { fun Cat.brush() })

→

instance method g.Brush(cat)

The receiver is the first parameter, named receiver; C# cannot declare an extension in a non-static class.

Extensions

member extension property (class G { val Cat.nick })

→

accessors g.GetNick(cat), SetNick(cat, v)

The receiver is the first parameter; a Flow typed one is a flow method Stream(cat).

Extensions

member extension in an object, companion, enum, value class, sealed type or interface

→

static, this-enum or instance method taking the receiver first

Property or function, suspend and Flow included; the enum and value class async forms name it receiver_.

Extensions

Generics

Kotlin

⇄

C#

Notes

Docs

class<T>

⇄

class<T>

→ type-erased wrapper; a bare T carries null unless bound non-null · ← bound per closed instantiation; BCL ones are diagnosed

Generics· Generic types

a closed Box<String> at a property, parameter, return or element

→

Box<string>

A returned wrapper is yours to dispose, a passed one is borrowed; a List argument skips, named, and Box<*> is Box<object?>.

Generics

a generic interface at any position (Keepsake<String>)

→

IKeepsake<string>, implementable in C#

A Kotlin value is the wrapper Keepsake<T>, yours to dispose; your own implementation comes back as itself, also in a collection or Box. suspend and Flow members bind, in C# too.

Interfaces, abstract and sealed

generic sealed class<T>

→

abstract class Outcome<T>, arms on a non-generic Outcome

An arm fixing the argument (Err : Outcome<Nothing>) is generic too (Err<T>); a Kotlin-generic arm binds suspend, Flow, callbacks and stored pairs over T; Outcome<*> skips.

Interfaces, abstract and sealed

class<T>(...) constructor

⇄

typed constructors

→ typed, null-capable arguments, one entry point per T· ← one Kotlin constructor per unambiguous instantiation; two that erase alike are both dropped

Generics· Generic types

a method declared on a class<T>

→

instance method on the generic carrier

Every T position crosses as a handle, T in a lambda or a Flow included; suspend members bind; a generic subclass is refused; List<T> binds as IReadOnlyList<T>.

Generics

a property declared on a class<T>

→

its own C# type

T/T? properties cross as a handle, a var with a setter; every other declared type (String, Cat, ...) keeps its own C# type, never T.

Generics

a type nested in a class<T>, or a generic class nested in a class

→

Box.Lid on a non-generic static Box· Outer.Inner<T>

Never Box<int>.Lid; an inner class of Box<T> sits there with T first (Box.Seal<T>(box)).

Classes and objects

top-level fun <T> f()

→

static generic method T F<T>(T value)

Every T crosses boxed, where carries bounds; reified reifies at its bound (Any? if none); a suspend lambda or Flow signature skips, named.

Generics

suspend fun <T> (top-level, class member, sealed arm, object, companion)

→

Task<T> FAsync<T>(T value, CancellationToken)

T boxed as in a generic method, List/Set/Map of T too, T : Enum<T> included; an out-of-bound T faults the Task.

Coroutines and Flow

suspend member of an object, companion, extension, enum or value class

→

static or extension Task<T> FAsync(..., CancellationToken)

Each call runs on its own scope, so only the token cancels it; a companion and an instance FAsync with equal parameters is a build error.

Coroutines and Flow

Flow<T>/StateFlow<T>/MutableStateFlow<T> member of a class<T>

→

KotlinFlow<T>/KotlinStateFlow<T>/KotlinMutableStateFlow<T>

The class is IAsyncDisposable; .Value is writable for a T with at most one bound.

Coroutines and Flow

suspend member of a class<T> or generic sealed arm

→

Task<T> TakeAsync(CancellationToken)

The class is IAsyncDisposable; T boxed, multi-bound T and M : Enum<M> work. Not an inner class or a generic sealed base.

Coroutines and Flow

fun <T> returning Flow<T> (class member)

→

KotlinFlow<T> F<T>(T value)

T boxed as in a generic method, where clauses kept; an out-of-bound T faults the stream. Also binds on an object, companion, top level, enum and value class (generic too).

Coroutines and Flow

fun <T> f(list: List<T>) (also Set, Map)

→

IReadOnlyList<T> F<T>(IReadOnlyList<T> list)

T is inferred, also with T : Enum<T> or several bounds; a Map key needs a non-null bound (K : Any); a returned wrapper element is yours to dispose.

Generics

fun <T> f() on a class, object, companion or sealed class

→

generic method T F<T>(T item)

T crosses as a handle, bounds become where; binds in List<T>, Box<T>, a lambda and a Flow parameter; interface, enum, value class, reified and a class Flow return skip, named.

Generics

a C#-implemented interface at an erased generic position (lambda, fun <T>, class<T>)

⇄

the interface itself, not its backing wrapper

→ returns the same instance · ← a Kotlin-backed value at T = IFoo materialises as IFoo

Generics· Interfaces, abstract and sealed

<T : Bound> constraint

→

where T : ...

T : Any is where T : notnull; Enum<T> is struct, global::System.Enum, also as an element; a stdlib bound (Comparable<T>) is dropped, so a T outside it throws; several bounds each list.

Generics

out T/in T variance

→

out T/in T

Generics

typealias

→

the underlying type

Use-site ? and a substituted generic alias (Box<Int>) bind as the written-out type on every route; a parameter nested in the RHS stays a named skip.

Generics

Collections

Kotlin

⇄

C#

Notes

Docs

List<T>

→

IReadOnlyList<T>

Eager copy at a property (settable when every component is wrappable), a return and a parameter; T may be a collection or an interface (Kotlin-backed elements arrive as wrappers).

Collections

MutableList<T>

→

IList<T>

Eager copy; a parameter never writes back to Kotlin. Settable as a property when every component is wrappable; T may itself be a collection.

Collections

Map<K,V>

→

IReadOnlyDictionary<K,V>

Eager copy at a property, return and parameter; a key or value may be a scalar, enum, handle, interface or collection. A nullable key (Map<K?, V>) has no route anywhere.

Collections

MutableMap<K,V>

→

IDictionary<K,V>

Eager copy; a parameter never writes back. Settable as a property when every component is wrappable; V may itself be a collection.

Collections

Set<T>

→

IReadOnlySet<T>

Eager copy at a property, return and parameter, over the same component set as Map, interfaces included (compared by reference).

Collections

MutableSet<T>

→

ISet<T>

Eager copy; a parameter never writes back. Settable as a property when every component is wrappable; T may itself be a collection.

Collections

T? (nullable collection reference)

→

T?

null crosses as IntPtr.Zero, independent of component nullability. Accessors, method and extension returns, suspend returns and Flow/MutableStateFlow elements carry it.

Collections

List<T>/Set<T>/Map<K,V> (always read-only, never Mutable*)

←

IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>, List<T> (→ List<T>); IReadOnlySet<T>, ISet<T>, HashSet<T> (→ Set<T>); IReadOnlyDictionary<K,V>, IDictionary<K,V>, Dictionary<K,V> (→ Map<K,V>)

Reverse only: an eager copy, so every C# shape binds read-only at every position. A mutable Kotlin type would let an add compile and change nothing. Map keys are never nullable.

Reverse collections

Functions and lambdas

Kotlin

⇄

C#

Notes

Docs

(T) -> R property (Kotlin → C#)

⇄

Func<>/Action<> property

Every owner, nullable and suspend too; a var takes any C# delegate. Nothing to dispose; the GC frees the Kotlin handle.

Lambdas and callbacks

function returning a lambda

⇄

Func<>/Action<> return

→ every owner, suspend and nullable too, nothing to dispose · ← a member of a C# implementation of a Kotlin interface may return a delegate Kotlin calls

Lambdas and callbacks

(T) -> R parameter (C# → Kotlin)

→

Func<>/Action<>

Per-call, arity 0-3; payload and result take Char, nullables, collections, value classes, T, Any and bound interfaces. Throwing lambda is catchable. 4+ parameters, Pair, IntArray refuse.

Lambdas and callbacks

stored callback parameter

→

IDisposable subscription

Subscribe and dispose; AddX(null!) throws ArgumentNullException. Payload and result as a lambda parameter, on a generic owner or sealed arm too. A refused payload refuses the pair, named.

Lambdas and callbacks

Kotlin lambda at a bound delegate parameter

←

Func<>/Action<>/Predicate<T>/Comparison<T>/Converter<,>/ package-declared delegate parameter

A bound method or constructor's delegate parameter takes a plain Kotlin lambda, Invoke arity up to 4. The C# delegate owns its lifetime. Async delegates are not bound yet.

Reverse delegates

interface parameter (C# → Kotlin)

→

C# implements I-prefixed type

add/remove paired, IDisposable, generic owners too; a var write lands on your object; lambda properties bind. An unsupported member refuses the pair, named; a throwing one is catchable.

Lambdas and callbacks

a bound C# interface in a lambda payload or result

⇄

the interface itself, Func<IFeedable, int>

The lambda receives the object you implemented, and a result you return is the one Kotlin reads; plain, suspend, stored, returned and property lambdas.

Lambdas and callbacks· The bridgeable subset

inline fun

→

regular method

Top-level declarations

inline fun <reified T>

→

generic method

Binds at top level, reified at its bound; on a member or with T : Enum<T> it skips, named.

Generics

value class (value/inline class)

→

underlying type / record struct

A readonly record struct; members bind, a failing receiver init throws. default(V) over a ByteArray, collection or interface is refused. A ByteArray or collection struct compares by content.

Value classes

value class at an ordinary position

→

the same record struct

Over any bound underlying (Char, sealed, Instant, a nullable or plain collection, interface), nullable too; Nick? stays apart from Nick(null). Inherited members never bind.

Value classes

value class as a List/Map/Set component

→

the same record struct, in IReadOnlyList<T>/IReadOnlyDictionary<K,V>/IReadOnlySet<T> etc.

Element, map key or map value, over any bound underlying; List<Nick?> keeps null apart from Nick(null). The wire carries the underlying per element, never the value class itself.

Value classes

generic value class (value class Sticker<T>(val value: T), phantom Decal<T> too)

→

readonly record struct Sticker<T>(T Value)

Binds at every position, over a List<T> too (Bundle<T>). Opt<T?> closed at Int is Opt<int?>. Sticker<*> is a method type parameter. Primary init runs on first crossing.

Value classes

same-name function overloads

⇄

method overload set

→ one natural C# overload set per container, the numbering hidden on the export symbol; a pair differing only in reference nullability fails generation · ← each member keeps its Kotlin name

Classes and objects· The bridgeable subset

function/method default parameters

→

one widened signature

Any subset settable by name; already-nullable params use shared KotlinOptional<T>. Cap of 8; a stored lambda default stays Kotlin-only; a real shorter overload wins over widening.

Classes and objects

Exception handling

Kotlin

⇄

C#

Notes

Docs

thrown exception

⇄

KotlinException

→ shared identity; mapped types keep BCL bases · ← throws surface as NugetManagedException; .NET type mapping, stack and causes are not built

Exceptions· The bridgeable subset

stack trace

→

KotlinStackTrace property

Kotlin frames only, host frames trimmed; a cause arrives only as InnerException, not as a Caused by: section.

Exceptions

e.cause

→

InnerException

The cause chain is preserved.

Exceptions

IllegalArgumentException etc., kotlinx.io.IOException, NullPointerException

→

ArgumentException, IOException, NullReferenceException etc.

Subclasses map too; catch (KotlinException) no longer catches them, filter e is IKotlinException. A null message reads the Kotlin type name.

Exceptions

Throwable/Throwable? in a property, return, suspend/Flow, List or Map value, lambda, interface slot or parameter

→

System.Exception?

Out: an unthrown exception, a snapshot per read. In: any exception arrives as a NugetManagedException, type name and message only. Not bound: Set element, Map key, MutableStateFlow.

Exceptions

Result<T> return

→

T, failure thrown, and bool TryX(..., out T, out Exception?)

TryX is false for a modelled failure and still throws for a thrown exception, so runCatching failures read as false. Result<Unit> is void; a property, parameter or element skips, named.

Exceptions

suspend function returning Result<T>

→

Task<T>

On every suspend owner; a failure faults the task with the mapped exception. There is no TryXAsync twin.

Coroutines and Flow

property getter / setter throws

→

propagated

Exceptions

constructor / init throws

→

propagated

Primary, secondary, a data class copy(), and generic and value class constructors.

Exceptions

Async and coroutines

Kotlin

⇄

C#

Notes

Docs

suspend fun

→

async/Task<T>

Two suspend overloads, on a class, an interface, a sealed base or arm, or at the top level, collapse into one C# overload set.

Coroutines and Flow

suspend fun returning T?

→

Task<T?>

A nullable string, object or primitive return carries its ? on both sides of the bridge.

Coroutines and Flow

suspend fun returning or taking a dependency-module type, top-level or on a class

→

the same mapped C# type

Admits like any other position; out-of-scope skips named via admit(...), never an undeclared spelling.

Coroutines and Flow

suspend fun returning a value class or enum

→

record struct / enum, unboxed or cast on completion

Null-guarded when nullable. A generic value class (Sticker<Int>) and a nullable-underlying one (Nick) bind too.

Coroutines and Flow

suspend fun returning an interface type, top-level or nested

⇄

Task<IFoo>

→ completes with the interface itself, nullable included · ← a value stored through a C# implementation comes back as the caller's own instance, not a fresh wrapper

Coroutines and Flow· Interfaces, abstract and sealed

suspend (T) -> R lambda property or return

⇄

Func<T, CancellationToken, Task<R>>

The same shape as a suspend lambda parameter; nullable, and a var takes an async delegate. A returned one is the same delegate.

Coroutines and Flow

suspend (T) -> R parameter (C# → Kotlin)

→

Func<T, CancellationToken, Task<R>>

Arity 0-3, constructors included; payloads as a lambda parameter. Kotlin owns the delegate until a GC round. A nullable lambda binds as Func<..>?; a Throwable result is a Task<Exception>.

Coroutines and Flow

plain (T) -> R parameter on a suspend or Flow-returning member

→

Action<>/Func<> on XAsync(..., CancellationToken) or the KotlinFlow<T> method

Every owner; kept alive until the Task ends or the enumeration ends, or until a returned KotlinFlow is disposed. Null throws ArgumentNullException.

Coroutines and Flow· Lambdas and callbacks

Flow<T> parameter (C# → Kotlin)

→

IAsyncEnumerable<T>

Primitive, String or own T elements, constructors included. Flow<T>? takes IAsyncEnumerable<T>?. One enumerator per collect. A StateFlow parameter refuses by name.

Coroutines and Flow

structured concurrency

→

honoured

Coroutines and Flow

coroutine cancellation

→

CancellationToken

Suspend lambdas included.

Coroutines and Flow

in-flight async drain

→

IAsyncDisposable

Graceful drain, owned by the first class in the chain, sealed base, or reachable interface that declares a suspend or Flow member. A refused-only owner gets neither scope nor interface.

Coroutines and Flow

Flow<T>

→

IAsyncEnumerable<T>

Cold streams at a method or property return; a var on a class, interface, sealed type or generic class takes any flow, one made in C# too. A slow reader stalls the producer one item ahead.

Coroutines and Flow

Flow family member of the top level, an object, a companion, an enum, a value class or a sync extension

→

static KotlinFlow<T> and its hot variants; this extension for an enum body (properties too) or an extension

Each enumeration runs on its own scope; a cold flow needs no Dispose(), a held Mutable* holder does. A var binds its setter: a set accessor, or Set<P>(...) on an enum body.

Coroutines and Flow

member types of an interface a C# class implements

→

the member as I<Name> declares it

Classes, collections, value classes, nullables, Instant, ByteArray, lambdas and any flow cross, any arity; a member's own <R> is erased to object?.

Interfaces, abstract and sealed

suspend and Flow members of an interface a C# class implements

→

Task<R> FooAsync(.., CancellationToken), KotlinFlow<T> Bar()

Kotlin awaits and collects them; cancellation reaches your token. KotlinFlow<T>.From makes a flow, Create a hot one; a Flow<Throwable> and a generic Flow<T> work.

Interfaces, abstract and sealed· Coroutines and Flow

Flow<T?> (nullable element)

→

KotlinFlow<T?>

A null emission is a genuine item, not the end of the stream, for an interface, String? or Int?.

Coroutines and Flow

Flow<T>? (nullable member)

→

KotlinFlow<T>?/Task<KotlinFlow<T>?>

null when the Kotlin member is null: a property, method or suspend fun, on every owner. A method is called twice (presence, then collection), so keep it pure.

Coroutines and Flow

Flow<T> of an interface element type (top-level or nested interface, class-method Flow positions only)

⇄

KotlinFlow<IFoo>

→ the element is spelled with the interface itself, nullable included · ← a value stored through a C# implementation comes back as the caller's own instance

Coroutines and Flow· Interfaces, abstract and sealed

StateFlow<T>/MutableStateFlow<T>

→

KotlinStateFlow<T>/KotlinMutableStateFlow<T> (.Value + IAsyncEnumerable<T>)

Hot, always current. A declared MutableStateFlow<T> (not narrowed to StateFlow<T>) gets settable .Value, CompareAndSet and Update; nullable and class suspend returns included.

Coroutines and Flow

StateFlow<T?>/StateFlow<T>?

→

KotlinStateFlow<T?>/KotlinStateFlow<T>?

A nullable element and a nullable member, independently or combined.

Coroutines and Flow

StateFlow<T> bound to XAML

→

flow.AsNotifying(): KotlinStateFlowObservable<T> (INotifyPropertyChanged, IDisposable)

Opt-in; Value raises PropertyChanged on the captured context. Dispose it, or it keeps delivering.

Coroutines and Flow

SharedFlow<T>/MutableSharedFlow<T>

→

KotlinSharedFlow<T>/KotlinMutableSharedFlow<T>

ReplayCache; a declared MutableSharedFlow<T> of a settable element (value classes, nullable collections too) adds EmitAsync, TryEmit, SubscriptionCount. Never ends: bound it.

Coroutines and Flow

StateFlow<E>/Flow<E> of an enum or value class element (nullable included)

→

KotlinStateFlow<E>/KotlinFlow<E>

.Value and await foreach return the C# enum or record struct. A MutableStateFlow of an enum (nullable too), or of a value class over any bound underlying (Char and sealed too), is settable.

Coroutines and Flow

suspend fun returning StateFlow<T>

→

Task<KotlinStateFlow<T>>

Stays a Task. Class, top-level and static owners; nullable element or holder binds as T?. A List/Set/Map element binds read-only; a MutableStateFlow of one, nullable too, is settable.

Coroutines and Flow

suspend fun returning Flow<T>

→

Task<KotlinFlow<T>>

Await acquisition once; dispose the acquired holder separately from its owner.

Coroutines and Flow

Flow<E>/StateFlow<E> as the type argument of a generic class (Box<Flow<E>>)

→

Box<KotlinFlow<E>>/Box<KotlinStateFlow<E>>

box.Value mints a new holder per read that owns its collection scope; dispose it. Read-only: a MutableStateFlow argument is not settable.

Coroutines and Flow

List/Set/Map parameter (nullable too) on a Flow/StateFlow-returning or suspend member

→

the collection type, never IntPtr

null crosses as Kotlin null. Converted eagerly, before the coroutine starts, so the handle is call-scoped; .Value re-marshals per read. Any other generic parameter skips, named.

Coroutines and Flow

List/Set/Map return on a suspend member

→

Task<IReadOnlyList<T>>/Task<IReadOnlySet<T>>/Task<IReadOnlyDictionary<K, V>>

Spelled and read as the property route spells the same type; ? and a sealed-base element bind. A closed Box<T> binds; every other generic suspend return skips, named.

Coroutines and Flow

A class, object, sealed base, or sealed subclass parameter on a Flow/StateFlow-returning or suspend member

→

the mapped C# type, spelled exactly as the return position on the same member, passed as x._handle

Nullable binds as T?, null crossing as no handle.

Coroutines and Flow

An enum parameter on a Flow/StateFlow-returning or suspend member

→

the mapped C# enum type

Bound by ordinal: (int)x in C#, Q.entries[x] in Kotlin.

Coroutines and Flow

Instant, Duration, Uuid, ByteArray, a plain interface or a value class as a suspend/Flow parameter, result or element

→

DateTimeOffset, TimeSpan, Guid, byte[], the interface, the record struct

Nullable too. A default(V) over a ByteArray, collection or interface throws ArgumentException; init runs in Kotlin.

Coroutines and Flow

suspend extension or value-class member on a generic, collection, interface, enum, Uuid, Instant, Duration, ByteArray or value class receiver

→

this extension, or an instance method on the record struct

The receiver is a leading parameter on its own wire, nullable too; the same types bind as a suspend parameter.

Coroutines and Flow

MutableStateFlow/MutableSharedFlow of Instant, Duration, Uuid or a wire value class

→

KotlinMutableStateFlow<T>/KotlinMutableSharedFlow<T>

Writes cross as ticks or text, or through the value class box pair. An awaited StateFlow of one reads too.

Coroutines and Flow

A nullable primitive/Char/String parameter on a Flow/StateFlow-returning or suspend member

→

int?/char?/bool?/string?

Coroutines and Flow

A default argument on a scalar/String /enum/handle parameter of a Flow/StateFlow-returning or suspend member

→

one widened signature, or required-but-nullable

A non-null collection default stays required, a nullable one widens to KotlinOptional<...?>; a same-name shorter suspend overload keeps it required-but-nullable rather than risk CS0121.

Coroutines and Flow

List/Set/Map element on a Flow/StateFlow property or method return

→

KotlinFlow<IReadOnlyList<T>>/KotlinStateFlow<IReadOnlyList<T>>/IReadOnlySet<T>/IReadOnlyDictionary<K, V>

Spelled and read as every other route spells the same Kotlin type. A sealed-base component binds on Flow; a nullable collection element binds too; every other generic element skips, named.

Coroutines and Flow

suspend fun returning Unit/T

←

Task/Task<T>, instance or static

A faulted task is a catchable NugetManagedException carrying the original .NET type. A trailing Async is stripped unless a sibling owns that name; a single CancellationToken parameter elides.

Instance members

IAsyncEnumerable<T> method return, instance or static

←

Flow<T>

Pull-based and cold: the C# method runs at collect, not at the call that returned the Flow. A mid-stream throw arrives as NugetManagedException.

Instance members

Documentation comments

Kotlin

⇄

C#

Notes

Docs

KDoc (/** ... */, @param, @return, @throws, @property, @constructor, @see, @suppress)

→

/// XML doc comment (<summary>, <remarks>, <param>, <returns>, <exception cref>, <seealso cref>)

Every generated declaration mirroring a documented one carries it. The first paragraph is the summary, the rest the remarks; @param is all or none. The reverse direction is not built.

Documentation comments

Runtime and packaging

Kotlin

⇄

C#

Notes

Docs

the fixed nuget_* ABI (NugetHandles, NugetError, the scalar wrap/unwrap, collection, callback and coroutine exports, the .NET ticks conversions, NugetCSharpBridge, nuget_runtime_version)

→

the same DllImports in Interop.cs, plus one more

Ships once as a versioned nuget-runtime library the plugin adds and exports, so the generator emits only per-declaration code. The C# side is still source-shipped.

Architecture

Two Kotlin-built NuGet packages in one .NET project

→

package-local generated APIs and native runtimes

Share exception and KotlinOptional<T> identity; native names and handle state stay package-specific.

Exceptions· Publishing Kotlin to C#

the consumer's Kotlin version

⇸

no C# projection

Any Kotlin from 2.4.0 up works; below it the build fails at configuration time, above the last tested version it warns.

Prerequisites

nuget { targetFramework }

→

the package layout contentFiles/cs/<tfm>/

One floor netX.0 (X >= 10), default net10.0; a consumer below it fails restore with NU1202 instead of silently compiling no generated C#.

NuGet DSL

every forward @CName export symbol

→

the matching DllImport EntryPoint

Always the library, then the package relative to rootPackage, then the declaration's own name, so same-named declarations in different packages never collide.

Architecture

every declaration generated by bind {}

←

the bound C# package's API

Marked @ExperimentalNugetBindingApi, an error until the module opts in with @OptIn or optIn.add(...); reverse may change in 1.x.

Consuming C# in Kotlin

Last modified: 03 November 2026