Converting C# to JavaScript

Neil Bevis

We were recently approached by a client who wanted to create a tablet app that would work offline and yet implement a large, complex C# library that was already being called by their website.
For platform flexibility, the decision had been made to build a HTML5 application and therefore our main challenge was in converting the C# library to JavaScript.

SharpKit

We opted to use SharpKit, which is designed to help C# developers generate JavaScript easily. It is indeed successful at enabling someone with limited JavaScript knowledge to write C# that will then be converted into .js files. Decorating a class with an attribute as shown below will result in compilation into .js file of the specified name:

[SharpKit.JavaScript.JsType(SharpKit.JavaScript.JsMode.Prototype, Filename="Filename.js")]
class DummyClass
{
   //code goes here
}

C# Classes to JavaScript Prototypes

SharpKit includes a mode known as Prototype, which for non-JavaScript coders may need some explanation. Class methods in JavaScript are specified via a prototype. For example, when you call "abc".indexOf("b"), you are actually executing String.prototype.indexOf. Even native types like string can be extended with bespoke functions, which will be important later.

SharpKit’s Prototype mode means that an object returned by new DummyClass() will be a fairly simple JavaScript object and yield generally expected behaviour for static and non-static C# members and methods.

Overloaded methods in JavaScript

JavaScript does not support function overloading, so the following C# code presents a problem:

class DummyClass
{
   int DummyMethod(int a) { return a; }
   int DummyMethod(int a, int b) { return a+b; }
}

It will be converted into JavaScript in which the second function simply replaces the first, so calling DummyMethod(1) would return NaN since the second function will execute but with b set to undefined.

Fortunately, SharpKit provides CLR mode, which provides a richer set of functionality but with added weight and requires runtime “compilation” via the Compile() function. CLR mode yields good support of C# overloading and inheritance.

Overall, SharpKit will handle many non-JavaScript features such as optional parameters, extension methods, and even Linq.

Repeatability

One of the key requirements was that the conversion was not a one-off process where the JavaScript code then replaces the C#. Both versions of the code would need to be maintained in parallel and at minimal future cost. For this reason, it was decided that the C# code would completely define the JavaScript code, so after conversion by SharpKit no manual changes would be allowed to the generated .js files.

Issues with converting a mature C# library to JavaScript

We found we had to give SharpKit a considerable helping-hand when it came to the converting the mature C# library, although we noted that the problems would have been easy to side-step if you were writing the library from scratch. Note that SharpKit is rapidly-developing tool and some of the problems we faced were actually fixed a week or so later.

Below are some examples of issues that we experienced:

  • ref and out keywords – when we began work, SharpKit did not provide support for these and we wrote our own wrapper class to enable the passed parameter to be a property of a JavaScript object that is passed to the function, thereby enabling the function to change the parameter. However SharpKit soon added correct support for these keywords.
  • Nullable types, e.g. int? – While JavaScript is perfectly happy with a variable that contains a number becoming null, in C# the null version has properties where in JavaScript null has none. To provide support for these we implemented static helper functions (e.g. JsHelper.HasValue(A)), which would convert correctly.
  • Incomplete CLR mode implementation – SharpKit provides support for many .Net library calls via a static .js file that implements core functionality such as C#’s System.Math. Sometimes we would find that a particular function was missing or only partially implemented and therefore modified the the static file accordingly.
  • Missing function from native prototype – Sometimes the simplest solution was to add a function to the prototype of a native type, which was done in the static .js file that was already present for CLR mode.
  • Default values – In C# an uninitialized Boolean will default to false, an integer to zero, and so on. However SharpKit did not provide the initialization code automatically. In the case of arrays, we wrote bespoke functions to initialize every element.
  • Multi-dimensional arrays – C# offers support for genuine multi-dimensional arrays, e.g. double[,], while JavaScript allows only arrays-of-arrays, e.g. double[][]. All occurrences of the former had to be replaced with the latter and we wrote bespoke functions to initialize the parent and all of the child arrays.
  • None of the above – Sometimes the only solution was to insert code in the C# to tell SharpKit that a piece of JavaScript should be explicitly inserted. For example:
    #if JS
    JsContext.JsCode("//JavaScript code here");
    #else
    //C# code here
    #endif

Summary

In this particular case SharpKit enabled us to have a single C# code-base for both the server code and the client-side JavaScript. It was not without teething problems, as this was a particularly ambitious case, but overall proved successful and future development should involve merely a bit of thought as to what C# features are used. Further, we managed to modify the C# library in order to compile correctly into JavaScript without breaking a single one of our client’s existing C# test cases.

This is a very different problem to that discussed in a recent post: Converting code from MatLab to FORTRAN. In that case, the conversion was from a dynamically-typed language to a statically-typed one, whereas here we needed to convert from C#, with its multitude of types, keywords, syntax and library functions to JavaScript with its flexible typing and minimal set of library functions.