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}");
}
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 const
and 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.
Originally published at https://vtsen.hashnode.dev.
Top comments (3)
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
Awesome, thank you!
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)