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
data Point { x: int, y: int }
// A free function whose first parameter is `Point` becomes a method on it —
// composition by attachment, instead of methods trapped inside a class body.
func dist2(p: Point) -> 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
}

Object-oriented, with a procedural front door

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

There is no class keyword. data (a value type) and ref data (a class, with identity) cover the type story; static func gives you a static class. Methods attach to a type through instance-method promotion rather than living inside it, and composition leans on interface conformance plus that promotion. Inheritance exists on ref data 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 ref data 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 { ref data Program {
return run() func main() -> int { return run() }
} }

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: