PONY λ M2 Modula-2
for C# programmers

You already know C#.Now explore other languages.

Side-by-side, interactive cheatsheets for C# programmers
comparing C# to other languages. Every example runs live in your browser — no setup, no installation.

▶ Start with Ruby Browse comparisons ↓

Choose your own path by reordering languages

Ruby ⚡ Works Offline ⚡ Offline

What C# developers reach for when they want expressiveness over ceremony. Ruby drops static, explicit types, and boilerplate for a world where everything is an object, blocks replace delegates, and open classes let you add methods to Integer at runtime.

  • Dynamic typing — no type annotations, no compile step; a variable springs into being on assignment and can hold any object at any time
  • Blocks and Enumerable — map, select, reduce, &:upcase replace LINQ's Select/Where/Aggregate without the lambda verbosity
  • Everything is an object — 42.times, nil.nil?, true.class all work; no primitive/boxing distinction
  • Open classes — reopen String, Integer, or any class and add methods at runtime; no extension-method limitation
  • Modules and mixins — include Comparable or Enumerable to gain 50+ methods for free, replacing C# interface boilerplate
  • Postfix conditionals and unlessputs value if valid? reads like plain English; no one-line if (cond) stmt; needed
Go Pre-Alpha

A familiar statically typed language — without the object-oriented ceremony. A C# developer reaches for Go when they want single-binary deployment, goroutines instead of async/await, and errors-as-values instead of exceptions — trading .NET's rich ecosystem for radical simplicity.

  • Goroutines — thousands of concurrent tasks at ~2 KB each, no Task/async/await, no thread pool; concurrency is baked into the language
  • Errors are return values, not exceptions — (result, error) pairs make every failure explicit in the function signature
  • No classes or inheritance — structs with methods and implicit interfaces replace the full C# OOP stack
  • Single static binary with no runtime to install — no .NET SDK, no NuGet packages, no .runtimeconfig.json
  • Sub-second compile times for large programs — where dotnet builds take seconds, Go builds take milliseconds
  • Zero values for every type — no null references, no NullReferenceException; every variable is initialized to a safe default
JavaScript Alpha ⚡ Works Offline ⚡ Offline

Static typing and the compiler are gone. A C# developer learning JavaScript trades explicit types, a single class-based object model, and a consistent this for a dynamic runtime with no safety net — but async/await syntax carries over almost unchanged.

  • var/let/const vs. C#'s var — a classic false friend: C#'s var is still statically typed, JavaScript's is not typed at all
  • Dynamic typing everywhere: no compiler, and no runtime enforcement even with JSDoc annotations
  • Prototypal inheritance vs. class-based OOP — ES6 class is sugar over a fundamentally different, mutable prototype-chain object model
  • this is determined by the call site, not by where a method is defined — pulling a method off its object silently breaks it, unlike C#'s consistent instance this
  • ==/=== and coercion rules where C# has a single, non-coercing ==
  • A strong bridge point: async/await syntax is nearly identical to C#'s, though JavaScript runs on one thread with an event loop instead of a thread pool
  • Array methods (map/filter/reduce) mirror LINQ almost one to one — a C# developer's LINQ intuition transfers directly
  • Two empty values, null and undefined, where C#'s nullable reference types track a single null
Python Beta ⚡ Works Offline ⚡ Offline

Python is dynamically typed, interpreted, and indentation-driven — the near-opposite of C# in every technical decision, yet dominant in data science, machine learning, and scripting. The same async/await keywords, the same OOP concepts, but no compiler, no braces, and no type declarations.

  • No type declarations — a variable is just an assignment; type hints (name: str) are advisory, not enforced by the runtime
  • Significant whitespace replaces braces and semicolons: a colon ends every header line, indentation defines the body
  • List comprehensions ([n*n for n in range(10) if n % 2 == 0]) replace LINQ's .Where(...).Select(...).ToList() in one readable expression
  • @dataclass mirrors record: auto-generates constructor, equality, and repr from field annotations; add frozen=True for immutability
  • No method overloading — last definition wins; use default arguments, *args/**kwargs, or isinstance() checks instead
  • asyncio replaces Task/Thread — same async/await keywords, single-threaded event loop, no thread pool; asyncio.gather() is Task.WhenAll()
Kotlin Pre-Alpha

The same industrial tier — static types, GC, LINQ-shaped pipelines, even the same out/in variance keywords — but the defaults all tighten. Null safety is enforced instead of advisory, classes are final unless opened, collections are read-only unless made mutable, and == is structural everywhere.

  • Null safety with teeth: C#'s nullable annotations warn and are erased; Kotlin's String? is a distinct type and dereferencing it without a check is a compile error — and !! actually throws at the assertion, unlike the erased !
  • Everything is an expression: if, when, and try all return values — no ternary operator, no mutable placeholder before a try block
  • sealed + when gives compiler-proven exhaustiveness a C# switch over a class hierarchy cannot offer — no defensive _ arm
  • == always calls equals (structural), with === for reference identity — inverting the C# default for classes
  • The LINQ vocabulary under FP names — Wherefilter, Selectmap — but EAGER by default; asSequence() opts back into deferred pipelines
  • No static: companion objects and object singletons; data class plays record (with copy() for with), and property delegation (by lazy, by interface forwarding) has no C# analog
  • Coroutines instead of async/await: suspend calls need no await keyword, return types stay unwrapped (String, not Task<string>), and scopes own their children
Rust Pre-Alpha

Memory safety without a runtime — Rust gives C# developers zero-cost performance with compile-time guarantees. Where the .NET GC runs in the background, Rust's ownership model eliminates allocations entirely — no pauses, no null, no data races, and no exceptions.

  • Ownership & borrowing — the compiler tracks who owns each value and when it is freed; no GC, no finalizers, no IDisposable
  • No null — absence is Option<T>; the compiler forces you to handle None before you can use the value
  • No exceptions — fallible functions return Result<T, E>; the ? operator propagates errors without try/catch
  • Enums with data — a Shape::Circle(f64) variant is a true algebraic type; no sealed class hierarchy needed
  • Zero-cost abstractions — traits and generics are monomorphised at compile time; the runtime cost is identical to hand-written specific code
  • Two string types — &str (borrowed slice) and String (owned buffer) — replacing C#'s single heap-allocated string
Swift Pre-Alpha

The closest big-language cousin C# has — inference, properties, generics, async/await all rhyme — but the defaults invert. Value types are the culture (String, Array, Dictionary are all structs), optionals are enforced instead of advisory, enums carry payloads, and ARC replaces the garbage collector.

  • Value types as the default modeling tool: struct everywhere, copy-on-write collections, mutating visible in signatures — the aliasing bugs C# collections invite cannot happen
  • Optionals with teeth: String? is a real enum you must unwrap (if let, guard let) — C#'s erased nullable-reference warnings become compile errors, and dictionary lookups return optionals instead of throwing
  • Enums are sum types: cases carry typed payloads with compiler-proven exhaustive switch — what C# fakes with an abstract record hierarchy and a defensive _ arm
  • Protocol extensions and retroactive conformance: default implementations callable on the concrete type, and any type — even Int — can conform to your protocol after the fact
  • Errors marked at every call site: throws in the signature, try at the call, try? folding failures into optionals — C#'s invisible exception paths are syntactically impossible
  • ARC, not GC: deterministic deinit replaces the IDisposable/using apparatus — but retain cycles become your problem (weak, [weak self])
  • Structured concurrency the compiler enforces: async let and task groups instead of Task.WhenAll discipline, and actor makes data races a compile error where lock was always just convention
TypeScript Alpha ⚡ Works Offline ⚡ Offline

TypeScript is C#'s sibling, not its replacement — same designer (Anders Hejlsberg), same static typing instincts, but running on the JavaScript runtime with structural typing and no runtime type information.

  • Structural typing instead of nominal: a value satisfies an interface by shape alone, with no implements declaration — the biggest conceptual shift from C#
  • One number type (IEEE 754 float) — no int, long, double, or decimal; use a library for financial arithmetic
  • Types are erased at runtime — no reflection, no is MyInterface at runtime; use typeof, instanceof, and discriminant properties instead
  • Two nothingness values: null (explicit absence) and undefined (not set) — always use ===, never ==
  • Union types (string | number), literal types ("north" | "south"), and utility types (Partial<T>, Readonly<T>) replace C# enums, DTOs, and separate nullable variants
  • async/await over a single event loop — no Task.Run, no threads, no ConfigureAwait(false)
Java Pre-Alpha

C#'s OOP cousin on a different runtime. Java and C# share syntax roots, generics, and a garbage-collected VM — but diverge on checked exceptions, generics implementation, async patterns, and the richness of their standard libraries.

  • == compares references for objects — always use .equals() for string and object value equality; the most common C# habit that breaks in Java
  • Checked exceptions force callers to handle declared failures at compile time — C# has no equivalent, so every API decision needs documentation instead
  • No LINQ — Streams API offers similar power (filter/map/collect) but requires explicit terminal operations and type erasure instead of reified generics
  • No extension methods, no properties, no named arguments, no default parameters — Java overloads instead
  • Records (Java 16+) and sealed types (Java 17+) + switch patterns (Java 21+) close most of the C# feature gap for data modeling
  • The JVM ecosystem: Gradle, Maven, Spring, Hibernate — a different but equally rich set of tools to the .NET world
Visual Basic Pre-Alpha

The same .NET runtime, LINQ, and BCL you already use — with English keywords instead of braces. Visual Basic compiles to the identical CLR bytecode as C#, so List(Of T), async/await, and every NuGet package work exactly the same; what changes is syntax and a handful of surprising semantics like case-insensitivity and AndAlso/OrElse.

  • No braces — If...Then...End If, For Each...Next, and every other block closes with an explicit End keyword instead of { }
  • Case-insensitive identifiers — myValue and MyValue are the same name; a real gotcha when translating C# code that relies on case to distinguish names
  • AndAlso / OrElse for short-circuit logic, plain And / Or for non-short-circuit — C#'s &&/|| vs &/| distinction, spelled out as words
  • LINQ query syntax reads like SQL (From x In list Where ... Select ...) as an alternative to the method-chain syntax C# developers default to — both compile to the same calls
  • Nothing instead of null, Me instead of this, MyBase instead of base — same concepts as C#, different vocabulary
  • Option Strict On is not the default — turn it on explicitly, or Visual Basic silently allows the implicit narrowing conversions C# always forces you to make explicit with a cast
F# Pre-Alpha

Functional-first on the same .NET runtime — without the ceremony of classes. F# starts where C# leaves off: immutability by default, discriminated unions instead of sealed hierarchies, and exhaustive pattern matching enforced by the compiler.

  • Immutable by default — let bindings cannot be reassigned; mutation requires the explicit mutable keyword, making side effects visible
  • Discriminated unions replace sealed class hierarchies — define type Shape = Circle of float | Rectangle of float * float and pattern-match exhaustively
  • The |> pipe operator threads data through transformations top-to-bottom, replacing LINQ method chains with readable functional pipelines
  • Option<'T> and Result<'T, 'E> replace null and exceptions for expected failure paths — no NullReferenceException, no hidden throws
  • Type inference is far more aggressive — parameter types, return types, and generic type arguments are almost never written explicitly
  • Full .NET interop — every NuGet package, every BCL type, every C# library works from F# with no binding layer
GDScript Pre-Alpha

Godot's built-in scripting language — Python-flavored, dynamically typed, no compilation step, and designed around Godot's Node tree. C# developers often use GDScript for rapid prototyping and C# for performance-critical systems within the same Godot project.

  • Dynamic typing by default — var x = 42 is a Variant; opt-in type annotations with var x: int = 42
  • match replaces switch — matches ranges, arrays, dicts, and bind-patterns; far more powerful than C# switch
  • Signals replace events — signal health_changed(new_health) is the observer pattern baked into the engine; no delegates required
  • Lambdas are Callables — var double = func(x): return x * 2 is explicit; invoke with double.call(5) rather than direct application
  • := for typed inference — var count := 42 infers int; without := the variable is untyped Variant
Odin Pre-Alpha

Everything the runtime did for you, done by hand — and one thing it never could. Odin drops the garbage collector, classes, interfaces, LINQ, and async/await, then hands you Span<T> as the ordinary case, real discriminated unions, and the struct-of-arrays layout Unity built a second runtime to reach.

  • No garbage collector: defer delete(...) beside each allocation, or an arena that drops a thousand of them at once — the bargain ArrayPool, stackalloc, and GC tuning were all approximating
  • No classes and no reference types: a struct is plain data, assignment copies, and sharing is an explicit ^T — so "is this a reference or a copy?" has one answer
  • No LINQ, because there are no capturing lambdas — a .Where().Select().Sum() chain becomes one loop with nothing allocated per stage
  • No interfaces: a tagged union is the discriminated union C# has wanted for five versions, with real exhaustiveness checking and no boxing
  • No exceptions and no IDisposable — errors are return values with or_return, and defer replaces both finally and using
  • No async/await and no Task, which is a genuine loss for I/O-bound work — but #soa expresses the DOTS layout change as one directive on the type
Drag cards to reorder · your order is saved locally