Skip to content

What is E#

E# is a general-purpose programming language for the .NET Common Language Runtime (CLR). Source files end in .es, and they compile through Mono.Cecil straight to ordinary .NET assemblies. A type written in E# is a real CLR type: a C# project references the .dll and uses it without knowing or caring that it wasn’t C#.

It is not ECMAScript (the .es extension is a collision, nothing more), and it is not a preprocessor or a dialect of C#. It is its own language that shares C#‘s runtime.

E# started from a wish list, not a gap in the market. The type system should read like Go — small, regular, few surprises — but go further than Go does: real generics, classes when a problem wants them, and a good chunk of the ideas Rust gets right in its type system (tagged unions you match exhaustively, errors as values, the -> return syntax). Concurrency modelled on what Go and Swift do well. And an object model that lands between Go and C# — more structure than Go, far less ceremony than C#. That combination didn’t exist on the CLR, so E# is it.

Targeting the CLR was a goal from day one, and that’s where the second pillar comes in. Because E# is a first-class CLR citizen, it can do something .NET never really had. The JVM has long been polyglot in the small: Java, Scala, and Kotlin mix inside one project, not just across a package boundary. The CLR never got that — you had C#, and while F# exists, it doesn’t drop into a C# codebase the way Scala drops into a Java one.

E# closes that case. .es and .cs files compile into a single assembly — drop both kinds in a directory and point the compiler at it: the E# half emits IL through Mono.Cecil, Roslyn compiles the C# half, and ILRepack fuses both into one .dll with bidirectional references. (Pure-E# code skips Roslyn entirely.) No NuGet boundary between them, no interop attributes, no two-projects-and-a-reference dance. This was an early priority precisely because it’s the payoff of being native to the runtime rather than bolted on after — a big part of the language, even if it isn’t the whole reason for it.

E# borrows the readability of modern languages and puts it on a runtime many of us already use:

  • -> return arrows and name-before-type annotations (func add(a: int, b: int) -> int)
  • expression-bodied functions (func double(x: int) -> int = x * 2)
  • value types by default, with the object world available when you ask for it
  • errors as values; match over tagged unions; uncolored-by-default async (semi-colored when you want the machinery)

The goal is not to out-feature C#. C# is, by feature count, about the largest language going (before you even reach its BCL). E# goes the other way: a smaller, composable core. Modern, object-oriented, and — relative to C# — simple.

namespace Geometry
struct Point { x: int, y: int }
// A receiver block makes `dist2` a method on `Point` —
// composition by attachment, instead of methods trapped inside a class body.
func (p: Point) dist2() -> int = p.x * p.x + p.y * p.y
func demo() -> int {
let p = Point { x: 3, y: 4 }
return p.dist2() // 25 — called as a method
}

The surface is deliberately small — small enough to keep in your head. It stays that way less by leaving things out than by rarely offering the same thing twice: where there are two ways to do something, they are usually two slightly different things, each there because it says what the other can’t.

The borrowings follow the same instinct. Bindings read as they do in Swiftlet and var. Pointers and the verbs around them come from Go*T, &x, and methods attached to a type by a receiver rather than enclosed in its body. The result is an object model in a Swift register with Go’s handling of pointers and dispatch, and little stacked above it.

E# does most of what C# does, with less to learn — and often by framing a problem differently. That reframing is much of the point: for the programs where it fits, the work becomes simpler to reason about, not merely shorter to write.

Object-oriented, with a procedural front door

Section titled “Object-oriented, with a procedural front door”

Two type kinds cover the story: struct (a value type) and class (identity, the object world); static gives you a static class. Methods attach to a type through a Go-style receiver block rather than living inside it (or sit directly in a class body), and composition leans on interface conformance plus those receiver methods. Inheritance exists on class but is opt-in and sealed by default — the opposite of C#‘s “inheritable unless you say otherwise.”

If you want a one-line placement: E#‘s object-orientation sits between Go and C# — more than Go (which has no classes or inheritance), less than C# (where everything is a class).

That choice extends to how a program starts. An executable is entered through main, in either shape: a bare top-level func main() (the program is a function), or a main method on a class Program (the program is an object). The class-style needs no launcher — the compiler enters it as Program().main():

// function-style // class-style
func main() -> int { class Program {
return run() func main() -> int { return run() }
} }

E# is imperative and object-oriented at its core, in the C family: statements and braces, explicit mutation, types written at their declarations and checked locally. It carries the toolkit newer languages converge on regardless of paradigm — tagged unions, exhaustive match, errors as values, the -> in a signature — and it supports a functional style where one reads well, with closures, higher-order functions, and guaranteed tail calls. These are facilities within an imperative language, not a turn toward functional-first programming.

Two choices mark that boundary. Type inference is local: annotations live at declarations and boundaries, and the compiler does not reconstruct a program’s types globally from their uses. And there are no functional-first defaults — nothing is immutable by convention, and no pipeline idiom stands in for ordinary control flow. On the CLR this places E# beside C# rather than F#: where F# is the runtime’s ML — expression- and inference-led — E# keeps the C-family register, with explicit let/var, value types, and *T and & for sharing and address, while drawing its surface ergonomics from Swift and Go.

E# is general-purpose — it covers the same range as any other CLR language, with no single intended niche. Where it slots in is shaped by mechanics rather than preference:

  • In an existing .NET project.es and .cs compile into one assembly, so E# can be adopted incrementally, a file at a time, instead of as a separate project.
  • As a library — an E# type is an ordinary CLR type; C# and F# consume it directly, with no shim.
  • Standalone — pure-E# code compiles straight to an assembly through Mono.Cecil.

E# is pre-alpha. The language is real and tested — the IL compiler is the source of truth, and every assembly the test harness produces is run through ILVerify — but the surface is still moving and some corners are unfinished.

The most honest picture of what compiles today is the corpus: a large set of real .es programs drawn from the language’s own test suite and samples — passing test cases, showcases, and integration tests — each one compiled cleanly (or rejected with its expected diagnostic) and either producing its asserted output or standing as a real, well-formed program. Start there, or walk the guide: