A comparison between the new ECMAScript 6 language features and C#

12 Minuten Lesezeit

A while ago, I took a look on the new language features of ECMAScript 6, the next JavaScript standard. After a short time, while playing around with the new features, something crossed my mind.

I know a few developers who still have a prejudice against JavaScript. Why shouldn't I try to compare its language features with C# 5.0?! If only one developer gets involved into that nice, lightweight and powerful language after reading this article, I'm quite happy. So, please don't see this blog post as a competition. It really is just a comparison. As of their paradigm and historical background, both languages are quite different.

JavaScript, a dynamic, prototype-based and functional language, has its roots in Lisp. C# on the other side is a static and object-oriented language that looks and works like any other C-based language.

Currently ECMAScript 6 only exists as a draft. It will be standardized in the middle of the next year. But with the transpiler Traceur most of the features can be used today. Keep in mind: nothing is production ready right now.

I've created a short list of some important links, if you want to dive into ECMAScript 6 now.

The comparison

In the following paragraphs there is a short code snippet for the most important and best specified ECMAScript 6 language features. Next to each snippet, you see how to reach the same result (in some cases nearly) with C#. The one comparison or another might be a bit unfair due to the different dynamic and static type system of both languages. If you want to get more information about a feature, then click on the provided "Read more" link at the end of each paragraph.

The JavaScript samples below should run with the great transpiler Traceur released by Google. It allows you to run ECMAScript 6 code in your browser today.

The C# samples can be executed with scriptcs, a scripting environment for C#. Here's an explanation by Scott Hanselman.

Classes

In some cases the concept of classes and inheritance could also make sense in dynamic languages like JavaScript. There are many different frameworks and libraries with their own class system implementation (e.g. Simple JavaScript Inheritance). But every implementation looks a bit different.

In ECMAScript 6 we get a standardized way for this work. Classes, constructors, properties and methods can be defined easily. In addition there's a simple inheritance system. The syntax itself is quite similar to Java.

Read more: http://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes

JavaScript

class Animal {
  constructor(sound) { this._sound = sound; }
  get sound() { return this._sound; }
  say() { console.log('I ' + this._sound); }
}

class Dog extends Animal {
  constructor(sound) { super('woof'); }
}

class Cat extends Animal {
  constructor(sound) { super('meeeou'); }
}

var dog = new Dog();
dog.say();

var cat = new Cat();
cat.say();

C#

class Animal {
  public Animal(string sound) { Sound = sound; }
  public string Sound { get; private set; }
  public void Say() { Console.WriteLine("I " + Sound); }
}

class Dog : Animal {
  public Dog() : base("woof") { }
}

class Cat : Animal {
  public Cat() : base("meeeou") { }
}

var dog = new Dog();
dog.Say();

var cat = new Cat();
cat.Say();

Modules

In the past, maybe the primitive times, JavaScript code was used as short snippets on static web pages. Nowadays there are large JavaScript applications running in the browser, directly on a client machine or on a server. Because of JavaScripts history, there doesn't exist anything like a module system to divide an application into several parts. You have to modularize your application by using external AMD- or CommonJS frameworks.

With ECMAScript 6 there is a common way to build up your JavaScript application and split it up into manageable parts.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:modules

JavaScript

// MathLib.js
export function max(...values) { }
export function min(...values) { }
export function avg(...values) { }

// Main.js
import {max, min} from './MathLib'; // relative filename without .js extension

console.log(max(1, 18, 9));
console.log(min(1, 18, 9));

C#

// MathLib/AggregateMethods.cs
namespace MathLib {
  static class AggregateMethods {
    public static int Max(params int[] values) { }
    public static int Min(params int[] values) { }
    public static int Avg(params int[] values) { }
  }
}

// Program.cs
using MathLib;

static class Program {
  public static void Main() {
    Console.WriteLine(AggregateMethods.Max(1, 18, 9));
    Console.WriteLine(AggregateMethods.Min(1, 18, 9));
  }
}

Default values of arguments

There is now the possibility to define optional arguments with some default values. You don't have to blow up your code by handling the arguments object or doing some other technique to do that in JavaScript.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:parameter_default_values

JavaScript

function splitLine(value, separator = ',') {
  return value.split(separator);
}

console.log(splitLine('value1,value2,value3'));
console.log(splitLine('value1;value2;value3', ';'));

C#

public string[] SplitLine(string value, string separator = ",") {
  return value.Split(new string[] { separator }, StringSplitOptions.None);
}

Console.WriteLine(SplitLine("value1,value2,value3"));
Console.WriteLine(SplitLine("value1;value2;value3", ";"));

Object initializer shorthand

There is no more need to specify redundant field names when objects are created. If you want or have to, you can explicitly define them, as you do it today.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:object_literals#object_literal_property_shorthands

JavaScript

function getPerson() {
  var name = 'Pete';
  var age = 42;

  return { name, age };
  // Instead of: return { name: name, age: age };
}

console.log(getPerson());
// { name: 'Pete', age: 42 }

C#

public object GetPerson() {
  var Name = "Pete";
  var Age = 42;

  return new { Name, Age };
}

Console.WriteLine(GetPerson());

Lexical block scope (let keyword, const keyword)

There are two new keywords to declare variables. These two don't work like the existing var keyword. So you don't have to struggle with the unintuitive function scope anymore. When the let keyword is used for a variable declaration, it can only be used in that block. In that way it feels more C-ish. const works exactly in the same way as let, but its value cannot be changed after it is initially assigned.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:block_scoped_bindings

JavaScript

// Sample 1
if(true) {
  let x = 5;
}

console.log(x); // undefined

// Sample 2
var funcs = [];

for(let x=0; x<3; x++) { 
  funcs.push(() => x); 
}

funcs.forEach(f => console.log(f()));
// Output: 0, 1, 2
// when using var instead of let in the for-loop: 3, 3, 3

C#

// Sample 1
if(true) {
  var x = 5;
}

Console.WriteLine(x); // error CS0103: The name 'x' does not exist in the current context

// Sample 2
var funcs = new List<Func<int>>();

for(var i=0; i<3; i++) {
  var value = i;
  funcs.Add(() => value);
}

funcs.ForEach(f => Console.WriteLine(f()));
// Output: 0, 1, 2
// without the value-variable in the for-loop: 3, 3, 3

Concise method definition

In ECMAScript 6 it's tried to reduce the use of the function keyword as often as possible. That's just some syntactic sugar to get less code and doesn't change any logic. The ECMAScript code cannot be really mapped to C#. In C# it's not allowed to access dynamically generated properties by the object initializer from function expressions.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:concise_object_literal_extensions

JavaScript

var person = {
  name: 'Pete',
  say() { console.log('Hi! My name is ' + this.name); }
}

person.say();

C#

var person = new {
  Name = "Pete",
  Say = new Action(() => Console.WriteLine("Hi! My name is " + this.Name))
};

person.Say();
// error CS0027: Keyword 'this' is not available in the current context

Lambda operator

Yeah, finally we get the lambda (aka fat arrow) operator. There's not much to say about it, but it dramatically reduces the amount of code you have to write. Imagine how your JavaScript code looks like when you use this syntax with something like Linq.js. Very familiar, or not?

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:arrow_function_syntax

JavaScript

var method = () => console.log('No parameters.');
var method2 = x => console.log(`One parameter: x=${x}`);
var method3 = (x, y) => console.log(`Two parameters: x=${x}, y=${y}`);

method();
method2(1);
method3(2, 3);

C#

Action method = () => Console.WriteLine("No parameters.");
Action<int> method2 = x => Console.WriteLine("One parameter: x={0}", x);
Action<int, int> method3 = (x, y) => Console.WriteLine("Two parameters: x={0}, y={1}", x, y);

method();
method2(1);
method3(2, 3);

Rest parameters

Rest parameters exist in C# since the beginning and can be defined with the help of the params keyword. In the current version of JavaScript you have to write a piece of code to get this behavior. In ECMAScript 6 the prepending ... transforms the several arguments outside of the function to an array of values inside of the function. Because JavaScript is dynamic the type of each argument can be different.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:rest_parameters

JavaScript

function average(...values) {
  return values.reduce((a, b) => a + b) / values.length;
}

console.log(average(1, 1, 3));
// 1.6666666666666667

C#

double Average(params double[] values) {
  return values.Aggregate((a, b) => a + b) / values.Length;
}

Console.WriteLine(Average(1, 1, 3));
// 1.6666666666666667

Spread operator

The spread operator allows to unpack an array into its elements. It's the counterpart of the rest parameter described above. So you can easily create new arrays out of existing arrays. That might be useful in some places. There doesn't exist something similar in C#.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:spread

JavaScript

// Sample 1
Array.prototype.pushMany = function(itemArrayToAdd) {
  this.push(...itemArrayToAdd);
};

var sports = [];
sports.pushMany(['Soccer', 'Tennis', 'Climbing']);
console.log(sports);
// ['Soccer', 'Tennis', 'Climbing']

// Sample 2
var a = [3, 4];
var b = [7];
var all = [0, 1, 2, ...a, 5, 6, ...b, 8, 9];

console.log(all);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

C#

// There's nothing similar.

Array comprehensions

UPDATE: Array comprehensions were moved to ECMAScript 7. Get more info in the Harmony working draft revision 27 of August 24, 2014.

In ECMAScript 6 array comprehensions help to initialize arrays in a more convenient way. In C# we have to use some API and don't get any syntactical support to achieve this.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:array_comprehensions

JavaScript

var value1 = [for (i of [1, 2, 3]) i * i];
var value2 = [for (x of [0, 1, 2]) for (y of [0, 1, 2]) x + '' + y];

console.log(value1); // [1, 4, 9]
console.log(value2); // ['00', '01', '02', '10', '11', '12', '20', '21', '22']

C#

// as LINQ Method Syntax
var value1 = new[] { 1, 2, 3 }.Select(i => i * i);
var value2 = new[] { 0, 1, 2 }.SelectMany(x => new[] { 0, 1, 2 }, (x, y) => x.ToString() + y);

// as LINQ Query Syntax
var value1 = from i in new[] { 1, 2, 3 } select i * i;
var value2 = from x in new[] { 0, 1, 2 } from y in new[] { 0, 1, 2 } select x.ToString() + y;

Console.WriteLine(value1); // [1, 4, 9]
Console.WriteLine(value2); // ['00', '01', '02', '10', '11', '12', '20', '21', '22']

Destructuring assignment

The destructuring assignment is great if a function returns multiple values as an array and these values should be assigned to some individual variables in one step. Or there is a need to extract several field values of a complex object. The syntactical element to achieve this exists in many functional languages. Now we'll get it with ECMAScript 6, too. Please take a look at the link below, because it's a really mighty feature and I only show a few use cases. In C# there doesn't exist something similar.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring

JavaScript

// Sample 1
var [m, d, y] = [3, 14, 1977];

console.log(m, d, y);
// 3, 14, 1977

// Sample 2
var x = 3; var y = 4;
[x, y] = [y, x];

console.log(x, y);
// 4, 3

// Sample 3
var person = { name: 'Pete', age: 42 };
var { name: nameOfPerson, age: ageOfPerson } = person;

console.log(nameOfPerson);
// Pete
console.log(ageOfPerson);
// 42

C#

// There's nothing similar

Binary and octal literals

Now there is a native way to describe binary and octal values. In C# there are just some special identifiers for the literals float, double and decimal. Normally the type byte is used to describe data.

JavaScript

console.log(0b0,  0b1, 0b11);
// 0, 1, 3

console.log(0o0, 0o1, 0o10, 0o77);
// 0, 1, 8, 63

C#

// There's nothing similar.

Generator functions

Generators functions produce return values, which can be iterated by a for-loop. Note that they are evaluated by the new special loop syntax for(var x of values). The method is only executed until the next yield statement is reached. C# also contains that kind of functions. Because it has a static type system the return value has to be of type IEnumerable or IEnumerable<T>.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:generators

JavaScript

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

for(var value of myGenerator()) {
  console.log(value);
}

C#

 IEnumerable MyGenerator() {
  yield return 1;
  yield return 2;
  yield return 3;
}

foreach(var value in MyGenerator()) {
  Console.WriteLine(value);
}

Promises

There are some popular JavaScript frameworks like jQuery or AngularJS, which provide a simple API that allows to define chainable asynchronous workflows. In ECMAScript 6, this API is part of the base library. In C# we can use the TPL with its continuation tasks.

Read more: http://www.html5rocks.com/en/tutorials/es6/promises/

JavaScript

function timeout(inMilliseconds) {
  return new Promise((resolve) => {
    console.log('start promise...');

    setTimeout(function() { resolve('returnValueOfTheTimeoutMethod'); }, inMilliseconds);
  });
}

timeout(1000)
  .then(() => console.log('... finished promise'))
  .then(() => timeout(1000))
  .then(x => console.log(`... another time with the return value ${x}`));

C#

string Timeout(int inMilliseconds)
{
  Console.WriteLine("start task...");
  Thread.Sleep(inMilliseconds);
  return "returnValueOfTheTimeoutMethod";
}

var task = new Task(() => Timeout(1000));

task
  .ContinueWith(_ => Console.WriteLine("...finished task"))
  .ContinueWith(_ => Timeout(1000))
  .ContinueWith(t => Console.WriteLine("... another time with the return value {0}", t.Result));

task.Start();

Console.ReadLine();

Collections (sets, maps, weak maps)

ECMAScript 6 also specifies a few new objects to handle special sorts of lists. There exist classes to work with maps (in C# Hashtable or Dictionary<T>), sets (in C# HashSet<T>) or weak maps.

Read more:

JavaScript

var map = new Map();
var uniqueItems = new Set();
var weakMap = new WeakMap();

C#

var map = new Hashtable();
var uniqueItems = new HashSet<object>();
// There's nothing similar for a WeakMap. A custom implementation of a Dictionary<WeakReference, object> is needed.

Template strings

Template strings are just awesome. If the single quotes to define a string are exchanged with backticks, then you can easily insert the value of a variable in it. Backticks might seem unfamiliar to C# developers, but they exist in many other languages out there. Template strings are a bit like C#s string.Format() on steroids.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:quasis

JavaScript

var person = { name: 'Pete', age: 42};

console.log(`Hello ${person.name}, you are ${person.age} years old!`);
// Hello Pete, you are 42 years old!

C#

var person = new { Name = "Pete", Age = 42};

Console.WriteLine("Hello {0}, you are {1} years old!", person.Name, person.Age);
// Hello Pete, you are 42 years old!

Proxy

Furthermore there is the introduction of a proxy object. That object can be placed around some existing object. Then the property access of the source object can be intercepted. To gain that functionality in C#, there are several options. The use of C# Emit stuff to generate code on the fly or existing libraries, like Castle DynamicProxy. Although the same can be done with frameworks like PostSharp or Fody by using IL weaving.

Read more: http://wiki.ecmascript.org/doku.php?id=harmony:direct_proxies

JavaScript

// Sample 1
var person = { name: 'Pete', age: 42 };
var proxiedPerson = new Proxy(person, {});

proxiedPerson.age = 37;

console.log(person.a); // 37
console.log(proxiedPerson.a); // 37

// Sample 2
var person = { name: 'Pete', age: 42 };

let validator = {
  get: function(obj, prop) {
    return obj[prop];
  },

  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }

      obj[prop] = value;
    }
  }
};

var proxiedPerson = new Proxy(person, validator);

proxiedPerson.age = 37;     // Okay
proxiedPerson.age = 'test'; // Error

C#

// No native or easy implementation
// Take a look at Castle DynamicProxy or similar libraries

Conclusion

As you could see, JavaScript really, really caught up to modern programming languages. Not only the speed of the language was improved over the last years. With the new ECMAScript 6 features like modules and classes, the way is paved for larger applications. All other features will bring many small improvements to your source code or will reduce its amount.


comments powered byDisqus