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#inheritance.cs
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();
	}
}
 
 
 
 
 
 
 
 
 
 
 
  
JSinheritance.js
////////////////////////////////////////////////////////////////////////////////
// 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); 

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

C#type-inference.cs
public class Driver {
    string F(int i) {
        return "F(int)";
    }

    string F(string s) {
        return "F(sting)";
    }

    string F(Func f) {
        return "F(Func)";
    }

    string F(Func f) {
        return "F(Func)";
    }

    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();
    }
}
 
  
JStype-inference.js
// 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)';
	},
	$f$1: function(f) {
		return 'F(Func)';
	},
	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); 

C#ref-out-parameters.cs
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();
    }
}
  
JSref-out-parameters.js
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); 

Generics Supported

Both generic types and generic methods are fully supported.

C#generics.cs
public class GenericClass {
	public string F() {
		return typeof(T).FullName;
	}
}

public class Driver {
	public string F() {
		return typeof(T).FullName;
	}

	public void Main() {
		var sb = new StringBuilder();
		sb.AppendLine("new GenericClass().F() = " + new GenericClass().F());;
		sb.AppendLine("F() = " + F());

		Document.Instance.GetElementById("output").InnerText = sb.ToString();
	}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  
JSgenerics.js
// 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().F() = ' + (new (Type.makeGenericType(GenericClass$1, [Driver]))()).f());
		;
		sb.appendLine('F() = ' + 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); 

Anonymous types are fully supported.

C#anonymous-types.cs
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();
	}
}
 
  
JSanonymous-types.js
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); 

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

C#lambdas-and-anonymous-delegates.cs
public class Driver {
	public int F(Func 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();
	}
}
 
 
 
 
 
  
JSlambdas-and-anonymous-delegates.js
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();
	}
}; 

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

C#user-defined-operators.cs
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();
	}
} 
JSuser-defined-operators.js
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); 

User-defined conversions are fully supported.

C#user-defined-conversions.cs
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();
	}
} 
JSuser-defined-conversions.js
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);
};
 
 
  

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

C#method-overloading.cs
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();
	}
} 
JSmethod-overloading.js
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); 

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#constructor-overloading.cs
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();
	}
}
  
JSconstructor-overloading.js
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); 

Both object and collection initializers are fully supported.

C#obj-col-initializers.cs
public class C {
	public string Property;
	public List Collection;

	public C() {
		Collection = new List();
	}
}

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();
	}
}
 
 
  
JSobj-col-initializers.js
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();
	}
}; 

foreach Supported

The C# foreach statement is fully supported.

C#foreach.cs
public class Driver {
	public void Main() {
		var list = new List { "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();
	}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
  
JSforeach.js
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); 

The C# using statement is fully supported.

C#using-statement.cs
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();
	}
}
 
 
 
 
 
 
 
 
 
 
  
JSusing-statement.js
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); 

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#exception-handling.cs
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();
	}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
  
JSexception-handling.js
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); 

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

C#named-and-default-arguments.cs
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();
	}
} 
JSnamed-and-default-arguments.js
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); 

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#variable-capture-semantics.cs
public class Driver {
	public void Main() {
		var actions = new List();

		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();
	}
}
 
 
 
[/cs]
[/left]
[right]
[js title="variable-capture-semantics.js"]
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);
[/js]
[/right]
[/compare]
[/feature]

[feature id="evaluate-expressions" title="Always evaluate expressions left-to-right" expanded="false"]

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#evaluate-expressions.cs
 
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(); } }                            
JSevaluate-expressions.js
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); 

Properties Supported

Both manually and automatically implemented properties are supported.

C#properties.cs
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();
	}
}
 
 
 
 
 
 
 
 
 
  
JSproperties.js
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); 

Indexers Supported

Indexers (including virtual and overloaded ones) are supported.

C#indexers.cs
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();
	}
}
 
 
 
 
  
JSindexers.js
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); 

Events Supported

Both manually and automatically implemented events are supported.

C#events.cs
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();
	}
}
 
 
 
 
 
 
 
 
  
JSevents.js
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); 

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

C#nullable-types.cs
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();
	}
}
  
JSnullable-types.js
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); 

dynamic Supported

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

C#dynamic.cs
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();
	}
} 
JSdynamic.js
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); 

Nested types Supported

Nested types are supported.

C#nested-types.cs
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();
	}
}
 
 
 
 
  
JSnested-types.js
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); 

C#query-expressions.cs
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();
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  
JSquery-expressions.js
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); 

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#goto-case.cs
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();
	}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  
JSgoto-case.js
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); 

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#iterator-blocks.cs
public class C {
	private StringBuilder _sb;

	public C(StringBuilder sb) {
		_sb = sb;
	}

	public IEnumerable 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();
	}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
[/cs]
[/left]
[right]
[js title="iterator-blocks.js"]
// 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);
[/js]
[/right]
[/compare]
[/feature]

[feature id="async" title="async" expanded="false"]

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

C#async.cs
 
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"); }); } }                                                                            
JSasync.js
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 are fully supported.

C#multi-dimensional-arrays.cs
 
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(); } }                                                     [/cs] [/left] [right] [js title="multi-dimensional-arrays.js"] 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); [/js] [/right] [/compare] [/feature] [feature id="expression-trees" title="Expression trees" expanded="false"]

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

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.

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 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 Supported

Named references and the accompanying 'extern alias' statement is an obscure feature of the C# language which is not 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 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 Not Supported

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