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.
Why it exists
Section titled “Why it exists”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.
The shape of it
Section titled “The shape of 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;
matchover 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}Simple by design
Section titled “Simple by design”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 Swift — let 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-stylefunc main() -> int { class Program { return run() func main() -> int { return run() }} }A C-family language
Section titled “A C-family language”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.
Where it’s a good fit
Section titled “Where it’s a good fit”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 —
.esand.cscompile 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.
Status
Section titled “Status”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:
- Types —
struct,class,union,enum,interface - Functions & methods — free functions, receiver methods,
static - Control flow & match —
if, loops, and pattern matching - Errors: Result & ? — errors as values
- Pointers & memory —
*T,new,&, and the mutation triad - Async & concurrency — uncolored async, channels, structured concurrency
- Interop & delegates — the polyglot payoff
- Showcase — curated, heavily-commented programs end to end