Motivation

Programming languages can be divided into many categories, such as dynamic language and static language, scripting language and programming language. In these categories, JS and C# can be the representation of dynamic and static languages. Although both of them can implement OOP and have many common language features, they are quite different especially in terms of their type checking, built-in data structures, OOP implementation and so on. As I have worked with them quite often, figuring out their differences and the reason behind can be quite helpful for further understanding of them. Further, this comparison can be connected to any other language.

This article will evolve around the common differences that should be noticed between them.


1. Compiled VS Interpreted

C# is a compiled language, meaning the source code gets compiled before runtime. As such, early errors will be caught at compile time. In .NET framework, C# gets compiled and runs at Common Language Runtime(CLR). The C# source code first gets converted to Intermediate Language(IL) code by the compiler. Then, the IL code will be converted to native machine code via Just In Time (JIT) compilation before finally the machine code gets executed by computer.

C# source code → converted to IL by CLR → converted to machine code by JIT

JavaScript historically is an interpreted language, meaning JS source code gets interpreted to machine code line by line and executed at runtime without compilation. As such, errors will be thrown when the code is at runtime. Today, thanks to modern JS engines, JS source code normally gets interpreted and then compiled. First, JS source code gets interpreted line by line and converted to byte code. Then, the byte code gets optimised and converted to machine code by Just In Time (JIT) compilation before finally the machine code gets executed by computer.

JS source code → converted to byte code by interpreter → optimised and converted to machine code by JIT


2. Dynamic Type and Static Type

C#:

  • Statically-typed language:
    • C# source code gets type-checking on compile-time before runtime, meaning that the program throws typing errors before this program is running.
  • Strongly-typed language:
    • C# has stricter typing rules.
    • We need to specify data types during variable declaration.
    • Most often, when we can re-assign a value that is a different data type to a variable, we need explicit conversion.
    • Implicit conversion is only allowed to convert a small number type to a bigger number type (eg. int -> double).
    • Otherwise, for non-compatible types and passing a bigger number type to a smaller number type (be careful information loss), explicit conversion is required.
int num1 = 1;

short num2 = Convert.ToInt16(num1);

/*
short num2 = num1;
Cannot implicitly convert type 'int' to 'short'.
An explicit conversion exists (are you missing a cast?)
*/

Console.WriteLine(num2); //1

JS:

  • Dynamically-typed language:
    • JS source code gets type-checking on run-time, meaning that the program only throws typing errors when this program is running and the computer reaches the line where errors occur.
  • Weakly-typed language:
    • JS has looser typing rules.
      • We can declare a variable without specifying data types.
      • We can re-assign a value that is a different data type to a variable without errors.
      • Implicit conversion is so weak that it can convert incompatible types (such as string to number, boolean to number and so on) implicitly.
      • However, it also supports explicit conversion and can check types with typeof.
2 + "3"; // output: '23'
/* 
+ is for numeric addiction and string concatenation. 
Here it converts number to string implicitly.
*/

/* 
other arithmetic operators 
convert string back to number implicitly
*/
3 * "5"; // output:15
1 + true; // output:2
false - 2; // output:-2

3. Working with Number

3.1 Number Type:

Number in computer will be represented by binary system (0 and 1). Bit is the smallest unit in a computer to store data. 1 bit can represent by binary number 0 or 1.

C#:

1 byte = 8 bits = 2^8 = 256 patterns;

Integral numbers:

  • byte (1 byte)
  • short (2 byte)
  • int (4 bytes)
  • long (8 bytes)

floating-point number (real numbers):

  • float (4bytes)
  • double (8bytes)
  • decimal (16bytes)

These are commonly used number types.

Note: decimal is normally used when more decimal precision is required, which is appropriate to financial and monetary calculation, because its range is smaller than float and double.

JS:

Integral and floating point number:

  • Number: a 8 bytes (64-bit) floating point IEEE 754 number (IEEE 754 is a standard for floating number arithmetic), which is equivalent to double in C#.
  • BigInt: Reliably representing any integer number exceeding the boundary of Number data type (2⁵³ -1).

3.2 Overflow and underflow

When overflow and underflow happen:

C#:

Floating-point type(float, double and decimal):

Overflowed numbers will be represented by the MaxValue of the corresponding floating-point number type.

Under-flowed numbers will be represented by the MinValue of the corresponding floating-point number type.

using System;

public class Program
{
	public static void Main()
	{
		double first = Double.MaxValue;
		double second = first + 1;

		Console.WriteLine(first);
		Console.WriteLine(second);
		//1.79769313486232E+308
		//1.79769313486232E+308
	}
}

It is worth nothing that decimal is a value type constructed by struct but not regarded as a primitive type in C# . As such, it will always throw errors of overflow or underflow:

using System;

public class Program
{
	public static void Main()
	{
		 Console.WriteLine("{0} is a primitive data type: {1}.",
         typeof(decimal).Name, typeof(decimal).IsPrimitive);
			//Decimal is a primitive data type: False.

			decimal first = Decimal.MaxValue;
			decimal second = first + 1;

			Console.WriteLine(first);
			Console.WriteLine(second);
			/*
			Run-time exception (line 12): Value was either
			too large or too small for a Decimal.
			*/
	}
}

Integral number type:

Overflow and Underflow integral numbers will be wrapped from one limit to the other.

max value + 1 => min value

min value - 1 => max value

using System;

public class Program
{
	public static void Main()
	{
		int first = Int32.MaxValue;
		int second = first + 1;

		Console.WriteLine(first);
		Console.WriteLine(second);
		//2147483647
		//-2147483648
	}
}

Usechecked” statement for throwing overflow errors for integral number:

using System;

public class Program
{
	public static void Main()
	{
		checked{
		int first = Int32.MaxValue;
		int second = first + 1;

		Console.WriteLine(first);
		Console.WriteLine(second);
		/*
		Run-time exception (line 9): Arithmetic operation resulted in an overflow.
		*/
		}

	}
}

JS:

Number in general (floating point number and integral number) :

Overflow number (> Number.MAX_VALUE) will be represented by INFINITY.

Number smaller than (< -Number.MAX_VALUE) will be represented by -INFINITY.

Underflow number (< Number.MIN_VALUE) will be converted to 0.

Note: Min_VALUE represents the smallest positive value (closest to 0) instead of the smallest possible number that JS can represent.

console.log(Number.MAX_VALUE); //1.7976931348623157e+308
console.log(Number.MAX_VALUE * 2); //Infinity

console.log(Number.MIN_VALUE); //5e-324
console.log(Number.MIN_VALUE / 2); // 0

Integral number type:

Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER represent the boundary of safe integer number for calculation. Any number outside the boundary should not be used for calculation because they will cause imprecision.

console.log(Number.MAX_SAFE_INTEGER); //9007199254740991 / 2^53 -1
console.log(Number.MIN_SAFE_INTEGER); //-9007199254740991 / - (2^53 -1)

If there is a need of using number larger than the boundary, you can use BigInt().

3.3 Round-up methods:

Math.floor() / Math.Floor():

  • Round a number up to the largest integer less than or equivalent to the given number.

Math.ceil / Math.Ceiling()

  • Round a number up to the next largest integer.

Math.round() /Math.Round()

  • Round up a number to the nearest integer.

Note: For C#, arithmetic calculation(such as division) with integer numbers will truncate the decimal numbers of the result automatically. This is not the case for JS.

3.4 Floating point precision

This is not a problem specific to JS or C#. Instead, it is a general problem from many programming languages due to the use of binary system to store number by computers.

Binary system (base 2) cannot represent some numbers that the decimal system (base 10) can represent. During conversion from one number of decimal system to another of binary system, decimal number like 0.1, 0.2, 0.3 need to be converted infinitely under the hood.

However, IEEE754 requires that only a limit amount of bits ( like 52 bits for a 64 bits number - double ) can be used to store a floating number, which causes floating-point imprecision. As such, some floating-point calculation will be imprecise. For example:

console.log(0.1 + 0.2);
//output: 0.30000000000000004

In C#, the result will get automatically round up to 0.3 while JS will not.


4. Null

C#:

null represents the absence of a value and is the default value for reference type. It is worth knowing that Value type variables cannot be null in C#, and variables will get assigned with default values if not initialised. Nevertheless, it can be explicitly set to Nullable type.

JS

null and undefined means the absence of value. Variables automatically gets initialised with “undefined” at initialisation phase regardless of var, let and const, while let and const variables will be in Temporal dead zone if accessing before the variable declaration statements. Accessing variables in Temporal dead zone results in ReferenceError.

Further, null is type of object because of history problems. As such, it is best to use undefined to represent the absence of an empty value.


5. Scope

Scope of variables

C#:

  • Class Level
  • Method Level
  • Block Level

Global Scope is not supported natively in C# because C# is an OOP language and global scope violates OOP principle. Alternatively, A static class wrapping variables can be used as global variables.

JS:

  • Global scope
  • Block scope with let and const
  • Function scope with var

6. “this” keyword

C#:

ms doc:

Refer to the current instance of the class (in this case, “this” cannot be in static methods)

  1. Used to refer to private fields whose names are the same as the names of parameters in constructor.

  2. Used to pass the current object as parameter in methods.

  3. Used to construct indexer of the class

  4. A modifier for extension methods

    4.1. “this” precedes the first parameter of an extension method which specifies the type this method operates on.

JS:

“this” is runtime binding and binds to whatever calls the functions containing “this”.

  1. global objects
  2. Instance objects

Note: Arrow functions do not have “this” and they will inherit the value of “this” from their enclosing context.


7. Data structures

Common data structures include: Arrays, Hash tables, Linked List, Stack and Queue, Trees and Graphs.

C#:

  • Collections are ways of storing data and the size of them can grow and shrink dynamically.

    Note: Static Arrays are fixed size but with better performance than dynamic arrays, because when the allocation of memory to a dynamic array is full, the complete array will get copied and assigned to bigger memory allocation.

  • Generic collections are normally used because it is type safe and more efficient at operations without boxing and unboxing because of predefined types.

  • Generic Collections include lists/dynamic arrays, hash tables, linked lists, stacks and queues.

JS:

  • JS has dynamic array and hash tables(object, map, set, weakMap, weakSet). Other data structures need to be constructed manually.