kotlin-native-nuget Help

Extensions

Kotlin extension functions and properties don't have a native C# analog (C# has extension methods but not extension properties), so both map to static methods, grouped by source file the same way as any other top-level declaration (see Top-level declarations). Extension functions render as true C# extension methods (this parameter); extension properties render as ordinary static getter methods, since C# can't declare an extension property.

Kotlin

C#

Notes

extension function

static method

true C# extension method (this parameter)

extension property

static accessor

see ADR-013

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

matching C# return type

same cascade as a class-method return via the shared plan, see Return marshalling below and Classes and objects

Kotlin

Extension functions on String, from test-library/src/nativeMain/kotlin/.../StringExtensions.kt:

fun String.meowify(): String = "$this meow!" fun String.isPurring(): Boolean = lowercase().contains("purr") val String.wordCount: Int get() = trim().split("\\s+".toRegex()).size

Extension functions and properties on Cat, from test-library/src/nativeMain/kotlin/.../cat/CatExtensions.kt:

fun Cat.sayName(): String = "My name is ${this.name}" fun Cat.greetWith(greeting: String): String = "$greeting, ${this.name}!" val Cat.isKitten: Boolean get() = lives > 7 val Cat.label: String get() = "${name} (${mood.name.lowercase()})"

Generated C#

From Interop.cs, CatExtensions static class. SayName is a genuine C# extension method (this Cat cat); GetIsKitten is the extension-property accessor, named with a Get prefix since C# has no extension-property syntax:

public static partial class CatExtensions { [DllImport("sample", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cat_sayName")] private static extern IntPtr Native_SayName(IntPtr handle); public static string SayName(this Cat cat) => Marshal.PtrToStringUTF8(Native_SayName(cat._handle))!; [DllImport("sample", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cat_greetWith")] private static extern IntPtr Native_GreetWith(IntPtr handle, string greeting); public static string GreetWith(this Cat cat, string greeting) => Marshal.PtrToStringUTF8(Native_GreetWith(cat._handle, greeting))!; [DllImport("sample", CallingConvention = CallingConvention.Cdecl, EntryPoint = "cat_get_isKitten")] private static extern bool Native_GetIsKitten(IntPtr handle, out IntPtr error); public static bool GetIsKitten(this Cat cat) { bool result = Native_GetIsKitten(cat._handle, out IntPtr error); if (error != IntPtr.Zero) { throw NugetErrorNative.BuildException(error); } return result; } }

Using it from C#

Extension functions, from IntegrationTests/ExtensionFunctionTests.cs:

[Fact] public void String_Meowify_AppendsMeow() { Assert.Equal("Oreo meow!", "Oreo".Meowify()); } [Fact] public void Cat_SayName() { using var cat = new Cat("Oreo", 9); Assert.Equal("My name is Oreo", cat.SayName()); } [Fact] public void Cat_GreetWith() { using var cat = new Cat("Oreo", 9); Assert.Equal("Hello, Oreo!", cat.GreetWith("Hello")); }

Extension properties, from IntegrationTests/ExtensionPropertyTests.cs, called as a method, not a property, on the C# side:

[Fact] public void Cat_GetIsKitten_ReturnsTrueForNewCatWithNineLives() { using var cat = new Cat("Oreo", 9); Assert.True(cat.GetIsKitten()); } [Fact] public void Cat_GetLabel_ReturnsNameWithMood() { using var cat = new Cat("Oreo", 9); Assert.Equal("Oreo (sleepy)", cat.GetLabel()); } [Fact] public void String_GetWordCount_ReturnsTwoForTwoWords() { Assert.Equal(2, "hello world".GetWordCount()); }

Return marshalling

An extension function's return goes through the same marshalling cascade a class-method return does (see Method returns in Classes and objects): object, nullable object, collection, nullable String, and nullable primitive (single call, bool has-value + valueOut out-parameter, not the property getter's two-call pattern).

From test-library/src/nativeMain/kotlin/.../cat/CatExtensions.kt. The receiver is Toy, not Cat: an extension can't be distinguished from a member of the same name on the same receiver, so exercising the extension-function export path needs a receiver with no colliding member:

/** Object return (converting), extension-function position. Always non-null. */ fun Toy.findOwner(): Cat = Cat(name, color.length) /** Nullable object return, extension-function position. Non-null only for the "Gray" toys. */ fun Toy.maybeOwner(): Cat? = if (color == "Gray") Cat(name, name.length) else null /** Collection return, converting element, extension-function position. */ fun Toy.tags(): List<String> = listOf("$name-tag", "$color-tag") /** Collection return, non-converting element, extension-function position. */ fun Toy.scores(): List<Int> = listOf(name.length, color.length) /** Nullable String return, extension-function position. Non-null only for the "Gray" toys. */ fun Toy.alias(): String? = if (color == "Gray") "$name (aka Grey Ghost)" else null /** Nullable primitive return, extension-function position. Non-null only for the "Gray" toys. */ fun Toy.ageInMonths(): Int? = if (color == "Gray") name.length * 12 else null

Generated C#, from Interop.cs, ToyExtensions:

public static Cat FindOwner(this Toy toy) { IntPtr nativeResult = Native_FindOwner(toy._handle, out IntPtr error); if (error != IntPtr.Zero) { throw NugetErrorNative.BuildException(error); } return new Cat(nativeResult); } public static Cat? MaybeOwner(this Toy toy) { IntPtr nativeResult = Native_MaybeOwner(toy._handle, out IntPtr error); if (error != IntPtr.Zero) { throw NugetErrorNative.BuildException(error); } return nativeResult == IntPtr.Zero ? null : new Cat(nativeResult); } public static int? AgeInMonths(this Toy toy) { bool hasValue = Native_AgeInMonths(toy._handle, out int value, out IntPtr error); if (error != IntPtr.Zero) { throw NugetErrorNative.BuildException(error); } return hasValue ? value : null; }

From IntegrationTests/MethodReturnMarshallingTests.cs:

[Fact] public void Toy_MaybeOwner_NonNullForGrayToy() { var mouse = new Toy("Mouse", "Gray"); using Cat? owner = mouse.MaybeOwner(); Assert.NotNull(owner); Assert.Equal("Mouse", owner!.Name); } [Fact] public void Toy_Tags_ReturnsMarshalledStringElements() { var mouse = new Toy("Mouse", "Gray"); IReadOnlyList<string> tags = mouse.Tags(); Assert.Equal(new List<string> { "Mouse-tag", "Gray-tag" }, tags); }
Last modified: 21 July 2026