Supported C# Language Features

This is a list of C# language features and whether SaltarelleCompiler supports them. If a feature is not on the list, it is most likely supported (unless it is very obscure).

  • Inheritance Supported

    Inheritance is fully supported, including the possibility to override members.

    C#

    public class Base { public virtual string Method() { return "Base.Method"; } public virtual string Property { get { return "Base.Property"; } } } public class Derived1 : Base { public override string Method() { return "Derived1.Method"; } public override string Property { get { return "Derived1.Property"; } } } public class Derived2 : Base { public new string Method() { return "Derived2.Method"; } public new string Property { get { return "Derived2.Property"; } } } public class Driver { public static void Main() { Derived1 d1 = new Derived1(); Derived2 d2 = new Derived2(); Base b2 = d2; var sb = new StringBuilder(); sb.AppendLine("d1.Method() = " + d1.Method()); sb.AppendLine("d1.Property = " + d1.Property); sb.AppendLine("d2.Method() = " + d2.Method()); sb.AppendLine("d2.Property = " + d2.Property); sb.AppendLine("b2.Method() = " + b2.Method()); sb.AppendLine("b2.Property = " + b2.Property); Document.Instance.GetElementById("output").InnerHTML = sb.ToString(); } }

    JavaScript

    //////////////////////////////////////////////////////////////////////////////// // Base Base = function() { }; Base.prototype = { method: function() { return 'Base.Method'; }, get_property: function() { return 'Base.Property'; } }; //////////////////////////////////////////////////////////////////////////////// // Derived1 Derived1 = function() { Base.call(this); }; Derived1.prototype = { method: function() { return 'Derived1.Method'; }, get_property: function() { return 'Derived1.Property'; } }; //////////////////////////////////////////////////////////////////////////////// // Derived2 Derived2 = function() { Base.call(this); }; Derived2.prototype = { method$1: function() { return 'Derived2.Method'; }, get_property$1: function() { return 'Derived2.Property'; } }; //////////////////////////////////////////////////////////////////////////////// // Driver Driver = function() { }; Driver.main = function() { var d1 = new Derived1(); var d2 = new Derived2(); var b2 = d2; var sb = new ss.StringBuilder(); sb.appendLine('d1.Method() = ' + d1.method()); sb.appendLine('d1.Property = ' + d1.get_property()); sb.appendLine('d2.Method() = ' + d2.method$1()); sb.appendLine('d2.Property = ' + d2.get_property$1()); sb.appendLine('b2.Method() = ' + b2.method()); sb.appendLine('b2.Property = ' + b2.get_property()); (document).getElementById('output').innerHTML = sb.toString(); }; Base.registerClass('Base', Object); Derived1.registerClass('Derived1', Base); Derived2.registerClass('Derived2', Base); Driver.registerClass('Driver', Object);

    Output

    d1.Method() = Derived1.Method d1.Property = Derived1.Property d2.Method() = Derived2.Method d2.Property = Derived2.Property b2.Method() = Base.Method b2.Property = Base.Property

  • Type inference Supported

    Type inference is fully supported, both using the 'var' keyword and implicitly typed lambdas.

    C#

    public class Driver { string F(int i) { return "F(int)"; } string F(string s) { return "F(sting)"; } string F(Func<int> f) { return "F(Func<int>)"; } string F(Func<string> f) { return "F(Func<string>)"; } public void Main() { var v1 = 1; var v2 = "x"; var sb = new StringBuilder(); sb.AppendLine(F(v1)); sb.AppendLine(F(v2)); sb.AppendLine(F(() => v1)); sb.AppendLine(F(() => v2)); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    //////////////////////////////////////////////////////////////////////////////// // Driver Driver = function() { }; Driver.prototype = { $f$2: function(i) { return 'F(int)'; }, $f$3: function(s) { return 'F(sting)'; }, $f: function(f) { return 'F(Func<int>)'; }, $f$1: function(f) { return 'F(Func<string>)'; }, main: function() { var v1 = 1; var v2 = 'x'; var sb = new ss.StringBuilder(); sb.appendLine(this.$f$2(v1)); sb.appendLine(this.$f$3(v2)); sb.appendLine(this.$f(function() { return v1; })); sb.appendLine(this.$f$1(function() { return v2; })); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    F(int) F(sting) F(Func<int>) F(Func<string>)

  • ref/out parameters Supported

    C#

    public class Driver { void F(ref int i) { i++; } public void Main() { int i = 1; var sb = new StringBuilder(); sb.AppendLine("Before: i = " + i); F(ref i); sb.AppendLine("After: i = " + i); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    //////////////////////////////////////////////////////////////////////////////// // Driver Driver = function() { }; Driver.prototype = { $f: function(i) { i.$++; }, main: function() { var i = { $: 1 }; var sb = new ss.StringBuilder(); sb.appendLine('Before: i = ' + i.$); this.$f(i); sb.appendLine('After: i = ' + i.$); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    Before: i = 1 After: i = 2

  • Generics Supported

    Both generic types and generic methods are fully supported.

    C#

    public class GenericClass<T> { public string F() { return typeof(T).FullName; } } public class Driver { public string F<T>() { return typeof(T).FullName; } public void Main() { var sb = new StringBuilder(); sb.AppendLine("new GenericClass<Driver>().F() = " + new GenericClass<Driver>().F());; sb.AppendLine("F<Driver>() = " + F<Driver>()); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    //////////////////////////////////////////////////////////////////////////////// // Driver Driver = function() { }; Driver.prototype = { f: function(T) { return function() { return T.get_fullName(); }; }, main: function() { var sb = new ss.StringBuilder(); sb.appendLine('new GenericClass<Driver>().F() = ' + (new (Type.makeGenericType(GenericClass$1, [Driver]))()).f()); ; sb.appendLine('F<Driver>() = ' + this.f(Driver).call(this)); (document).getElementById('output').innerText = sb.toString(); } }; //////////////////////////////////////////////////////////////////////////////// // GenericClass$1 GenericClass$1 = function(T) { var $type = function() { }; $type.prototype = { f: function() { return T.get_fullName(); } }; $type.registerGenericClassInstance($type, GenericClass$1, [T], function() { return Object; }, function() { return []; }); return $type; }; GenericClass$1.registerGenericClass('GenericClass$1', 1); Driver.registerClass('Driver', Object);

    Output

    new GenericClass<Driver>().F() = Driver F<Driver>() = Driver

  • Anonymous types Supported

    Anonymous types are fully supported.

    C#

    public class Driver { public void Main() { var v = new { i = 1, s = "x" }; var sb = new StringBuilder(); sb.AppendLine("v.i = " + v.i); sb.AppendLine("v.s = " + v.s); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var v = { i: 1, s: 'x' }; var sb = new ss.StringBuilder(); sb.appendLine('v.i = ' + v.i); sb.appendLine('v.s = ' + v.s); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    v.i = 1 v.s = x

  • Lambdas and anonymous delegates Supported

    Lambdas (both implicitly and explicitly typed) and C#2-style anonymous delegates are fully supported.

    C#

    public class Driver { public int F(Func<int, int> f) { return f(1); } public void Main() { var sb = new StringBuilder(); sb.AppendLine("Implicitly typed: " + F(i => i + 1)); sb.AppendLine("Explicitly typed: " + F((int i) => i + 1)); sb.AppendLine("Block lambda: " + F(i => { return i + 1; })); sb.AppendLine("C#2 anonymous delegate: " + F(delegate(int i) { return i + 1; })); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver.prototype = { f: function(f) { return f(1); }, main: function() { var sb = new ss.StringBuilder(); sb.appendLine('Implicitly typed: ' + this.f(function(i) { return i + 1; })); sb.appendLine('Explicitly typed: ' + this.f(function(i1) { return i1 + 1; })); sb.appendLine('Block lambda: ' + this.f(function(i2) { return i2 + 1; })); sb.appendLine('C#2 anonymous delegate: ' + this.f(function(i3) { return i3 + 1; })); (document).getElementById('output').innerText = sb.toString(); } };

    Output

    Implicitly typed: 2 Explicitly typed: 2 Block lambda: 2 C#2 anonymous delegate: 2

  • User-defined operators Supported

    User-defined operators are supported, except for 'operator true' and 'operator false'.

    C#

    public class Value { public int i; public Value(int i) { this.i = i; } public static Value operator+(Value v, int i) { return new Value(v.i + i); } } public class Driver { public void Main() { var sb = new StringBuilder(); var v = new Value(13); sb.AppendLine("Result = " + (v + 12).i); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var sb = new ss.StringBuilder(); var v = new Value(13); sb.appendLine('Result = ' + Value.op_Addition(v, 12).i); (document).getElementById('output').innerText = sb.toString(); } }; Value = function(i) { this.i = 0; this.i = i; }; Value.op_Addition = function(v, i) { return new Value(v.i + i); }; Driver.registerClass('Driver', Object); Value.registerClass('Value', Object);

    Output

    Result = 25

  • User-defined conversions Supported

    User-defined conversions are fully supported.

    C#

    public class Value { public int i; private Value(int i) { this.i = i; } public static explicit operator Value(int i) { return new Value(i); } } public class Driver { public void Main() { var sb = new StringBuilder(); var v = (Value)13; sb.AppendLine("Result = " + v.i); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var sb = new ss.StringBuilder(); var v = Value.op_Explicit(13); sb.appendLine('Result = ' + v.i); (document).getElementById('output').innerText = sb.toString(); } }; Value = function(i) { this.i = 0; this.i = i; }; Value.op_Explicit = function(i) { return new Value(i); };

    Output

    Result = 13

  • Method overloading Supported

    Method overloading is supported. Overload resolution is performed during compile-time, so there is no runtime overhead.

    C#

    public class Driver { public string F(int i) { return "F(int)"; } public string F(string s) { return "F(string)"; } public void Main() { var sb = new StringBuilder(); sb.AppendLine(F(0)); sb.AppendLine(F("X")); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { f: function(i) { return 'F(int)'; }, f$1: function(s) { return 'F(string)'; }, main: function() { var sb = new ss.StringBuilder(); sb.appendLine(this.f(0)); sb.appendLine(this.f$1('X')); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    F(int) F(string)

  • Constructor overloading Supported

    Constructor overloading is supported, with overload resolution being performed during compile-time. This means that there is no runtime overhead for using this feature.

    C#

    public class C { private string message; public C(int i) { message = "Constructed using int"; } public C(string s) { message = "Constructed using string"; } public string Message { get { return message; } } } public class Driver { public string F(int i) { return "F(int)"; } public string F(string s) { return "F(string)"; } public void Main() { var sb = new StringBuilder(); sb.AppendLine("Constructing with int: " + new C(1).Message); sb.AppendLine("Constructing with string: " + new C("x").Message); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    C = function(i) { this.$message = null; this.$message = 'Constructed using int'; }; C.prototype = { get_message: function() { return this.$message; } }; C.$ctor1 = function(s) { this.$message = null; this.$message = 'Constructed using string'; }; C.$ctor1.prototype = C.prototype; Driver = function() { }; Driver.prototype = { f: function(i) { return 'F(int)'; }, f$1: function(s) { return 'F(string)'; }, main: function() { var sb = new ss.StringBuilder(); sb.appendLine('Constructing with int: ' + (new C(1)).get_message()); sb.appendLine('Constructing with string: ' + (new C.$ctor1('x')).get_message()); (document).getElementById('output').innerText = sb.toString(); } }; C.registerClass('C', Object); Driver.registerClass('Driver', Object);

    Output

    Constructing with int: Constructed using int Constructing with string: Constructed using string

  • Object and collection initializers Supported

    Both object and collection initializers are fully supported.

    C#

    public class C { public string Property; public List<string> Collection; public C() { Collection = new List<string>(); } } public class Driver { public void Main() { var c = new C() { Property = "Property", Collection = { "Value 1", "Value 2", "Value 3" } }; var sb = new StringBuilder(); sb.AppendLine("c.Property = " + c.Property); sb.AppendLine("c.Collection = " + c.Collection); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    C = function() { this.property = null; this.collection = null; this.collection = new Array(); }; Driver = function() { }; Driver.prototype = { main: function() { var $t1 = new C(); $t1.property = 'Property'; $t1.collection.add('Value 1'); $t1.collection.add('Value 2'); $t1.collection.add('Value 3'); var c = $t1; var sb = new ss.StringBuilder(); sb.appendLine('c.Property = ' + c.property); sb.appendLine('c.Collection = ' + c.collection); (document).getElementById('output').innerText = sb.toString(); } };

    Output

    c.Property = Property c.Collection = Value 1,Value 2,Value 3

  • foreach Supported

    The C# foreach statement is fully supported.

    C#

    public class Driver { public void Main() { var list = new List<string> { "Value 1", "Value 2", "Value 3" }; var sb = new StringBuilder(); foreach (var s in list) { sb.AppendLine(s); } Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var $t1 = new Array(); $t1.add('Value 1'); $t1.add('Value 2'); $t1.add('Value 3'); var list = $t1; var sb = new ss.StringBuilder(); var $t2 = list.getEnumerator(); try { while ($t2.moveNext()) { var s = $t2.get_current(); sb.appendLine(s); } } finally { if (Type.isInstanceOfType($t2, ss.IDisposable)) { Type.cast($t2, ss.IDisposable).dispose(); } } (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    Value 1 Value 2 Value 3

  • using statement Supported

    The C# 'using' statement is fully supported.

    C#

    public class MyDisposable : IDisposable { public bool Disposed = false; public void Dispose() { Disposed = true; } } public class Driver { public void Main() { var d = new MyDisposable(); using (d) { // Here we could do stuff. } var sb = new StringBuilder(); sb.AppendLine("Disposed: " + d.Disposed); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var d = new MyDisposable(); { var $t1 = d; try { // Here we could do stuff. } finally { if (ss.isValue($t1)) { $t1.dispose(); } } } var sb = new ss.StringBuilder(); sb.appendLine('Disposed: ' + d.disposed); (document).getElementById('output').innerText = sb.toString(); } }; MyDisposable = function() { this.disposed = false; }; MyDisposable.prototype = { dispose: function() { this.disposed = true; } }; Driver.registerClass('Driver', Object); MyDisposable.registerClass('MyDisposable', Object, ss.IDisposable);

    Output

    Disposed: true

  • Exception handling Supported

    C#-style exception handling where exceptions are caught based on their type is supported. Non-Saltarelle exceptions are caught as the generic System.Exception type.

    C#

    public class MyException : Exception { public MyException(string message) : base(message) { } } public class Driver { public void Main() { var sb = new StringBuilder(); try { throw new Exception("message 1"); } catch (MyException ex) { sb.AppendLine("Caught MyException: " + ex.Message); } catch (Exception ex) { sb.AppendLine("Caught Exception: " + ex.Message); } try { throw new MyException("message 2"); } catch (MyException ex) { sb.AppendLine("Caught MyException: " + ex.Message); } catch (Exception ex) { sb.AppendLine("Caught Exception: " + ex.Message); } try { Script.Eval("throw 'message 3'"); } catch (MyException ex) { sb.AppendLine("Caught MyException: " + ex.Message); } catch (Exception ex) { sb.AppendLine("Caught Exception: " + ex.Message); } Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var sb = new ss.StringBuilder(); try { throw new ss.Exception('message 1'); } catch ($t1) { $t1 = ss.Exception.wrap($t1); if (Type.isInstanceOfType($t1, MyException)) { var ex = Type.cast($t1, MyException); sb.appendLine('Caught MyException: ' + ex.get_message()); } else { var ex1 = $t1; sb.appendLine('Caught Exception: ' + ex1.get_message()); } } try { throw new MyException('message 2'); } catch ($t2) { $t2 = ss.Exception.wrap($t2); if (Type.isInstanceOfType($t2, MyException)) { var ex2 = Type.cast($t2, MyException); sb.appendLine('Caught MyException: ' + ex2.get_message()); } else { var ex3 = $t2; sb.appendLine('Caught Exception: ' + ex3.get_message()); } } try { eval('throw \'message 3\''); } catch ($t3) { $t3 = ss.Exception.wrap($t3); if (Type.isInstanceOfType($t3, MyException)) { var ex4 = Type.cast($t3, MyException); sb.appendLine('Caught MyException: ' + ex4.get_message()); } else { var ex5 = $t3; sb.appendLine('Caught Exception: ' + ex5.get_message()); } } (document).getElementById('output').innerText = sb.toString(); } }; MyException = function(message) { ss.Exception.call(this, message); }; Driver.registerClass('Driver', Object); MyException.registerClass('MyException', ss.Exception);

    Output

    Caught Exception: message 1 Caught MyException: message 2 Caught Exception: message 3

  • Named and default arguments Supported

    C# named and default arguments are supported. Care is also taken so that reordered arguments are also evaluated left-to-right.

    C#

    public class Driver { public string F(int a, int b, int c, string d = "default value") { return "a = " + a + ", b = " + b + ", c = " + c + ", d = " + d; } public void Main() { var sb = new StringBuilder(); sb.AppendLine(F(12, c: 34, b: 56)); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { f: function(a, b, c, d) { return 'a = ' + a + ', b = ' + b + ', c = ' + c + ', d = ' + d; }, main: function() { var sb = new ss.StringBuilder(); sb.appendLine(this.f(12, 56, 34, 'default value')); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    a = 12, b = 56, c = 34, d = default value

  • C# variable capture semantics Supported

    In C#, each iteration of a loop gets its own instances of all variables declared inside the loop. This does normally not matter, but it is important if the value is captured by an anonymous method or lambda (Eric Lippert explains the issue).

    In JavaScript, however, variables are always function-scoped. SaltarelleCompiler performs compiler magic to work around this issue.

    C#

    public class Driver { public void Main() { var actions = new List<Action>(); var sb = new StringBuilder(); for (int i = 0; i < 5; i++) { int i2 = i; actions.Add(() => sb.AppendLine(i2)); } for (int i = 0; i < actions.Count; i++) actions[i](); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var actions = new Array(); var sb = new ss.StringBuilder(); for (var i = 0; i < 5; i++) { var i2 = { $: i }; actions.add(Function.mkdel({ i2: i2 }, function() { sb.appendLine(this.i2.$); })); } for (var i1 = 0; i1 < actions.length; i1++) { actions[i1](); } (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    0 1 2 3 4

  • Always evaluate expressions left-to-right Supported

    The compiler will ensure that expressions are always evaluated in a left-to-right order, if it is not guaranteed that the order of evaluation is unimportant.

    C#

    public class Driver { private StringBuilder sb; void F(int a = 1, int b = 2, int c = 3, int d = 4, int e = 5, int f = 6, int g = 7) {} int F1() { sb.AppendLine("F1"); return 0; } int F2() { sb.AppendLine("F2"); return 0; } int F3() { sb.AppendLine("F3"); return 0; } int F4() { sb.AppendLine("F4"); return 0; } public void Main() { sb = new StringBuilder(); F(d: F1(), g: F2(), f: F3(), b: F4()); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { this.$sb = null; }; Driver.prototype = { $f: function(a, b, c, d, e, f, g) { }, $f1: function() { this.$sb.appendLine('F1'); return 0; }, $f2: function() { this.$sb.appendLine('F2'); return 0; }, $f3: function() { this.$sb.appendLine('F3'); return 0; }, $f4: function() { this.$sb.appendLine('F4'); return 0; }, main: function() { this.$sb = new ss.StringBuilder(); var $t1 = this.$f1(); var $t2 = this.$f2(); var $t3 = this.$f3(); this.$f(1, this.$f4(), 3, $t1, 5, $t3, $t2); (document).getElementById('output').innerText = this.$sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    F1 F2 F3 F4

  • Properties Supported

    Both manually and automatically implemented properties are supported.

    C#

    public class Driver { public int MyInt { get; set; } private string myString; public string MyString { get { return myString; } set { myString = value; } } public void Main() { MyInt = 3; MyString = "x"; var sb = new StringBuilder(); sb.AppendLine("MyInt = " + MyInt); sb.AppendLine("MyString = " + MyString); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { this.$1$MyIntField = 0; this.$myString = null; }; Driver.prototype = { get_myInt: function() { return this.$1$MyIntField; }, set_myInt: function(value) { this.$1$MyIntField = value; }, get_myString: function() { return this.$myString; }, set_myString: function(value) { this.$myString = value; }, main: function() { this.set_myInt(3); this.set_myString('x'); var sb = new ss.StringBuilder(); sb.appendLine('MyInt = ' + this.get_myInt()); sb.appendLine('MyString = ' + this.get_myString()); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    MyInt = 3 MyString = x

  • Indexers Supported

    Indexers (including virtual and overloaded ones) are supported.

    C#

    public class C { public string this[int i] { get { return "Retrieved index " + i; } set {} } } public class Driver { public void Main() { var c = new C(); var sb = new StringBuilder(); sb.AppendLine(c[13]); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    C = function() { }; C.prototype = { get_item: function(i) { return 'Retrieved index ' + i; }, set_item: function(i, value) { } }; Driver = function() { }; Driver.prototype = { main: function() { var c = new C(); var sb = new ss.StringBuilder(); sb.appendLine(c.get_item(13)); (document).getElementById('output').innerText = sb.toString(); } }; C.registerClass('C', Object); Driver.registerClass('Driver', Object);

    Output

    Retrieved index 13.

  • Events Supported

    Both manually and automatically implemented events are supported.

    C#

    public class Driver { private Action manualEventDelegate; public event Action AutoEvent; public event Action ManualEvent { add { manualEventDelegate += value; } remove { manualEventDelegate -= value; } } public void Main() { var sb = new StringBuilder(); AutoEvent += () => sb.AppendLine("Auto event"); ManualEvent += () => sb.AppendLine("Manual event"); AutoEvent(); manualEventDelegate(); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { this.$manualEventDelegate = null; this.$1$AutoEventField = null; }; Driver.prototype = { add_autoEvent: function(value) { this.$1$AutoEventField = Function.combine(this.$1$AutoEventField, value); }, remove_autoEvent: function(value) { this.$1$AutoEventField = Function.remove(this.$1$AutoEventField, value); }, add_manualEvent: function(value) { this.$manualEventDelegate = Function.combine(this.$manualEventDelegate, value); }, remove_manualEvent: function(value) { this.$manualEventDelegate = Function.remove(this.$manualEventDelegate, value); }, main: function() { var sb = new ss.StringBuilder(); this.add_autoEvent(function() { sb.appendLine('Auto event'); }); this.add_manualEvent(function() { sb.appendLine('Manual event'); }); this.$1$AutoEventField(); this.$manualEventDelegate(); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    Auto event Manual event

  • Nullable types and lifted operators Supported

    Nullable types and lifted operations (included the special kind of lifted operations that exist for the bool type) are fully supported.

    C#

    public class Driver { public void Main() { int? i = null; var sb = new StringBuilder(); sb.AppendLine("i = " + i); sb.AppendLine("i + 1 = " + (i + 1)); sb.AppendLine("i.HasValue = " + i.HasValue); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var i = null; var sb = new ss.StringBuilder(); sb.appendLine('i = ' + i); sb.appendLine('i + 1 = ' + ss.Nullable.add(i, 1)); sb.appendLine('i.HasValue = ' + (ss.isValue(i))); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    i = null i + 1 = null i.HasValue = false

  • dynamic Supported

    The C# 'dynamic' type is fully supported and allows you to write operations that comes out untranslated in JavaScript.

    C#

    public class Driver { public void Main() { dynamic myDynamic = new object(); myDynamic["property"] = "property value"; var sb = new StringBuilder(); sb.AppendLine((string)myDynamic.property); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var myDynamic = new Object(); myDynamic['property'] = 'property value'; var sb = new ss.StringBuilder(); sb.appendLine(Type.cast(myDynamic.property, String)); (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    property value

  • Nested types Supported

    Nested types are supported.

    C#

    public class Driver { public class Nested { public string GetTypeName() { return GetType().FullName; } } public void Main() { var sb = new StringBuilder(); sb.AppendLine(new Nested().GetTypeName()); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var sb = new ss.StringBuilder(); sb.appendLine((new Driver$Nested()).getTypeName()); (document).getElementById('output').innerText = sb.toString(); } }; Driver$Nested = function() { }; Driver$Nested.prototype = { getTypeName: function() { return (Type.getInstanceType(this)).get_fullName(); } }; Driver.registerClass('Driver', Object); Driver$Nested.registerClass('Driver$Nested', Object);

    Output

    Driver$Nested

  • Query expressions Supported

    C# query expressions are fully supported.

    C#

    public void Main() { int[] arr1 = new[] { 1, 2, 3 }; int[] arr2 = new[] { 1, 2, 3 }; var query = from i in arr1 from j in arr2 where i >= j let k = i + j select "i = " + i + ", j = " + j + ", k = " + k; var sb = new StringBuilder(); foreach (var x in query) sb.AppendLine(x); Document.Instance.GetElementById("output").InnerText = sb.ToString(); }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var arr1 = [1, 2, 3]; var arr2 = [1, 2, 3]; var query = (Enumerable.from(arr1).selectMany(function(i) { return arr2; }, function(i1, j) { return { i: i1, j: j }; })).where(function($x0) { return $x0.i >= $x0.j; }).select(function($x1) { return { $x1: $x1, k: $x1.i + $x1.j }; }).select(function($x2) { return 'i = ' + $x2.$x1.i + ', j = ' + $x2.$x1.j + ', k = ' + $x2.k; }); var sb = new ss.StringBuilder(); var $t1 = query.getEnumerator(); try { while ($t1.moveNext()) { var x = $t1.get_current(); sb.appendLine(x); } } finally { if (Type.isInstanceOfType($t1, ss.IDisposable)) { Type.cast($t1, ss.IDisposable).dispose(); } } (document).getElementById('output').innerText = sb.toString(); } }; Driver.registerClass('Driver', Object);

    Output

    i = 1, j = 1, k = 2 i = 2, j = 1, k = 3 i = 2, j = 2, k = 4 i = 3, j = 1, k = 4 i = 3, j = 2, k = 5 i = 3, j = 3, k = 6

  • goto/goto case/goto default Supported

    The 'goto' keyword, and its siblings 'goto case' and 'goto default' are fully supported. Because these features do not exist in JavaScript, when they are used the method has to be transformed into a state machine, implemented as a giant for (;;) statement and a switch.

    The use of goto is not recommended in the usual case, the reason for implementing the feature is that iterator blocks and async methods use the same strategy.

    C#

    public class Driver { public void Main() { var sb = new StringBuilder(); int a = 0; lbl1: sb.AppendLine("lbl1"); if (a == 0) goto lbl2; else if (a == 1) goto lbl3; else goto lbl4; lbl2: sb.AppendLine("lbl2"); goto lbl3; lbl3: a = 2; sb.AppendLine("lbl3"); goto lbl1; lbl4: sb.AppendLine("lbl4"); Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { main: function() { var $state = 0, sb, a; $sm1: for (;;) { switch ($state) { case 0: { sb = new ss.StringBuilder(); a = 0; $state = 1; continue $sm1; } case 1: { sb.appendLine('lbl1'); if (a === 0) { $state = 2; continue $sm1; } else if (a === 1) { $state = 3; continue $sm1; } else { $state = 4; continue $sm1; } } case 2: { sb.appendLine('lbl2'); $state = 3; continue $sm1; } case 3: { a = 2; sb.appendLine('lbl3'); $state = 1; continue $sm1; } case 4: { sb.appendLine('lbl4'); (document).getElementById('output').innerText = sb.toString(); $state = -1; break $sm1; } default: { break $sm1; } } } } }; Driver.registerClass('Driver', Object);

    Output

    lbl1 lbl2 lbl3 lbl1 lbl4

  • Iterator blocks (yield) Supported

    Iterator blocks (methods containing yield return / yield break) are fully supported. Yield inside finally behaves exactly as the C# standard mandates (code is executed when the enumerator is disposed, or when it runs to end).

    C#

    public class C { private StringBuilder _sb; public C(StringBuilder sb) { _sb = sb; } public IEnumerable<int> GetEnumerable(int n) { try { for (int i = 0; i < n; i++) { _sb.AppendLine("yielding " + i); yield return i; } } finally { _sb.AppendLine("in finally"); } } } public class Driver { public void Main() { var sb = new StringBuilder(); int n = 0; foreach (var i in new C(sb).GetEnumerable(5)) { sb.AppendLine("got " + i); if (++n == 2) break; } Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    //////////////////////////////////////////////////////////////////////////////// // C C = function(sb) { this.$_sb = null; this.$_sb = sb; }; C.prototype = { getEnumerable: function(n) { return new ss.IteratorBlockEnumerable(function() { return (function(n) { var $result, $state = 0, i; $finally = function() { this.$_sb.appendLine('in finally'); }; return new ss.IteratorBlockEnumerator(function() { $sm1: for (;;) { switch ($state) { case 0: { $state = 1; i = 0; $state = 3; continue $sm1; } case 3: { $state = 1; if (!(i < n)) { $state = 2; continue $sm1; } this.$_sb.appendLine('yielding ' + i); $result = i; $state = 4; return true; } case 2: { $state = -1; $finally.call(this); $state = -1; break $sm1; } case 4: { $state = 1; i++; $state = 3; continue $sm1; } default: { break $sm1; } } } return false; }, function() { return $result; }, function() { try { switch ($state) { case 1: case 2: case 3: case 4: { try { } finally { $finally.call(this); } } } } finally { $state = -1; } }, this); }).call(this, n); }, this); } }; //////////////////////////////////////////////////////////////////////////////// // Driver Driver = function() { }; Driver.prototype = { main: function() { var sb = new ss.StringBuilder(); var n = 0; var $t1 = (new C(sb)).getEnumerable(5).getEnumerator(); try { while ($t1.moveNext()) { var i = $t1.get_current(); sb.appendLine('got ' + i); if (++n === 2) { break; } } } finally { $t1.dispose(); } (document).getElementById('output').innerText = sb.toString(); } }; C.registerClass('C', Object); Driver.registerClass('Driver', Object);

    Output

    yielding 0 got 0 yielding 1 got 1 in finally

  • async Supported

    Methods with the async modifier, and the await statement, are fully supported, although the generated script can look a bit scary.

    C#

    public class Driver { public void Method() { jQuery.Select("#main h2").Click(async (el, evt) => { await jQuery.Select("#main p").FadeOutTask(); await jQuery.Select("#main p").FadeInTask(); Window.Alert("Done"); }); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { method: function() { $('#main h2').click(Function.thisFix(function(el, evt) { var $state = 0, $t1, $t2; var $sm = function() { try { $sm1: for (;;) { switch ($state) { case 0: { $state = -1; $t1 = ss.Task.fromDoneCallback($('#main p'), 'fadeOut'); $state = 1; $t1.continueWith($sm); return; } case 1: { $state = -1; $t1.getResult(); $t2 = ss.Task.fromDoneCallback($('#main p'), 'fadeIn'); $state = 2; $t2.continueWith($sm); return; } case 2: { $state = -1; $t2.getResult(); window.alert('Done'); $state = -1; break $sm1; } default: { break $sm1; } } } } catch ($t3) { } }; $sm(); })); } }; Driver.registerClass('Driver', Object);

  • Multi-dimensional arrays Supported

    Multi-dimensional arrays are fully supported.

    C#

    public class Driver { public double[,] Identity { get { return new double[,] { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; } } public double[,] Multiply(double[,] a, double[,] b) { var result = new double[3,3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { result[i, j] += a[i, k] * b[k, j]; } } } return result; } public void Main() { var m1 = new double[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; var m2 = new double[,] { { 10, 11, 12 }, { 13, 14, 15 }, { 16, 17, 18 } }; var i = Identity; var result = Multiply(Multiply(m1, i), m2); var s = result[0, 0] + ", " + result[0, 1] + ", " + result[0, 2] + "\n" + result[1, 0] + ", " + result[1, 1] + ", " + result[1, 2] + "\n" + result[2, 0] + ", " + result[2, 1] + ", " + result[2, 2] + "\n"; Document.Instance.GetElementById("output").InnerText = sb.ToString(); } }

    JavaScript

    Driver = function() { }; Driver.prototype = { get_identity: function() { var $t1 = Array.multidim(Number.getDefaultValue(), 3, 3); $t1.set(0, 0, 1); $t1.set(0, 1, 0); $t1.set(0, 2, 0); $t1.set(1, 0, 0); $t1.set(1, 1, 1); $t1.set(1, 2, 0); $t1.set(2, 0, 0); $t1.set(2, 1, 0); $t1.set(2, 2, 1); return $t1; }, multiply: function(a, b) { var result = Array.multidim(Number.getDefaultValue(), 3, 3); for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { for (var k = 0; k < 3; k++) { result.set(i, j, result.get(i, j) + a.get(i, k) * b.get(k, j)); } } } return result; }, main: function() { var $t1 = Array.multidim(Number.getDefaultValue(), 3, 3); $t1.set(0, 0, 1); $t1.set(0, 1, 2); $t1.set(0, 2, 3); $t1.set(1, 0, 4); $t1.set(1, 1, 5); $t1.set(1, 2, 6); $t1.set(2, 0, 7); $t1.set(2, 1, 8); $t1.set(2, 2, 9); var m1 = $t1; var $t2 = Array.multidim(Number.getDefaultValue(), 3, 3); $t2.set(0, 0, 10); $t2.set(0, 1, 11); $t2.set(0, 2, 12); $t2.set(1, 0, 13); $t2.set(1, 1, 14); $t2.set(1, 2, 15); $t2.set(2, 0, 16); $t2.set(2, 1, 17); $t2.set(2, 2, 18); var m2 = $t2; var i = this.get_identity(); var result = this.multiply(this.multiply(m1, i), m2); var s = result.get(0, 0) + ', ' + result.get(0, 1) + ', ' + result.get(0, 2) + '\n' + result.get(1, 0) + ', ' + result.get(1, 1) + ', ' + result.get(1, 2) + '\n' + result.get(2, 0) + ', ' + result.get(2, 1) + ', ' + result.get(2, 2) + '\n'; $('#main p').css('white-space', 'pre'); $('#main p').text(s); } }; Driver.registerClass('Driver', Object);

    Output

    84, 90, 96 201, 216, 231 318, 342, 366

  • Expression trees Supported

    Expression trees are supported in a way that is almost fully compatible with .net

  • User-defined value types Almost fully supported

    User-defined value types (structs) are supported. However, any such type that is mutable (has a non-readonly instance field) is required to be decorated with a [MutableAttribute], and mutable value types cannot be used as generic arguments. Immutable value types should not suffer from any restrictions.

  • lock statement Has no effect

    The lock statement does not really make sense in JavaScript. If it is used anyway, the locked expression and the embedded statement will be evaluated, but the lock statement itself has no effect.

  • operator true/false Not yet supported

    C# 'operator true' and 'operator false' operators are not supported. If there is public demand for the feature, it can probably be done rather quickly, but I don't think anyone uses these operators anyway.

  • extern alias Not yet supported

    Named references and the accompanying 'extern alias' statement is an obscure feature of the C# language which is not supported.

  • Clipped integer type (short/byte) Not yet supported

    The Saltarelle type system only has two kinds of numbers, floating-points and integers. Integer division always produces integers, and the expression (int)(object)1.5 will throw an exception, but there is currently no support for an integer type whose range is limited to eg. 0-65535.

  • checked/unchecked Not yet supported

    The checked and unchecked keywords are ignored. Code inside a checked/unchecked statement will be evaluated, but the keywords themselves have no effect.

  • Pointers No support planned

    Pointers do not make sense in JavaScript and are thus not supported.