Why N#?
The best parts of Go's simplicity, built for the .NET ecosystem.
Discriminated Unions
First-class union types with exhaustive pattern matching. The compiler catches missing cases at build time.
union Shape {
Circle { radius: double }
Rect { width: double, height: double }
}
func Area(s: Shape): double {
return match s {
Shape.Circle { radius } => 3.14 * radius * radius,
Shape.Rect { width, height } => width * height
}
}
Duck Interfaces
Structural typing without boilerplate. If a type has the right methods, it matches the interface.
duck interface IReader {
func Read(): string
}
class FileReader {
func Read(): string {
return "file contents"
}
}
// FileReader matches IReader automatically
func Process(r: IReader) {
print r.Read()
}
Go-Style Syntax
Short variable declarations with :=, no semicolons, convention-based visibility. Less noise, more signal.
func Main() {
name := "Alice" // type inferred
let items = [1, 2, 3] // immutable binding
count: int = 0 // explicit type
for item in items {
count += item
}
print $"{name}: {count}" // string interpolation
}
Pragmatic C# Interop
Use NuGet packages and call C# libraries in covered interop scenarios. N# is designed for CLR integration, with limitations documented instead of hidden.
import Microsoft.AspNetCore.Builder
func main(args: string[]) {
builder := WebApplication.CreateBuilder(args)
app := builder.Build()
app.MapGet("/", () => "Hello from N#!")
app.MapGet("/json", () => new {
Message: "Works with ASP.NET Core"
})
app.Run()
}
Tooling That Is Becoming the Product
A broad pre-release CLI surface modeled on Go/Rust inner loops, with structured JSON on the key automation commands used by agents.
nlc CLI
A broad pre-release CLI surface modeled on Go/Rust inner loops: check, format, lint, test, perf reports, and related project commands.
nlc check && nlc format --check && nlc lintVS Code Extension
The public installer adds the N# VS Code extension when the code CLI is available, with the language server bundled for editor features.
code --install-extension nsharp.nsharpLLM-First Queries
`check`, `fix`, `query`, and `lint` default to structured JSON; other commands expose JSON where implemented for automation and AI agents.
nlc query inspect --file main.nl --pos 10:5Auto-Fix
Compiler-suggested fixes for supported scenarios such as missing imports and cleanup. Use --dry-run in CI or review before applying.
nlc fix --dry-runDependency Management
Add, remove, update, audit, and visualize your NuGet dependencies. Detect unused packages with nlc tidy.
nlc add Serilog@3.1.1 && nlc treeStatic Analysis
Static analysis covers supported rules such as unused variables, missing imports, async-without-await, and unreachable code. Verify rule coverage against current nlc help/tests.
nlc lint --textQuick Start
One copied command installs nlc, templates, SDK restore support, the language server, and VS Code tooling when VS Code is on PATH.
curl -fsSL https://raw.githubusercontent.com/schneidenbach/nsharplang/main/scripts/install.sh | bash && . "$HOME/.nsharp/env"
nlc doctor
nlc new MyApp
cd MyApp
nlc run
N# in Action
Real code, real syntax. No pseudocode.
import System
func Main() {
name := "World"
print $"Hello, {name}!"
}
func Divide(a: int, b: int): int {
if b == 0 {
throw new Exception("divide by zero")
}
return a / b
}
func Main() {
result, err := Divide(10, 0)
if err != null {
print $"Error: {err.Message}"
}
}
union Result {
Success { value: int }
Failure { error: string, code: int }
}
func Handle(r: Result): string {
return match r {
Result.Success { value } =>
$"OK: {value}",
Result.Failure { error, code } =>
$"Error {code}: {error}"
}
}
import System.Linq
func Main() {
let names = ["Alice", "Bob", "Charlie", "Dave"]
result := names
.Where(n => n.Length > 3)
.Select(n => n.ToUpper())
.ToList()
foreach name in result {
print name
}
}
record Point {
X: int
Y: int
}
class Logger(name: string) {
func Log(msg: string) {
print $"[{name}] {msg}"
}
}
func Main() {
p := new Point { X: 10, Y: 20 }
p2 := p with { X: 30 }
print $"({p2.X}, {p2.Y})"
}
func Add(a: int, b: int): int => a + b
test "Add returns correct sum" {
assert Add(2, 3) == 5
assert Add(-1, 1) == 0
assert Add(0, 0) == 0
}
test "Add handles large numbers" {
result := Add(1000000, 2000000)
assert result == 3000000
}