A Kotlin object becomes a C# static class: no instance, no constructor, its members reached directly through the type. A companion object's members land as static members directly on the enclosing C# class, not on a separate Companion type. A data object nested inside a sealed class becomes a sealed subclass instead; see Interfaces, abstract and sealed classes. An object can also own a nested type the same way a class can; see Nested types.
For this declaration in CatRegistry.kt:
object CatRegistry {
private val cats: MutableList<String> = mutableListOf()
fun register(name: String) {
cats.add(name)
}
fun count(): Int = cats.size
}
Call it from C# through the type; there is nothing to construct:
CatRegistry.Register("Oreo");
CatRegistry.Register("Mylo");
int count = CatRegistry.Count(); // 2
Object methods are PascalCased and their returns marshalled exactly like class methods; see Classes and objects.
Companion objects
A companion's members are static members on the enclosing class itself:
class Cat(
name: String,
val lives: Int = 9,
) : Animal(name) {
companion object {
const val SPECIES: String = "Felis catus"
val defaultBreed: String = "Domestic Shorthair"
fun fromName(name: String): Cat = Cat(name)
}
}
string species = Cat.Species; // "Felis catus"
string breed = Cat.DefaultBreed; // "Domestic Shorthair"
using var cat = Cat.FromName("Whiskers");
There is no separate Cat.Companion class in the generated output.
Method overloads
Two or more same-named members on an object or a companion object generate one natural C# overload set, resolved by parameter type like any other C# overload, including an Int overload beside an enum overload:
object Parlour {
fun rate(stars: Int): String = "the parlour is rated $stars"
fun rate(coat: Coat): String = "the parlour grooms ${coat.name.lowercase()} coats"
}
string byStars = Parlour.Rate(10); // "the parlour is rated 10"
string byCoat = Parlour.Rate(Coat.Tuxedo); // "the parlour grooms tuxedo coats"
See Method overloads in Classes and objects for the same rule on class methods, top-level functions, and extension functions.
Method default parameters
A trailing run of defaulted parameters on an object or companion member generates one overload per omitted trailing default, each using the Kotlin default for the parameters it drops:
object Kibble {
fun scoop(flavour: String, scoops: Int = 2): String = "$scoops scoops of $flavour"
}
string oneFlavour = Kibble.Scoop("tuna"); // "2 scoops of tuna"; scoops defaults to 2
Only a trailing run of defaults gets an omitting overload: a defaulted parameter followed by a non-defaulted one does not, so pass it explicitly. A trailing default the bridge cannot carry costs only that arity.
object Switchboard {
fun patch(level: Int = 0, events: Flow<Int>? = null): String =
"patch $level/${events?.toString() ?: "-"}"
}