The bridgeable subset
Not every public C# type or member crosses into Kotlin. This page is the support matrix: what binds, what shape it takes in Kotlin, and what to do when a build warning says a member was skipped.
What binds
C# construct | Binds as | Notes |
|---|---|---|
Public class with at least one bridgeable member | Kotlin class, handle-backed | |
Public static class | Kotlin | |
Public, top-level, non-generic interface with at least one admissible member | Kotlin | see Interfaces below |
Public, top-level, default- | standalone Kotlin | see Enums below |
Bridgeable struct (a state-covering constructor, or public settable fields/auto-properties) | immutable Kotlin | see C# structs |
Generic class reached through a closed instantiation ( | real Kotlin generic class, one per instantiation | see Generic types |
| same rules as an ordinary class; not a distinct construct in metadata | |
| not bound | any member referencing one is skipped |
Nested type, public or not | not bound | only top-level public types are candidates |
Open generic type parameter | member skipped | |
Generic interface ( | not bound |
Enums
A supported enum becomes a standalone Kotlin enum class. Member names convert to SCREAMING_SNAKE_CASE. Enum values cross the ABI as their ordinal Int, so a constructor argument, property, or return typed with the enum uses the generated Kotlin type directly:
[Flags]enums, a non-intunderlying type, and explicit, sparse, negative, or aliased values don't bind.A nullable enum, a nested enum, and an enum as a collection element (
List<CatMood>) aren't supported yet. An enum used as the type argument of a bound generic class (Box<CatMood>) is supported.An unsupported enum is skipped, named on a build warning (see Build warnings below), not silently dropped.
Interfaces
A public, top-level, non-generic interface with at least one admissible member becomes a plain Kotlin interface:
The interface itself carries no handle. A value arriving at an interface-typed position (a parameter, a return, or a property) is wrapped in a generated class that implements the interface and dispatches every member back to the real C# object, whether or not that object's own runtime class is itself bound.
Interface inheritance binds too: interface ITagged : IFeedable becomes interface ITagged : IFeedable in Kotlin, and dispatches IFeedable's inherited members correctly, as long as IFeedable is itself admissible and bound. If the base isn't, the derived interface binds with only its own declared members. A bound class only declares an implemented interface as a Kotlin supertype when every interface member has an identically-signed public member on the class; a C# explicit interface implementation is invisible in metadata, so such a class omits the supertype entirely.
Implementing a C#-declared interface in Kotlin
A plain Kotlin class implementing IFeedable, with no generated wrapper machinery of its own, can be passed back at any IFeedable-typed parameter or property:
Nothing needs to be registered or generated ahead of time: the first time a Goat crosses into C#, Kotlin mints a small C#-side bridge for it automatically. From C#, the result is indistinguishable from a bound IFeedable:
Only a limited set of member shapes can cross this bridge: val/var property getters and setters, and methods of arity 0-2 returning Unit, a primitive, Boolean, an enum, String, String?, a bound-object handle, or a bound interface (nullable included for the last two). A struct-typed member, a generic-instance-typed member, a collection-typed member (List/Map/Set), or a Task-returning member is out of scope and named on a build warning instead of bridged; a class with one of those members can't be passed at that interface position.
A Kotlin implementation of a derived interface binds too: Tabby : ITagged (where ITagged : IFeedable) can be passed at either an ITagged-typed or an IFeedable-typed C# parameter. A C# object handed into a Kotlin interface-slot parameter transfers ownership to Kotlin, so an implementation can safely store it past the call that delivered it, the same as an ordinary reverse-bound parameter.
Exposing a C# interface in your own Kotlin API
A bound C# interface can also appear at an ordinary forward parameter or return, on a Kotlin author's own public API, using the original C# type:
The generated C# signature names the real Test.Menagerie.IFeedable, not a re-projected duplicate, so it composes with the rest of that package's own API. A parameter is an ordinary handle transfer, so a C#-implemented value passed in and returned back out is the same managed instance, and a Kotlin implementation passed in resolves to the original Kotlin object:
This works even for a consumer that never directly depends on the package IFeedable came from: your library's own package metadata declares it as a transitive dependency.
Supported today: a non-nullable bound interface at an ordinary parameter or a method/top-level function return. Not supported, each named on a build warning instead of silently skipped:
A nullable interface-typed position (
IFeedable?).An interface-typed property.
An interface as a collection component (
List<IFeedable>).A bound class at a forward position (
fun sanctuary(): Sanctuary): not attempted at all.A return of a Kotlin implementation of an interface that has no C#-side bridge for it (see Implementing a C#-declared interface in Kotlin above); the same interface still works as a parameter.
An interface stays unavailable at a forward position at all if it, or a base it extends, has a member that references an internal bound class: a public interface referencing an internal type wouldn't compile.
Interface limitations
Generic interfaces (
IBox<T>) don't bind.A
static,static abstract, orstatic virtualinterface member is skipped.A default interface method is skipped.
An interface with zero admissible members is skipped entirely.
No downcast from an interface-typed value to a concrete bound class (see the note above).
Methods and properties
Static and instance methods and properties bind on classes the same way; see Instance members and Static classes and methods. Struct members bind under their own rules; see C# structs.
async/Task-returning methods (Task,Task<T>,ValueTask,ValueTask<T>,IAsyncEnumerable<T>) don't bind. This applies on classes and interfaces alike.An indexer (
this[int]) doesn't bind, on a class or an interface.An
eventmember doesn't bind, on a class or an interface.
Overload sets
Bridgeable C# method and constructor overloads keep their name and parameter types as ordinary Kotlin overloads. An unsupported member is diagnosed independently and doesn't hide supported siblings. If two different C# signatures would collapse to the same Kotlin scope, name, and ordered parameter types, generation fails with an error naming both signatures instead of producing a broken build.
Types that cross the wire
Beyond primitives and string (see Primitives and strings; the reverse direction has no nullable value types and no Instant/Duration/Uuid mapping), a member's type binds as:
a bound, non-static, non-value-type, non-
ref structclass from the current extraction: an opaque handle, see Objects and handlesa bridgeable struct: a decomposed, handle-free
data class, see C# structsa supported enum: an ordinal
Intconverted to and from a Kotlinenum class, see Enums abovea bound, admissible interface: a Kotlin
interface, see Interfaces above
Everything else, arrays, collections, delegates, dynamic, object, open generics, and a generic instantiation of a definition outside the bound assemblies (List<int>), doesn't bind. A closed instantiation of a bound generic class is the one generic shape that does; see Generic types. System.String is the only external (out-of-assembly) reference type recognized: a type from a namespace you didn't include(), from an assembly outside the extraction run, or from an undeclared NuGet dependency is treated the same as an unsupported type, even though the reader can see and name it.
Exceptions
A C# exception thrown while dispatching a bound member is caught at the crossing and reaches Kotlin as a catchable NugetManagedException instead of terminating the host:
NugetManagedException carries the .NET type's full name and its Message, verbatim, and nothing else: no stack trace and no InnerException /cause chain today.
Unsupported members show up as build warnings
Every member the reader excludes, and every enum, struct, or interface it can't bind, is recorded with its type, member, and reason, and logged as a Gradle build warning when nugetGenerateBindings runs. If a method you expected in Kotlin is missing, search the build log for it by name rather than guessing why. An unsupported member never hides its siblings: the rest of an overload set, or the rest of a class, still binds. For registration-time failures (a stale build, a contract mismatch at process startup) rather than an extraction-time skip, see Registration diagnostics.