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).
Supported C# Language Features
-
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);
Outputd1.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);
OutputF(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);
OutputBefore: 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);
Outputnew 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(); } }
JavaScriptDriver = 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);
Outputv.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(); } }
JavaScriptDriver.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(); } };
OutputImplicitly 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(); } }
JavaScriptDriver = 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);
OutputResult = 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(); } }
JavaScriptDriver = 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); };
OutputResult = 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(); } }
JavaScriptDriver = 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);
OutputF(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(); } }
JavaScriptC = 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);
OutputConstructing 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(); } }
JavaScriptC = 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(); } };
Outputc.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(); } }
JavaScriptDriver = 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);
OutputValue 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(); } }
JavaScriptDriver = 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);
OutputDisposed: 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(); } }
JavaScriptDriver = 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);
OutputCaught 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(); } }
JavaScriptDriver = 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);
Outputa = 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(); } }
JavaScriptDriver = 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);
Output0 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(); } }
JavaScriptDriver = 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);
OutputF1 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(); } }
JavaScriptDriver = 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);
OutputMyInt = 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(); } }
JavaScriptC = 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);
OutputRetrieved 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(); } }
JavaScriptDriver = 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);
OutputAuto 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(); } }
JavaScriptDriver = 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);
Outputi = 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(); } }
JavaScriptDriver = 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);
Outputproperty 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(); } }
JavaScriptDriver = 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);
OutputDriver$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(); }
JavaScriptDriver = 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);
Outputi = 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(); } }
JavaScriptDriver = 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);
Outputlbl1 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);
Outputyielding 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"); }); } }
JavaScriptDriver = 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(); } }
JavaScriptDriver = 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);
Output84, 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
-
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.
-
User-defined value types
Not yet supported
User-defined value types (structs) are not supported. They don't really fill a purpose in the script world because the reason people use them is for performance, and in order to preserve the semantics they would need to be copied all the time in the script, thus performing worse than normal reference types.
-
Pointers
No support planned
Pointers do not make sense in JavaScript and are thus not supported.