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]
COROUTINES / TASKS
Async / Await
Kotlin
data class Stats(val full_name: String, val stargazers_count: Int = -1, val forks: Int = -1)

val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

val repos = listOf("jetbrains/kotlin", "dotnet/csharplang")

val asyncRequests = repos.map { repo ->
    GlobalScope.async {
        val body = Fuel.get("https://api.github.com/repos/$repo")
            .responseString()
            .third.component1() // Fuel Result & Body
        body?.let { mapper.readValue(it) } ?: Stats(repo)
    }
}

runBlocking {
    val results = asyncRequests.map { it.await() }
    results.forEach{ println("${it.full_name} : ${it.stargazers_count} - ${it.forks}") }
}
C#
var client = new HttpClient();

var repos = new [] { "jetbrains/kotlin", "dotnet/csharplang" };

var asyncRequests = repos.Select(async repo =>
{
    var response = await client.GetAsync($"https://api.github.com/repos/{repo}");
    var json = await response.Content.ReadAsStringAsync();
    dynamic content = JsonConvert.DeserializeObject(json);
    return new { repo, stars = content.stargazers_count, forks = content.forks };
});

var results = await Task.WhenAll(asyncRequests);

foreach(var data in results)
    Console.WriteLine($"{data.repo} : {data.stars} - {data.forks}");