DEV Community

Cover image for Complete C# to Kotlin Syntax Comparisons
Vincent Tsen
Vincent Tsen

Posted on • Updated on • Originally published at vtsen.hashnode.dev

Complete C# to Kotlin Syntax Comparisons

Kotlin Cheat Sheet for C# developers

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}");  
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

    println("[$prefix] $message")  
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Kotlin

var a: String = "initial"   
val b: Int = 1               
val c = 3       
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

class Contact(val id: Int, var email: String)
Enter fullscreen mode Exit fullscreen mode

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>();  
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

fun <T> createGenericList() = GenericList<T>()
Enter fullscreen mode Exit fullscreen mode

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!");  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

class Yorkshire : Dog() {         
    override fun sayHello() {  
        println("wif wif!")  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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;  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

fun SwitchFunction(input: Int) {  
    when (input) {  
        0 -> {  
        }  
        else -> {  
        }  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Switch vs When Expression

C#

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

Kotlin

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

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);
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Ranges

C#

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

Kotlin

for(index in 1..5) { 
    print(index)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

println(authors == writers)   // return true
println(authors === writers)  // return false
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

println(max(99, -42))
Enter fullscreen mode Exit fullscreen mode

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; } 
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

data class User(val id: Int, val name: String) {
}
Enter fullscreen mode Exit fullscreen mode

Enum Classes

C#

enum State
{
    IDLE, RUNNING, FINISHED             
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

enum class State {
    IDLE, RUNNING, FINISHED                        
}
Enter fullscreen mode Exit fullscreen mode

Sealed Classes

C#

public sealed class Mammal {}   
Enter fullscreen mode Exit fullscreen mode

Kotlin

sealed class Mammal()   
Enter fullscreen mode Exit fullscreen mode

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()
        {
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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() {  
        }  
    }  
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)    
Enter fullscreen mode Exit fullscreen mode

Lambda Methods / Functions

C#

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

Kotlin

val upperCase = { str: String -> str.uppercase() }     
Enter fullscreen mode Exit fullscreen mode

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()); 
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

// usage
val value = 1000  
println(value.isBig()) 
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

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");
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

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

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"
};
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

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

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);
Enter fullscreen mode Exit fullscreen mode

Kotlin

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

val positives = numbers.filter { x -> x > 0 } 
val negatives = numbers.filter { it < 0 }
Enter fullscreen mode Exit fullscreen mode

Select vs map

C#

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

Kotlin

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

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);
Enter fullscreen mode Exit fullscreen mode

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 } 
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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() 
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Kotlin

val lazyStr: String by lazy {
   println("computed!") 
   "my lazy"
}
Enter fullscreen mode Exit fullscreen mode

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.


Originally published at https://vtsen.hashnode.dev.

Top comments (3)

Collapse
 
jkone27 profile image
jkone27

you might like to update this with latest C# versions, e.g. String? and String -- >
C# supports nullable ref since a while.. (in a similar way of kotlin),
also C# has record types so also the "short class" version can be written in 1 line in C# as well.

if you want to check "even lighter and even more functional" code both compared to C# and Kotlin, you might have a look at F# :)

dotnet.microsoft.com/en-us/languag...

Thanks for the article! cheers

Collapse
 
vtsen profile image
Vincent Tsen

Awesome, thank you!

Collapse
 
jkone27 profile image
jkone27

You are welcome ! You might want to
give F# a try and Fsharp.Data, pretty awesome stuff type providers! And F# type inference is better than scala (ML Milner type inference)