Kotlin is like C#

Compare the syntax of Kotlin vs C#. Don't take language likeness comparison too seriously.

Fixes, improvents and additions are welcome. Open an issue or a pull request.

BASICS Hello World Kotlin println("Hello, world!") C# public void Main() { Console.WriteLine("Hello, world!"); } Variables and Constants Kotlin var myVariable = 42 myVariable = 50 val myConstant = 42 C# var myVariable = 42; myVariable = 50; // C# doesn't have local runtime constants Explicit Types Kotlin val explicitDouble: Double = 70.0 C# double explicitDouble = 70.0; Type Coercion Kotlin val label = "The width is " val width = 94 val widthLabel = label + width // The width is 94 C# var label = "The width is "; var width = 94; var widthLabel = label + width; // The width is 94 Compile Time Constants Kotlin const val SYSTEM_DEPRECATED: String = "System is deprecated" C# const string SYSTEM_DEPRECATED = "System is deprecated"; String Interpolation Kotlin val apples = 3 val oranges = 5 val fruitSummary = "I have ${apples + oranges} " + "pieces of fruit." C# var apples = 3; var oranges = 5; var fruitSummary = $"I have {apples + oranges} " + "pieces of fruit."; If Expression / Statement Kotlin val age = 42 if (age < 10) { println("You're too young to watch this movie") } else if (age < 13) { println("You can watch this movie with a parent") } else { println("You can watch this movie") } C# var age = 42; if (age < 10) { Console.WriteLine("You're too young to watch this movie"); } else if (age < 13) { Console.WriteLine("You can watch this movie with a parent"); } else { Console.WriteLine("You can watch this movie"); } Conditionals Kotlin // if is an expression, so ternary operation not needed val loaded = true val status = if (loaded) "Ready" else "Loading..." // "Ready C# var loaded = true; var status = loaded ? "Ready" : "Loading..."; // "Ready

FUNCTIONS Functions Kotlin fun greet(name: String, day: String): String { return "Hello $name, today is $day." } val text = greet("Bob", "Tuesday") // Hello Bob, today is Tuesday C# string Greet(string name, string day) { return $"Hello {name}, today is {day}"; } var text = Greet("Bob", "Tuesday"); // Hello Bob, today is Tuesday Single Expression Functions Kotlin // Single expression functions can be without braces and return type fun double(value: Int) = value * 2 val result = double(4) // 8 C# // Single expression functions can be without braces int Double(int value) => value * 2; var result = Double(4); // 8 Named Arguments Kotlin fun area(width: Int, height: Int) = width * height var result = area(width = 2, height = 3) // This is also possible with named arguments result = area(2, height = 2) result = area(height = 3, width = 2) C# int Area(int width, int height) => width * height; var result = Area(width: 2, height: 3); // This is also possible with named arguments result = Area(2, height: 2); result = Area(height: 3, width: 2); Default Arguments Kotlin fun displayGreeting(message: String, name: String = "Guest") { println("Hello $name, $message") } displayGreeting("welcome!") // Hello Guest, welcome! C# void DisplayGreeting(string message, string name = "Guest") { Console.WriteLine($"Hello {name}, {message}"); } DisplayGreeting("welcome!"); // Hello Guest, welcome! Variable Number of Arguments Kotlin fun sumOf(vararg numbers: Int): Int { var sum = 0 for (number in numbers) { sum += number } return sum } val sum = sumOf(42, 597, 12) // sumOf() can also be written in a shorter way: fun sumOf(vararg numbers: Int) = numbers.sum() C# int SumOf(params int[] numbers) { var sum = 0; foreach (var number in numbers) sum += number; return sum; } var sum = SumOf(42, 597, 12); // SumOf() can also be written in a shorter way: int SumOf(params int[] numbers) => numbers.Sum(); Lambdas Kotlin fun containsEven(numbers: List<Int>) = numbers.any { it % 2 == 0 } C# bool ContainsEven(List<int> numbers) => numbers.Any(e => e % 2 == 0); Higher-Order Functions - Return a Function Kotlin fun makeIncrementer(): (Int) -> Int { val addOne = fun(number: Int): Int { return 1 + number } return addOne } val increment = makeIncrementer() val result = increment(7) // makeIncrementer can also be written in a shorter way: fun makeIncrementer() = fun(number: Int) = 1 + number C# Func<int, int> MakeIncrementer() { int addOne(int number) => 1 + number; return addOne; } var increment = MakeIncrementer(); var result = increment(7); // MakeIncrementer can also be written in a shorter way: Func<int, int> MakeIncrementer() => i => 1 + i; HOF - Function as Parameter Kotlin fun transform(initial: String, f: (String) -> String) = f(initial) val result = transform("hello", { x -> x.toUpperCase() }) // Trailing lambda can be placed outside the parentheses val result2 = transform("hello") { x -> x.toUpperCase() } // HELLO C# string Transform(string initial, Func<string, string> f) => f(initial); var result = Transform("hello", x => x.ToUpper()); // HELLO Tuple Return Kotlin // Kotlin doesn't have tuples, use data classes data class GasPrices(val a: Double, val b: Double, val c: Double) fun getGasPrices() = GasPrices(3.59, 3.69, 3.79) val prices = getGasPrices(); val (a, b, c) = getGasPrices(); C# (double a, double b, double c) GetGasPrices() => (3.59, 3.69, 3.79); var result = GetGasPrices(); var (a, b, c) = GetGasPrices();

COLLECTIONS Arrays Kotlin val shoppingList = arrayOf("catfish", "water", "tulips", "blue paint") shoppingList[1] = "bottle of water" C# var shoppingList = new[] { "catfish", "water", "tulips", "blue paint" }; shoppingList[1] = "bottle of water"; Lists Kotlin val shoppingList = listOf("catfish", "water", "tulips", "blue paint") val shoppingListMutable = mutableListOf("catfish", "water", "tulips", "blue paint") shoppingListMutable[1] = "bottle of water" shoppingListMutable.add("bucket") C# IReadOnlyList<string> shoppingList = new List<string> { "catfish", "water", "tulips", "blue paint" }; var shoppingListMutable = new List<string> { "catfish", "water", "tulips", "blue paint" }; shoppingListMutable[1] = "bottle of water"; shoppingListMutable.Add("bucket"); Maps / Dictionaries Kotlin val occupations = mapOf( "Malcolm" to "Captain", "Kaylee" to "Mechanic" ) val occupationsMutable = mutableMapOf( "Malcolm" to "Captain", "Kaylee" to "Mechanic" ) occupationsMutable["Jayne"] = "Public Relations" occupationsMutable.put("Rick", "Navigation") C# IReadOnlyDictionary<string,string> occupations = new Dictionary<string, string> { ["Malcolm"] = "Captain", ["Kaylee"] = "Mechanic" }; var occupationsMutable = new Dictionary<string, string> { ["Malcolm"] = "Captain", ["Kaylee"] = "Mechanic" }; occupationsMutable["Jayne"] = "Public Relations"; occupationsMutable.Add("Rick", "Navigation"); Empty Collections Kotlin val emptyList = mutableListOf<String>() val emptyMap = mutableMapOf<String, Float>() // read-only empty list val empty = emptyList<String>() C# var emptyList = new List<string>(); var emptyDictionary = new Dictionary<string, float>(); // read-only empty list var empty = Enumerable.Empty<string>(); ForEach Kotlin val names = arrayOf("Anna", "Alex", "Brian", "Jack") val count = names.count() for (name in names) { println("Person is called $name") } names.forEach { println("Person is called $it") } // Person is called Anna // Person is called Alex // Person is called Brian // Person is called Jack C# var names = new List<string> { "Anna", "Alex", "Brian", "Jack" }; foreach (var name in names) { Console.WriteLine($"Person is called {name}"); } names.ForEach(name => Console.WriteLine($"Person is called {name}")); // Person is called Anna // Person is called Alex // Person is called Brian // Person is called Jack Range Operator Kotlin val names = arrayOf("Anna", "Alex", "Brian", "Jack") val count = names.count() for (i in 0..count - 1) { println("Person ${i + 1} is called ${names[i]}") } // Person 1 is called Anna // Person 2 is called Alex // Person 3 is called Brian // Person 4 is called Jack C# var names = new[] { "Anna", "Alex", "Brian", "Jack" }; var count = names.Count(); foreach (var i in Enumerable.Range(0, count)) { Console.WriteLine($"Person {i + 1} is called {names[i]}"); } // Person 1 is called Anna // Person 2 is called Alex // Person 3 is called Brian // Person 4 is called Jack Inclusive Range Operator Kotlin for (index in 1..5) { println("$index times 5 is ${index * 5}") } // 1 times 5 is 5 // 2 times 5 is 10 // 3 times 5 is 15 // 4 times 5 is 20 // 5 times 5 is 25 C# foreach (var index in Enumerable.Range(1, 5)) { Console.WriteLine($"{index} times 5 is {index * 5}"); } // 1 times 5 is 5 // 2 times 5 is 10 // 3 times 5 is 15 // 4 times 5 is 20 // 5 times 5 is 25 Collection Range and Index Kotlin val names = arrayOf("Anna", "Alex", "Brian", "Jill", "Jack") val count = names.count() for (name in names.slice(1 until count - 1)) { println("Person is called $name") } // Person is called Alex // Person is called Brian // Person is called Jill C# var names = new[] { "Anna", "Alex", "Brian", "Jill", "Jack" }; foreach (var name in names[1..^1]) { Console.WriteLine($"Person is called {name}"); } // Person is called Alex // Person is called Brian // Person is called Jill

COLLECTION OPERATIONS / LINQ Map Kotlin val numbers = listOf(20, 19, 7, 12) val multiplied = numbers.map { 3 * it } // [ 60, 57, 21, 36 ] C# var numbers = new[] { 20, 19, 7, 12 }; var multiplied = numbers.Select(e => 3 * e); // [ 60, 57, 21, 36 ] Sort Kotlin val ordered = listOf(1, 5, 3, 12, 2).sorted() // [ 1, 2, 3, 5, 12 ] C# var ordered = new[] { 1, 5, 3, 12, 2 }.OrderBy(i => i); // [ 1, 2, 3, 5, 12 ] Filter / GroupBy / Average Kotlin var datas = listOf( SensorData(1, "A", 2.89), SensorData(2, "B", 12.01), SensorData(3, "B", 11.89), SensorData(4, "A", 3.11), SensorData(5, "A", -456.0) ) var avgs = datas .filter { it.value > -50.0 } .groupBy { it.location } .map { g -> Location(g.key, g.value.map { it.value }.average()) } // (location=A, value=3.0) // (location=B, value=11.95) C# var datas = new List<SensorData> { new SensorData { Id = 1, Location = "A", Value = 2.89 }, new SensorData { Id = 2, Location = "B", Value = 12.01 }, new SensorData { Id = 3, Location = "B", Value = 11.89 }, new SensorData { Id = 4, Location = "A", Value = 3.11 }, new SensorData { Id = 5, Location = "A", Value = -456.0 } }; var avgs = datas .Where(e => e.Value > -50.0) .GroupBy(e => e.Location) .Select(g => new { Location = g.Key, Value = g.Average(e => e.Value) }); // { Location = A, Value = 3.0 } // { Location = B, Value = 11.95 } Sequences Kotlin // Sequence is lazy val seq = sequenceOf(1, 2, 3, 4) .filter { println("Filter $it, "); it % 2 == 1 } .map { println("Map $it, "); it * 2 } // Computations are evaluated during terminal operation val items = seq.toList() // Filter 1, // Map 1, // Filter 2, // Filter 3, // Map 3, // Filter 4, // List is not lazy, so functions are evaluated immediately val items2 = listOf(1, 2, 3, 4) .filter { println("Filter $it, "); it % 2 == 1 } .map { println("Map $it, "); it * 2 } // Filter 1, // Filter 2, // Filter 3, // Filter 4, // Map 1, // Map 3, C# // LINQ is lazy, so no need to use other collection types var query = new List { 1, 2, 3, 4 } .Where(i => { Console.WriteLine($"Filter {i}, "); return i % 2 == 1; }).Select(i => { Console.WriteLine($"Map {i}, "); return i * 2; }); var items = query.ToList(); //Filter 1, //Map 1, //Filter 2, //Filter 3, //Map 3, //Filter 4,

CLASSES Declaration Kotlin class Shape { var numberOfSides = 0 fun simpleDescription() = "A shape with $numberOfSides sides." } C# class Shape { public int NumberOfSides { get; set; } public string SimpleDescription() => $"A shape with {NumberOfSides} sides."; } Usage Kotlin var shape = Shape() shape.numberOfSides = 7 var shapeDescription = shape.simpleDescription() // A shape with 7 sides. C# var shape = new Shape(); shape.NumberOfSides = 7; var shapeDescription = shape.SimpleDescription(); // A shape with 7 sides. Subclass Kotlin open class NamedShape(val name: String) { var numberOfSides = 0 open fun simpleDescription() = "A shape with $numberOfSides sides." } class Square(var sideLength: Double, name: String) : NamedShape(name) { init { numberOfSides = 4 } fun area() = sideLength.pow(2) override fun simpleDescription() = "A square with sides of length $sideLength." } val square = Square(5.2, "My square") val area = square.area() val desc = square.simpleDescription() C# class NamedShape { private readonly string _name; public NamedShape(string name) => _name = name; protected int NumberOfSides { get; set; } public virtual string SimpleDescription() => $"A shape with {NumberOfSides} sides."; } class Square: NamedShape { private readonly double _sideLength; public Square(double sideLength, string name) : base(name) { _sideLength = sideLength; NumberOfSides = 4; } public double Area() => Math.Pow(_sideLength, 2); override public string SimpleDescription() => $"A square with sides of length {_sideLength}."; } var square = new Square(5.2, "My square"); var area = square.Area(); var desc = square.SimpleDescription(); Data Class Kotlin data class Customer(var id: Long, var name: String) val customer = Customer(1, "Sachin") val name = customer.name customer.id = 2 C# // C# doesn't have data classes public class Customer { public int Id { get; set; } public string Name { get; set; } } var customer = new Customer { Id = 1, Name = "Sachin" }; var name = customer.Name; customer.Id = 2 Immutable Data Class Kotlin data class Customer(val id: Long, val name: String) val customer = Customer(1, "Sachin") val name = customer.name customer.id = 2 // Error C# public class Customer { public Customer(int id, string name) => (Id, Name) = (id, name); public int Id { get; } public string Name { get; } } var customer = new Customer(1, "Sachin"); var name = customer.Name; customer.Id = 2 // Error Extensions / Extension Methods Kotlin fun MutableList<Int>.swap(idx1: Int, idx2: Int) { val tmp = this[idx1] this[idx1] = this[idx2] this[idx2] = tmp } val list = mutableListOf(1, 5, 3) list.swap(0, 2) // [ 3, 5, 1 ] C# public static class Extensions { public static void Swap(this List<int> list, int idx1, int idx2) { var temp = list[idx1]; list[idx1] = list[idx2]; list[idx2] = temp; } } var list = new List<int> { 1, 5, 3 }; list.Swap(0, 2); // [ 3, 5, 1 ] Interfaces Kotlin interface Nameable { fun name(): String } fun <T: Nameable> genericFunction(x: T) { println("Name is " + x.name()) } class Person : Nameable { override fun name() = "Person A" } C# interface INameable { string Name(); } void GenericMethod<T>(T x) where T : INameable { Console.WriteLine("Name is " + x.Name()); } class Person : INameable { public string Name() => "Person A"; }

TYPES Checking Type Kotlin var movieCount = 0 var songCount = 0 for (item in library) { if (item is Movie) { ++movieCount } else if (item is Song) { ++songCount } } C# var movieCount = 0; var songCount = 0; foreach (var item in library) { if (item is Movie) ++movieCount; else if (item is Song) ++songCount; } Casting Kotlin // Unsafe (throw exception) val circle: Circle = shape as Circle // Safe (return null) val circle: Circle? = shape as Circle? val circle: Circle? = shape as? Circle C# // Unsafe (throw exception) var circle = (Circle)shape; // Safe (return null) var circle = shape as Circle; // If Nullable reference types enabled (optional feature) Circle? circle = shape as Circle; Smart Cast Kotlin for (current in library) { if (current is Movie) { println("Movie: '${current.name}', " + "dir. ${current.director}") } } C# foreach (var current in library) { if (current is Movie movie) { Console.WriteLine($"Movie: '{movie.Name}', " + $"dir. {movie.Director}"); } }

EXCEPTIONS Exception Handling Kotlin try { // some code } catch (e: SomeException) { if (e.SomeCode == 404) { // handle SomeException when SomeCode is 404 } else { // handle SomeException } } catch (e: Exception) { // handle rest of the Exceptions } finally { // optional finally block } C# try { // Some code } catch (SomeException e) when (e.SomeCode == 404) { // Handle SomeException only when SomeCode is 404 } catch (SomeException e) { // Handle SomeException } catch (Exception e) { // Handle rest of the Exceptions } finally { // Optional finally block } Exception Experssion Kotlin // try is an expression, i.e. it may have a return value val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null } C# // try is not an expression int? a; try { a = int.Parse(input); } catch { a = null; }

PATTERN MATCHING When / Switch Expression Kotlin val nb = 42 val text = when (nb) { in 0..7, 8, 9 -> "single digit" 10 -> "double digits" in 11..99 -> "double digits" in 100..999 -> "triple digits" else -> "four or more digits" } // double digits C# var nb = 42; var text = nb switch { int i when i < 10 => "single digit", 10 => "double digits", int i when i < 100 => "double digits", int i when i < 1000 => "triple digits", _ => "four or more digits" }; // double digits Is Expression / When Clause / Property Pattern Kotlin // Not supported yet // https://youtrack.jetbrains.com/issue/KT-20004 // http://openjdk.java.net/jeps/305 C# var result = item switch { Square s => Handle(s), Circle c when c.Radius < 10 => HandleUnder10(c), Circle { Radius: 20 } c => Handle20(c), Circle c => Handle(c), _ => throw new Exception("Unknown shape") }; // Same with if statements if (item is Square s) { } else if (item is Circle c && c.Radius < 10) { } else if (item is Circle { Radius: 20} c) { } else if (item is Circle ci) { }

NULL Nullable Types Kotlin data class Measurement(val celsius: Double) val data: Measurement = null // Error: can't be null val data: Measurement? = null // Ok: can be null fun printMayBeNull(data: Measurement?) { // data can be null, must have null check if (data == null) return println(data.celsius) } fun printNoNull(data: Measurement) { // data can't be null. No need for check println(data.celsius) } val current: Measurement? = getDataFromApi() printMayBeNull(current) // Ok: can be null if (current == null) return printNoNull(current) C# // Nullable reference types are optional feature in C# public class Measurement { public double Celsius { get; set; } } Measurement? data = null; // Ok: can be null Measurement data = null; // Error: can't be null void PrintMayBeNull(Measurement? data) { // data can be null, must have null check if (data == null) return; Console.WriteLine(data.Celsius); } void PrintNoNull(Measurement data) { // data can't be null. No need for check Console.WriteLine(data.Celsius); } Measurement? current = GetDataFromApi(); PrintMayBeNull(current); // No need for check as method accepts nulls if (current == null) return; PrintNoNull(current); // OK: Null is checked before method call Null Conditional Kotlin data class DataPoint(val id: Int, val celsius: Double, val child: DataPoint? = null) val data = DataPoint(1, 22.1, DataPoint(2, 22.8)) val result = data.child?.child?. let { toFahrenheit(it.celsius) } ?: Double.MIN_VALUE C# public class DataPoint { public int Id { get; set; } public double Celsius { get; set; } public DataPoint? Child { get; set; } } var data = new DataPoint { Id = 1, Celsius = 22.1, Child = new DataPoint { Id = 2, Celsius = 22.8 } }; var result = data.Child?.Child != null ? ToFahrenheit(data.Child.Child.Celsius) : double.MinValue; Elvis Operator / Null Coalescing Kotlin val data = DataPoint(1, 22.1, DataPoint(2, 22.8)) val result = data.child?.child?.celsius ?: Double.MIN_VALUE // Double.MIN_VALUE C# var data = new DataPoint { Id = 1, Celsius = 22.1, Child = new DataPoint { Id = 2, Celsius = 22.8 } }; var result = data.Child?.Child?.Celsius ?? double.MinValue; // double.MinValue Generics, Out and Conditional Kotlin // Use .let and forget weird helper methods val data = DataPoint(1, 22.1, DataPoint(2, 22.8)) val result = data.child?.child?. let { toFahrenheit(it.celsius) } ?: Double.MIN_VALUE C# // Generic helper method that will return boolean and set output bool GetValue<T>(T input, out T output) { output = input; return output != null; } var data = new DataPoint { Id = 1, Celsius = 22.1, Child = new DataPoint { Id = 2, Celsius = 22.8 } }; var result = GetValue(data.Child?.Child, out var output) ? ToFahrenheit(output.Celsius) : double.MinValue; string set = "My text"; var text = GetValue(set, out var output) ? output : "Not set"; // "My text" string notSet = null; var text = GetValue(notSet, out var output) ? output : "Not set"; // "Not set"

JSON / DYNAMIC Dynamic Kotlin // The dynamic type is not supported in code targeting the JVM // https://kotlinlang.org/docs/reference/dynamic-type.html // JSON example with data classes data class Work(val name: String, val location: String) data class User(val id: String, val work: Work) val json = """[ { "id": "A", "work": { "name": "ACME 2", "location": "NY" } }, { "id": "B", "work": { "name": "Box Co", "location": "SF" } }, { "id": "C", "work": { "name": "DotCom", "location": "NY" } } ]""" val users = jacksonObjectMapper().readValue<List<User>>(json) val name = users.first().work.name // ACME 2 val fromNy = users .filter { it.work.location == "NY" } .map { it.id } // [A, C] C# /* with dynamic, type is not known until runtime */ var json = @"[ { 'id': 'A', 'work': { 'name': 'ACME 2', 'location': 'NY' } }, { 'id': 'B', 'work': { 'name': 'Box Co', 'location': 'SF' } }, { 'id': 'C', 'work': { 'name': 'DotCom', 'location': 'NY' } } ]"; var users = JsonConvert.DeserializeObject<List<dynamic>>(json); var name = users.First().work.name; // ACME 2 var fromNY = users .Where(e => e.work.location == "NY") .Select(e => e.id); // [A, C]