PONYΞ»M2Modula-2
CodeCompared
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 RubyBrowse comparisons ↓Explore the language map β†—

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 unless β€” puts value if valid? reads like plain English; no one-line if (cond) stmt; needed
GoPre-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
JavaScriptAlpha⚑ 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
PythonBeta⚑ 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()
CPre-Alpha

What is on the other side of every [DllImport]. Marshalling a struct across that boundary means knowing what the C side looks like in memory β€” and the C# features that exist for this job, Span<T>, stackalloc, fixed and StructLayout, all make more sense once you have seen it.

  • Span<T> is a pointer and a length β€” the pair a C function takes as two parameters, made one type the compiler can check
  • fixed exists because the collector moves objects to compact the heap; C has no collector, so an address stays valid until you free the thing
  • [StructLayout(LayoutKind.Sequential)] is the promise that your declaration order is the memory order β€” without it the CLR may reorder fields and a marshalled struct arrives scrambled
  • A C# long is 64 bits everywhere; a C long is 8 bytes on Linux and macOS and 4 on Windows, so a binding right on one platform can truncate on another with no error
  • A C# string stores its length and may contain \0; marshalling it to char* produces a NUL-terminated copy, so anything after an embedded zero is silently lost
KotlinPre-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 β€” Whereβ†’filter, Selectβ†’map β€” 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
RustPre-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
SwiftPre-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
TypeScriptAlpha⚑ 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)
JavaPre-Alpha⚑ Works Offline⚑ Offline

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
C++Pre-Alpha

You already have the ideas β€” what changes is that the guardrails are gone. A C# developer arrives with value types, ref/in/out, using blocks, Span<T>, stackalloc, unsafe, reified generics and explicit virtual/override. So this is not a page about what a value type is; it is a page about the four ways your struct and C++'s differ, which is why Value Types comes first.

  • 🚨 struct and class differ only by default access β€” both are value types, both copy on assignment. Treat every C++ type as if it were a C# struct, and reach for a pointer or reference when you want class behavior
  • using becomes the destructor, and the obligation moves from every call site into the type β€” so the whole Dispose pattern, finalizer and SuppressFinalize included, is deleted rather than translated
  • ref is &, in is const&, and out becomes a returned std::optional β€” but 🚨 the call site is not marked, so const& is the only way a reader can tell
  • Span<T> is std::span and stackalloc is just a local array β€” the habits transfer exactly, but nothing stops you storing a span that outlives what it points into
  • Copies are DEEP by default, which is the reverse of a C# struct holding a reference β€” hence the rule of five, and the rule of zero that replaces it
  • LINQ becomes ranges (Where is filter, Select is transform), and concepts are where clauses that can require an operator rather than only an interface
  • 🚨 Signed overflow is undefined behavior, not wrapping and not OverflowException β€” there is no checked to reach for
Visual BasicPre-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
GDScriptPre-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
RocPre-Alpha

A fast, friendly, purely functional language from Richard Feldman β€” no garbage collector, no lifetimes, no nulls: memory is managed by compile-time reference counting, and all I/O is delegated to a host "platform" written in another language.

  • Purely functional with managed effects β€” effectful functions are marked with a ! suffix (main!, echo!), so purity is visible in every signature
  • Compile-time reference counting instead of a garbage collector or ownership annotations β€” memory safety with zero programmer bookkeeping
  • Tag unions with exhaustive match β€” the compiler catches every unhandled case
  • The Try type replaces both nil and exceptions β€” every fallible operation returns Ok or Err
  • The platform model β€” a Roc program cannot perform I/O on its own; a host written in Rust, Zig, or another language provides every effect
OdinPre-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