C# vs Kotlin
When it comes to sugar and fancy features in programming languages, C# and Kotlin are among the first options in mind. Since these two languages occupy similar niches, that is, they are strongly typed, garbage collected, cross-platform, used both in the backend and in mobile development, today we will try to compare their syntactic capabilities and arrange a small vote. To make the comparison fair, we will consider the latest versions of both languages. This article is a comparison article, not a tutorial, so some run-of-the-mill syntactic possibilities may be omitted.
Let’s start at the entry point
In C# this role is played by the static Main or top-level entry point method, for example
1 2 3 |
using static System.Console; WriteLine("Ok"); |
Kotlin needs a main function
1 |
fun main() = println("Ok") |
From these small two examples, first of all, it is noticeable that in Kotlin you can omit the semicolon. With deeper analysis, we see that in C #, despite the conciseness of the indicative entry point, static methods in other files still need to be wrapped in a class and explicitly imported from it (using static System.Console), and Kotlin goes further and allows you to create full-fledged functions …
Declaring Variables
In C #, the type is written on the left, and the new keyword is used to create an instance. There is a special word var, which can replace the type name on the left. However, variables within methods in C # remain susceptible to re-assignment.
1 2 3 |
Point y = new(0, 0); var x = new Point(1, 2); x = y; // Normal |
In Kotlin, types are written on the right, but they can be omitted. Besides var, val is also available, which does not allow re-assignment. You don’t need to specify new when instantiating.
1 2 3 |
val y: Point = Point(0, 0) val x = Point(1, 2) x = y // Compilation error! |
Working with memory
In C#, meaningful (when used inside methods, they are placed on the stack) and reference (placed on the heap) types are available to us. This feature allows you to apply low-level optimizations and reduce memory consumption.
For objects of structures and classes, the ‘==’ operator will behave differently when comparing values or references, although this behavior can be changed thanks to overloading. At the same time, some restrictions related to inheritance are imposed on structures.
1 2 3 4 |
// Value type, when used inside a method, will be pushed onto the stack struct ValueType {} // Reference type, will be on the heap class ReferenceType {} |
As for Kotlin, it does not have any separation in terms of working with memory. Comparison ‘==’ always happens by value, for comparison by reference there is a separate operator ‘===’. Objects are almost always allocated on the heap, and only for some basic types, for example, Int, Char, Double, the compiler can apply optimizations by making them primitives jvm and placing them on the stack, which is not reflected in their semantics in the syntax. One gets the impression that, on the one hand, runtime and working with memory is a stronger side of .NET in general, and on the other hand, the reference semantics in Kotlin is implemented much easier, you just write the code and do not think about the placement/options for passing the value.
Null safety
C# (since version 8) has null protection. However, it can be circumvented explicitly using the ! operator
1 2 3 |
var legalValue = maybeNull!; // if legalValue is now null, // then we will get an exception on the first try to use |
In Kotlin, you need to use two exclamations to use null, but there is another difference that shows great thoughtfulness.
1 2 3 |
val legalValue = maybeNull!! // if maybeNull == null, // then we will get an exception immediately |
Class properties
In C#, a convenient abstraction is available instead of get/set methods, that is, well-known properties. At the same time, traditional fields remain available.
1 2 3 4 5 6 7 8 9 10 11 |
class Example { // Calculated in advance and stored in the backing field public string Name1 { get; set; } = "Pre-calculated expression"; // Calculated with each call public string Name2 => "Calculated now"; // Traditional field private const string Name3 = "Field"; } |
In Kotlin it is simpler, there are no fields at all, only properties are available by default. At the same time, unlike C#, the public is the default scope, so it is recommended to omit the corresponding keyword. To distinguish properties, with and without set, the same var/val keywords are used.
1 2 3 4 5 6 7 8 |
class Example { // Calculated in advance and stored in the backing field val name1 = "Pre-calculated expression" // Calculated with each call val name2 get() = "Calculated now" } |
Data classes
In C#, the word record is enough to create a class for storing data, it will have the semantics of meaningful types in comparison, but it still remains referential (it will be allocated on the heap):
1 2 3 4 5 6 7 8 9 10 11 12 |
class JustClass { public string FirstName { init; get; } public string LastName { init; get; } } record Person(string FirstName, string LastName); ... Person person1 = new("Nancy", "Davolio"); Person person2 = person1 with { FirstName = "John" }; |
In Kotlin, you need to add the data keyword to the word class
1 2 3 4 5 6 7 8 |
class JustClass(val firstName: String, val lastName: String) data class Person(val firstName: String, val lastName: String) ... val person1 = Person("Nancy", "Davolio") val person2 = person1.copy(firstName = "John") |
Type extensions
In C#, such types must be in a separate static class and accept the caller as the first argument marked with this
1 2 3 4 5 6 |
static class StringExt { public static Println(this string s) => System.Console.WriteLine(s) public static Double(this string s) => s + s } |
In Kotlin, the expandable type must be to the left of the method, which can be placed anywhere. In this case, the type can be extended not only by the method but also by the property
1 2 3 |
fun String.println() = println(this) fun String.double get() = this * 2 |
Lambda expressions
C# has a special operator => for them
1 2 3 4 5 6 |
numbers.Any(e => e % 2 == 0); numbers.Any(e => { // volumetric logic ... return calculatedResult; }) |
In Kotlin, lambdas fit organically into the C-like syntax, in addition, in many cases, the compiler will inline their calls directly into the method used. This allows you to create efficient and beautiful DSLs (Gradle + Kotlin for example).
1 2 3 4 5 |
numbers.any { it % 2 == 0 } numbers.any { // volumetric logic ... calculatedResult } |
Terms and templates
C# has a very powerful pattern matching with conditions (example from the documentation)
1 2 3 4 5 6 7 |
static Point Transform(Point point) => point switch { { X: 0, Y: 0 } => new Point(0, 0), { X: var x, Y: var y } when x < y => new Point(x + y, y), { X: var x, Y: var y } when x > y => new Point(x - y, y), { X: var x, Y: var y } => new Point(2 * x, 2 * y), }; |
Kotlin has a similar switch when expression, which, despite the possibility of pattern matching, cannot contain deconstruction and guard conditions at the same time, but thanks to the concise syntax, you can get out:
1 2 3 4 5 6 7 8 |
fun transform(p: Point) = when(p) { Point(0, 0) -> Point(0, 0) else -> when { p.x > p.y -> Point(...) p.x < p.y -> Point(...) else -> Point(...) } } |
Summing up
It is almost impossible to put all the differences of both languages in one article. However, we can already draw some conclusions. It is noticeable that the Kotlin-way is more about minimizing the number of keywords, implementing all the sugar on top of the basic syntax, and C# seeks to become more convenient by increasing the number of available expressions at the level of the language itself. Kotlin has the advantage that its creators can look back at the good features of C# and laconic them, and C# benefits from strong support from Microsoft and better runtime.
Related Posts
Leave a Reply Cancel reply
Service
Categories
- DEVELOPMENT (103)
- DEVOPS (53)
- FRAMEWORKS (26)
- IT (25)
- QA (14)
- SECURITY (13)
- SOFTWARE (13)
- UI/UX (6)
- Uncategorized (8)