C# vs JavaScript: Type System

This post belongs to the C# vs JavaScript series.
by Nicklas Envall

A type system's ultimate purpose is to prevent errors by offering a set of rules to a language. Those rules can and often do differ from language to language. For instance, the rules for the type systems of JavaScript and C# are very different. Though it's not by accident, one main reason for the inconsistencies is that a typed language can be:

  • Static or Dynamic
  • Strong or Weak

Firstly, let us look at the difference between static and dynamic typing. To begin with, statically typed languages are type-checked during compile time. It means that type-unsafe programs will fail to compile, and bugs can get found in the early stages. Into the bargain, variables with types can make our programs more readable and enforce disciplined programming. Whereas dynamically typed languages (aka untyped languages) are type-checked during runtime. As a result, you do not have to declare the data type of a variable. Then, similarly to how variables with types can make our programs more readable, you could argue that variables without types are more syntactically pleasing and thus more readable. So, as you see, positive arguments for static typing or dynamic typing can be very subjective, and experienced developers can often be persuasive in their reasoning.

The name "dynamically typed" is somewhat confusing to many developers. Instead, you can view it as "dynamically checked." Also, the observant reader might now be wondering how a dynamically typed language can be untyped. For that, I recommend you to read this StackOverflow post as a gateway to learn more.

With this in mind, let us now shift our focus to strong typing versus weak typing. First of all, what strong typing or weak typing truly means is not universally agreed upon, and authors online can be very subjective in their explanations. However, generally, you can say that a strongly-typed language means that conversions between types have strict restrictions. Whereas a weakly-typed language entails a more loose approach to conversions between types. At the same time, there are degrees of how weak or strong a language is.

Additionally, weakly-typed languages depend on implicit type conversions, though it may also occur in statically typed languages. At its essence, it means that a compiler or runtime system automatically converts types according to a set of rules, which avoids type mismatches. Moreover, implicit type conversion is also known as type coercion and is somewhat controversial. In fact, it's one of the main reasons why some developers dislike JavaScript so much. Because JavaScript uses type coercion heavily and is therefore very weakly typed. Though, the reason for that is to make JavaScript programs highly tolerable to errors. Thus, runtime type mismatch exceptions rarely occur - or at least that's the idea.

Of course, this comes with advantages and disadvantages. Where one main disadvantage is that it may lead to strange and unexpected results. For example, if you add two arrays containing numbers together in JavaScript, then each array will get implicitly converted into a string, and then those strings will get concatenated:

[8, 9, 1] + [0, 11, 12] // "8,9,10,11,12"

While we would have to explicitly convert the arrays into strings in strongly typed C#:

int[] arr1 = new int[] { 8, 9, 1 };
int[] arr2 = new int[] { 0, 11, 12 };

arr1 + arr2; // Operator '+' cannot be applied to operands of type 'int[]' and 'int[]'
String.Join(", ", arr1) + String.Join(", ", arr2); // "8,9,10,11,12"

Just like adding an int with a bool will also cause an error in C#:

int a = 5;
int b = a + 2; // OK - 7
bool c = true;
int d = a + c; // Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'

Yet, JavaScript would have no problem adding an int with a bool:

const a = 5;
const b = a + 2; // OK - 7
const c = true;
const d = a + c; // OK - 6

But before we resort to the usual "because JavaScript" conclusion, let's take a look at C++:

int a = 1;
int b = 1 + true;
std::cout << b; // 2

As we see, it works in C++ as well. So, C++ and JavaScript have something in common? Now, the point certainly isn't to say C++ and JavaScript are the same - because they are not. Instead, it's that static typing != strong typing and that there are different degrees to how "strong" or "weak" a language is. For example, did you know that Python is both dynamically typed and strongly typed?

That said, JavaScript is not immune to runtime errors. Exceptions can be thrown and will when doing something like accessing a property from null:

const a = {};
a.x; // OK - undefined

const b = null;
b.x; // Uncaught TypeError: b is null

Also, going back to dynamic versus static, more unexpected results can occur in dynamically typed JavaScript. For example, adding a number and string might not be that strange, but it can lead to unexpected results:

const string = '200';
const number = 600;
const result = string + number;
result; // "200600"

It's also possible to do this in C#, so why would C# result in a less unexpected result?

The answer to that is types.

Because C# is statically typed and enforces types, and as a result, all variables and constants must have one. Furthermore, values are instances of them, unlike in, JavaScript where values have types, not variables. So in C#, we must either explicitly declare the data types for our variables or let the compiler infer the types:

int num = 200;
string str = "600";
string result = num + str;
result; // "200600"

As expected, we got the same result as in JavaScript. Yet something is very different because note how our variable result is explicitly declared as a string and, as a result, makes it clear what value it holds. Additionally, we would have got a compile error if we declared result as an int. So clearly, static typing can save us headaches and gives us valuable information for our decisions. In this case, perhaps our decision would have been to explicitly parse the string to an int?

JavaScript Variables

JavaScript variables do not have types, but the values they hold do. Instead of declaring types, we use let and const:

const name1 = 'hello world';
const name2 = 123;
const name3 = true;

All variables (not constants) can be re-assigned values of all types:

let name1 = 'hello world';
name1 = 123;
name1 = true;

C# Variables

C# variables have types, and values are instances of them. To declare a variable, you may use the type varName = value; syntax:

string name1 = "hello world";
int name2 = 123;
bool name3 = true;

C# also supports strongly typed implicit variable declarations with the var keyword. It instructs the C# compiler to infer the type based on the value the variable gets assigned. But, note that it's not like JavaScript's let because it may not be re-assigned to a new type.

string name1 = "hello world"; // Explicit
var name2 = "hello world"; // Implicit
var name3 = 123;
var name4 = true;