Complete C# to Kotlin Syntax Comparisons

Kotlin Cheat Sheet for C# developers

Complete C# to Kotlin Syntax Comparisons

Introduction

If you are a C# developer and new to Kotlin, this article is for you. It gives you a quick overview of syntax comparisons between C# and Kotlin. It also can be served as your quick reference guide for Kotlin syntax.

Please note that the standard naming conventions and coding styles for these 2 languages are also different. You can see the differences in the following code examples.

If you have difficulty understanding the Kotlin syntax, please refer to the examples from kotlinlang.org .

Methods vs Functions

C#

public virtual void PrintMessageWithPrefix(  
    String message,   
    String prefix = "Info")  
{  
     Console.WriteLine($"{prefix} {message}");  
}

Kotlin

fun printMessageWithPrefix(  
    message: String,   
    prefix: String = "Info") {

    println("[$prefix] $message")  
}

If the visibility modifier is not stated in Kotlin, it is public and virtual by default.

Variables

C#

string a = "initial";  
const int b = 1;  
const int c = 3;

Kotlin

var a: String = "initial"   
val b: Int = 1               
val c = 3

Both var in C# and Kotlin are similar. There is no val concept in C#. The closest thing is constand readonly.

Null Safety

C#

string nullable = "You can keep a null here";  
nullable = null // okay

Kotlin

var neverNull: String = "This can't be null"  
neverNull = null // compilation error

var nullable: String? = "You can keep a null here"  
nullable = null // okay

There is no Null Safety in C#. In Kotlin, variable is not nullable by default unless you specify ? in your variable declaration.

Classes

C#

public class Contact  
{  
    public int id;  
    public string email;

    public Contact(int id, string email)  
    {  
        this.id = id;  
        this.email = email;  
    }  
}

Kotlin

class Contact(val id: Int, var email: String)

Can you see how amazing Kotlin is?

Generics

C#

public class GenericList<T>  
{  
    public void Add (T data)  
    {  
    }  
}

public GenericList<T> CreateGenericList<T>()  
{  
    return new GenericList<T>();  
}

Kotlin

class GenericList<T>() {  
    fun add (data: T) {  
    }  
}  

fun <T> createGenericList() = GenericList<T>()

Inheritance

C#

public class Dog  
{  
    public virtual void SayHello()  
    {  
        Console.WriteLine("wow wow!");  
    }  
}

public class Yorkshire : Dog 
{  
    public override void SayHello()  
    {  
        Console.WriteLine("wif wif!");  
    }  
}

Kotlin

open class Dog {                  
    open fun sayHello() {         
        println("wow wow!")  
    }  
}

class Yorkshire : Dog() {         
    override fun sayHello() {  
        println("wif wif!")  
    }  
}

Kotlin's classes and functions are final and virtual by default. open modifier is required to allow inheritance.

Switch vs When Statement

C#

void SwitchFunction(int input)  
{  
    switch (input)  
    {  
        case 0:  
            break;  

        default:  
             break;  
    }  
}

Kotlin

fun SwitchFunction(input: Int) {  
    when (input) {  
        0 -> {  
        }  
        else -> {  
        }  
    }  
}

Switch vs When Expression

C#

var input = 1;  
var output = input switch  
{  
    1 => "one",  
    2 => "two",  
    _ => "too big",  
};

Kotlin

val input = 1  
val output = when (input) {  
    1 -> "one"  
    2 -> "two"  
    else -> "too big"  
}

Loops

C#

var cakes = new List<String>() { "carrot", "cheese", "chocolate" };

// for loop
foreach (var cake in cakes)
{
    Console.WriteLine($"Yummy, it's a {cake} cake!");
}

// while  
var cakesEaten = 0;
while (cakesEaten < cakes.Count)
{
    ++cakesEaten;
}

// do-while  
cakesEaten = 0;
do
{
    ++cakesEaten;
} while (cakesEaten < cakes.Count);

Kotlin

val cakes = listOf("carrot", "cheese", "chocolate")  

// for loop  
for (cake in cakes) {  
    println("Yummy, it's a $cake cake!")  
}  

// while  
var cakesEaten = 0  
while (cakesEaten < cakes.size) {  
    ++cakesEaten  
}  

// do-while  
cakesEaten = 0  
do {                              
    ++cakesEaten  
} while (cakesEaten < cakes.size)

Ranges

C#

foreach (var index in Enumerable.Range(1, 5))
{ 
    Console.WriteLine(index); 
}

Kotlin

for(index in 1..5) { 
    print(index)
}

Equality Checks

C#

var authors = new HashSet<string>() 
    { "Shakespeare", "Hemingway", "Twain" };
var writers = new HashSet<string>() 
    { "Twain", "Shakespeare", "Hemingway" };

Console.WriteLine(authors.SetEquals(writers)); // return true
Console.WriteLine(authors == writers);  // return false

Kotlin

val authors = setOf("Shakespeare", "Hemingway", "Twain")
val writers = setOf("Twain", "Shakespeare", "Hemingway")

println(authors == writers)   // return true
println(authors === writers)  // return false

The important thing here == in Kotlin is a structural comparison and === is a reference comparison. In C#, == (similar to Equals() API) is reference comparison. For structural comparison in C#, specify API needs to be called (e.g. SetEquals()) as in the above example.

Conditional Expression

C#

 int Max(int a, int b) => a > b ? a : b;

 Console.WriteLine(Max(99, -42));

Kotlin

fun max(a: Int, b: Int) = if (a > b) a else b 

println(max(99, -42))

No ternary operator condition ? then : else in Kotlin.

Record vs Data Classes

C#

// available only in C# 9
public record User{
    public  int Id { get; set; } 
    public  string Name { get; set; } 
}

Kotlin

data class User(val id: Int, val name: String) {
}

Enum Classes

C#

enum State
{
    IDLE, RUNNING, FINISHED             
}

Kotlin

enum class State {
    IDLE, RUNNING, FINISHED                        
}

Sealed Classes

C#

public sealed class Mammal {}

Kotlin

sealed class Mammal()

There are similar but sealed class in Kotlin still allow you to subclass as long as it is still within the same package. In C# sealed means 100% sealed in all scenarios.

Static vs Object Keyword

C#

//no object expression in C# 
public class DayRates
{
    int standard = 30;
}
var dayRates = new DayRates();

// static class and method
static class DoAuth
{                       
    static void DoSomething() 
    {  
    }  
}

// static class/method within another class
class Server
{
    static class DoAuth
    {
        static void DoSomething()
        {
        }
    }
}

Kotlin

//object expression  
val dayRates = object {  
    var standard: Int = 30  
}

// object declaration (similar to static class/method)  
object DoAuth {                             
    fun doSomething() {  
    }  
}

// when object delaration is used inside a class
// companion object is used
class Server {  
    companion object DoAuth {  
        fun doSomething() {  
        }  
    }  
}

Updated: May 21, 2022 - The following article shows how object keyword can reduce plenty of boilerplate code.

Higher-Order Methods / Functions

C#

// method that takes operation has higher-order method
public int Calculate(int x, int y, Func<int, int, int> operation)
{
    return operation(x, y);
}

// method to be passed in as higher-order method
public int Sum(int x, int y)
{
    return x + y;
}

// usage
int sumResult = Calculate(1, 2, Sum)

Kotlin

// function that takes operation has higher-order function
fun calculate(x: Int, y: Int, operation: (Int, Int) -> Int): Int {  
    return operation(x, y)                                        
}

// function to be passed in as higher-order function
fun sum(x: Int, y: Int) = x + y          

// usage
val sumResult = calculate(1, 2, ::sum)

Lambda Methods / Functions

C#

Func<String, String> UpperCase 
    = (String str) => { return str.ToUpper(); };

Kotlin

val upperCase = { str: String -> str.uppercase() }

Extension Methods / Functions

C#

// implement int.IsBig() extention method
public static class Extensions
{
    public static bool IsBig(this int value)
    {
        return value > 100;
    }
 }

 // usage
 int value = 1000;
 Console.WriteLine(value.IsBig());

Kotlin

// implement Int.isBig() extention function
fun Int.isBig() :Boolean {  
    return this > 100  
}

// usage
val value = 1000  
println(value.isBig())

List / IReadOnlyList vs MutableList / List

C#

// mutable list
List<int> systemUsers = new List<int> { 1, 2, 3 };
// immutable list
IReadOnlyList<int> sudoers = new List<int> { 1, 2, 3 };

Kotlin

// mutable list
val systemUsers: MutableList<Int> = mutableListOf(1, 2, 3)        
// immutable list
val sudoers: List<Int> = systemUsers

Kotlin List is immutable by default and C#List is mutable by default.

HashSet / ImmutableHashSet vs MutableSet / Set

C#

var openIssues = new HashSet< String> 
    { "uniqueDescr1", "uniqueDescr2", "uniqueDescr3"};

var immutableOpenIssues = ImmutableHashSet.Create<String>();
immutableOpenIssues.Add("uniqueDescr1");
immutableOpenIssues.Add("uniqueDescr2");
immutableOpenIssues.Add("uniqueDescr3");

Kotlin

val openIssues: MutableSet<String> = mutableSetOf(
    "uniqueDescr1", "uniqueDescr2", "uniqueDescr3") 

val immutableOpenIssues: Set<String> = setOf(
    "uniqueDescr1", "uniqueDescr2", "uniqueDescr3")

IReadOnlyDictionary / Dictionary vs Map / MutableMap

C#

// immutable dictionary
IReadOnlyDictionary<string, string> occupations =
    new Dictionary<string, string>
    {
        ["Malcolm"] = "Captain",
        ["Kaylee"] = "Mechanic"
    };

// mutable dictionary
var occupationsMutable = new Dictionary<string, string>
{
    ["Malcolm"] = "Captain",
    ["Kaylee"] = "Mechanic"
};

Kotlin

// immutable Map
val occupations = mapOf( 
    "Malcolm" to "Captain", 
    "Kaylee" to "Mechanic" ) 

// mutable Map
val occupationsMutable = mutableMapOf( 
    "Malcolm" to "Captain", 
    "Kaylee" to "Mechanic" )

Where vs filter

C#

var numbers = new List<int> { 1, -2, 3, -4, 5, -6 };

var positives = numbers.Where(x => x > 0);
var negatives = numbers.Where(x => x < 0);

Kotlin

val numbers = listOf(1, -2, 3, -4, 5, -6) 

val positives = numbers.filter { x -> x > 0 } 
val negatives = numbers.filter { it < 0 }

Select vs map

C#

var numbers = new List<int> { 1, -2, 3, -4, 5, -6 };
var doubled = (List<int>)numbers.Select(x => x * 2);

Kotlin

val numbers = listOf(1, -2, 3, -4, 5, -6) 
val doubled = numbers.map { x -> x * 2 }

Please note that map and Map are different. Map is a dictionary collection and map is the extension functions of the collection.

any, all, none, and etc.

C#

var numbers = new List<int> { 1, -2, 3, -4, 5, -6 };

var anyNegative = numbers.Any(x => x < 0);
var allEven = numbers.All(x => x % 2 == 0);
var allOdd = numbers.All(x => x % 2 != 0);

Kotlin

val numbers = listOf(1, -2, 3, -4, 5, -6)

val anyNegative = numbers.any { it < 0 } 
val allEven = numbers.all { it % 2 == 0 } 
val allOdd = numbers.none { it % 2 == 0 }

none is not available in C# but you can achieve the same result with All

There are other similar extension functions which I do not list all of them here such as find, findAll, first, last, count, associateBy, groupBy, partition, flatMap, minOrNull, maxOrNull, sorted, [] - map element access, zip and getOrElse. C# may or may not have the equilvalent functions.

let, run, with, apply, also

C#

public void CustomPrint(String str)
{
    Console.WriteLine($"Custom: {str}");
}

var myStr = "test";

// no scope functions in C#
CustomPrint(myStr);
var empty = myStr.Length == 0;

Kotlin

fun customPrint(str: String) {  
    println("Custom: $str")  
} 

var myStr = "test"  
// let scope function
var empty = myStr.let {  
    customPrint(it)  
    it.isEmpty() // return the last expression  
}  

// run scope funciton
empty = myStr.run {  
    customPrint(this)  
    isEmpty() // return the last expression 
}

// with scope function
empty = with(myStr) {  
  customPrint(this)  
    isEmpty()  // return the last expression
}

// apply scope function
empty = myStr.apply {  
  customPrint(this)  
}.isEmpty()  

// also scope function  
empty = myStr.also {  
  customPrint(it)  
}.isEmpty()

There are no scope methods in C# and it needs custom implementation if we need one.

As you can see, these scope functions are very similar. In fact, there are interchangeablein my opinion. I do not 100% know how to use them. However, I think there are conventions on how to use them (will be covered by another blog post).

[Updated - Jan 11, 2022]: For my usage recommendations, see the article below:

by lazy - Delegated Properties

C#

// no equivalent delegated properties in C#
string lazyStr = null;
public string LazyStr
{
    get
    {
        if (lazyStr == null)
        {
            Console.WriteLine("computed!");
            lazyStr = "my Lazy";
        }
        return lazyStr;
    }
}

Kotlin

val lazyStr: String by lazy {
   println("computed!") 
   "my lazy"
}

Summary

In my opinion, Kotlin is more powerful than C#. It reduces the use of boilerplate code. C# can accomplish the same thing with more codes. I hope these comparisons help.

Did you find this article valuable?

Support Vincent Tsen by becoming a sponsor. Any amount is appreciated!