# The Compiler (CSharpToJs) > 🌐 This page in: English Β· **[PortuguΓͺs](Compiler-pt-BR)** The compiler is the core component that enables the magic of **eQuantic.UI**. It transforms C# semantics into efficient and readable TypeScript code. Correctness is held by a **differential conformance suite**: the same C# is evaluated in .NET and in the embedded Bun, and the two answers must agree. Anything the compiler cannot faithfully translate is a **build error with a location** (the `EQ2xxx` diagnostics), never silent wrongness. ## πŸ›‘οΈ Boundaries (Server vs Client) To keep browser code honest, the compiler enforces strict boundaries, inspired by **Next.js** (Server/Client split) and **Flutter** (constraints), and validates them _before_ emitting JS: - **Client components** (`StatefulComponent` / `StatelessComponent`): UI logic, state management, `System.Linq`, basic types (`string`, `int`, `DateTime`). - **Forbidden on the client**: `System.IO`, direct `System.Net.Http`, blocking `.Wait()`. `File.ReadAllText()` in a component body is a build error. - **The bridge**: data fetching goes through methods annotated with `[ServerAction]` (RPC style). See [Security & Server Actions](Security). ## πŸ› οΈ Compiler Components ### 1. TypeScriptEmitter The `TypeScriptEmitter` is the entry point for generating `.ts` files. It organizes imports, defines classes, and uses the `CSharpToJsConverter` to convert method bodies. ### 2. CSharpToJsConverter Dispatches every Roslyn node to a strategy β€” one per construct (`BinaryExpressionStrategy`, `IfStatementStrategy`, …) β€” and returns **IR**, not text. Statements always build a `JsStatement`. An expression strategy that has crossed over builds a `JsExpr` (`IExpressionIrStrategy`); one that still returns text is spliced as an *opaque* node, byte-identical to what it always produced. That boundary is what lets the migration proceed one strategy at a time, and `tests/eQuantic.UI.Compiler.Tests/Coverage/ir-migration.baseline.txt` is the list of text strategies β€” it only ever shrinks, and a new strategy is born on the IR. ### 3. SourceMapGenerator Generates standard V3 Source Maps with Base64 VLQ encoding, mapping generated JavaScript/TypeScript back to the original `.cs` or `.eqx` source lines. ### 4. Symbols first, names only where honest The converter asks the semantic model before it guesses. A member, a local, a parameter, a static β€” each emits from its symbol (`this.name`, `Widget.name`, a bare name), and `Console.WriteLine` is `console.log` because the symbol says `System.Console`. Name heuristics (a leading underscore, a capitalised property) are legal only where the model cannot be asked β€” a snippet with no model, a node a strategy rewrote. Under an authoritative model an in-tree name that does not bind is a build error (EQ2006), never a guessed translation. ### 5. The IR and its writers *Since **0.2.0-preview.36*** `CodeGen/Ir/` is a small tree with one writer per level β€” `JsExpr` β†’ `JsStatement` β†’ `JsClassMember` β†’ `JsClass` β†’ `JsModule` β€” and the writers own everything a strategy must never hand-write: - **Parentheses** come from precedence and associativity, never from a template. Before this, `f ?? g && g` shipped verbatim: C# needs no parentheses there, JavaScript refuses the bare mix, and the whole bundle failed to parse. - **Single evaluation**: a `JsTemplate` names what it computes (`{0} === {0}.normalize()`) and the writer binds a part used more than once exactly once β€” a plain name or literal is inlined, a member read is not (a getter may count). - **Layout**: one statement per line, blocks indented (`JsLayout.Pretty`); `Compact` reproduces the former string world byte for byte, which is how each migration step is proven. A class has one layout rule β€” a blank line before a member with a body, fields contiguous β€” and a module is its imports as `JsImport` records, a blank line, its body. - **The emitter** (`TypeScriptEmitter`) decides *what* a module contains and hands nodes to the builder; it assembles no text. Two nets hold all of it: the component pins (every shared component's generated module, byte for byte β€” a layout change must be whitespace-only against them) and the conformance suite, which executes every translated shape on both sides and compares the answers. The generated twins under `src/eQuantic.UI.Runtime/src/shared/components` are pinned, type-checked and tested β€” not linted: generated code answers to its writer, not to a style guide. ## πŸ”„ Supported Strategies Currently, the compiler supports a wide range of C# constructs: - **Expressions**: Arithmetic, Logical, Ternary, String Interpolation, Null-coalescing (`??`), Conditional Access (`?.`, `?[]`) - **Control Flow**: `if`, `switch`, `for`, `foreach`, `while`, `do-while`, `break`, `continue`, `throw` - **Modern Patterns**: Full support for Recursive, Property, Positional, Relational, and Logical patterns (C# 9.0 - 12.0) - **Resource Management**: Support for `using` statements and `using var` declarations - **Exceptions**: Full support for `try-catch-finally` and `throw` statements (Exception β†’ Error) - **Indexes and Ranges**: Support for index-from-end operator (`array[^1]` β†’ `array[array.length - 1]`) - **String Methods**: Instance methods (`Split`, `Replace`, `StartsWith`, `EndsWith`, `Contains`, `Substring`, `IndexOf`, `LastIndexOf`, `PadLeft`, `PadRight`, `Trim`, `TrimStart`, `TrimEnd`, `ToUpper`, `ToLower`, `ToUpperInvariant`, `ToLowerInvariant`, `Insert`, `Remove`, `ToCharArray`) and static methods (`IsNullOrEmpty`, `IsNullOrWhiteSpace`, `Join`, `Concat`, `Compare`, `Equals`, `Format`) - **Number Methods**: `int.Parse`, `double.Parse`, `float.Parse`, `decimal.Parse`, `long.Parse`, `int.TryParse`, `double.TryParse` - **List Methods**: `Add`, `AddRange`, `Insert`, `InsertRange`, `Remove`, `RemoveAt`, `RemoveRange`, `RemoveAll`, `Clear`, `IndexOf`, `LastIndexOf`, `Find`, `FindIndex`, `FindLast`, `FindLastIndex`, `FindAll`, `Exists`, `TrueForAll`, `Sort`, `ForEach`, `GetRange`, `CopyTo`, `BinarySearch` - **Array Static Methods**: Full support for Array static methods - `Array.Sort(array)` β†’ `array.sort()` - Sort array in place - `Array.Sort(array, comparison)` β†’ `array.sort(comparison)` - Sort with custom comparer - `Array.Reverse(array)` β†’ `array.reverse()` - Reverse array in place - `Array.Find(array, predicate)` β†’ `array.find(predicate)` - Find first matching element - `Array.FindIndex(array, predicate)` β†’ `array.findIndex(predicate)` - Find index of first match - `Array.FindAll(array, predicate)` β†’ `array.filter(predicate)` - Find all matching elements - `Array.IndexOf(array, value)` β†’ `array.indexOf(value)` - Find index of value - `Array.LastIndexOf(array, value)` β†’ `array.lastIndexOf(value)` - Find last index of value - `Array.Exists(array, predicate)` β†’ `array.some(predicate)` - Check if any element matches - `Array.TrueForAll(array, predicate)` β†’ `array.every(predicate)` - Check if all elements match - `Array.Clear(array)` β†’ `array.splice(0)` - Clear all elements - `Array.Resize(ref array, size)` β†’ `array.length = size` - Resize array - **Enum Methods**: Full enum operations support - `Enum.Parse(string)` β†’ `parseEnum(value, EnumType)` (case-insensitive) - `Enum.TryParse(string, out var result)` β†’ `(result = parseEnum(value, EnumType), result !== undefined)` - `Enum.GetValues()` β†’ `Object.values(EnumType)` - Get all enum values - `Enum.GetNames()` β†’ `Object.keys(EnumType)` - Get all enum member names - `Enum.IsDefined(typeof(T), value)` β†’ `(EnumType[value] !== undefined)` - Validate enum value - **Dictionary Methods**: Complete Dictionary/IDictionary support - `ContainsKey(key)` β†’ `(key in dict)` - Check if key exists - `TryGetValue(key, out var value)` β†’ `(value = dict[key]) !== undefined` - Safe value retrieval - `Add(key, value)` β†’ `dict[key] = value` - Add or update entry - `Remove(key)` β†’ `delete dict[key]` - Remove entry - `Clear()` β†’ `Object.keys(dict).forEach(k => delete dict[k])` - Remove all entries - `Keys` (property) β†’ `Object.keys(dict)` - Get all keys as array - `Values` (property) β†’ `Object.values(dict)` - Get all values as array - **LINQ**: Direct conversion of LINQ methods to JS equivalents: - **Projection**: `Select` β†’ `map`, `SelectMany` β†’ `flatMap` - **Filtering**: `Where` β†’ `filter`, `Distinct` β†’ `[...new Set()]` - **Ordering**: `OrderBy/OrderByDescending` β†’ `sort`, `Reverse` β†’ `[...arr].reverse()` - **Partitioning**: `Skip` β†’ `slice(n)`, `Take` β†’ `slice(0, n)` - **Element**: `First/FirstOrDefault` β†’ `find/[0]`, `Last/LastOrDefault` β†’ `arr[arr.length-1]`, `Single/SingleOrDefault` β†’ `find/[0]` - **Quantifiers**: `Any` β†’ `some/length > 0`, `All` β†’ `every`, `Contains` β†’ `includes` - **Aggregation**: `Count` β†’ `length/filter().length`, `Sum` β†’ `reduce((a,b) => a+b, 0)`, `Average` β†’ `reduce()/length`, `Min` β†’ `Math.min(...)`, `Max` β†’ `Math.max(...)` - **Set Operations**: - `Concat(other)` β†’ `[...source, ...other]` - Concatenate two sequences - `Union(other)` β†’ `[...new Set([...source, ...other])]` - Unique elements from both sequences - `Intersect(other)` β†’ `[...new Set(source)].filter(x => other.includes(x))` - Common elements - `Except(other)` β†’ `[...new Set(source)].filter(x => !other.includes(x))` - Elements in source but not in other - **Type Filtering**: - `Cast()` β†’ passthrough (JavaScript is dynamically typed) - `OfType()` β†’ `filter(x => typeof x === 'type')` for primitives, `filter(x => x instanceof Type)` for objects - **Async/Await**: Mapping of `Task` to `Promise` and native `await` support. - **Modern C# Operators**: Support for modern C# operators and keywords - **Null-coalescing assignment**: `x ??= value` β†’ `x ?? (x = value)` - Assign only if null/undefined - **nameof operator**: `nameof(variable)` β†’ `'variable'` - Get name as string at compile time - **default keyword**: `default(int)` β†’ `0`, `default(string)` β†’ `null`, `default` β†’ `undefined` - Get default value for type --- ## What C# gives you for free, and JavaScript does not Two defaults are implicit in C# and absent in JavaScript. Both were emitted as nothing for a while, and both fail LATE, not at build time, and not where the cause is. ### An unset value type is ZERO *Since **0.2.0-preview.22*** A field of a value type is zero whether or not anyone wrote `= 0`. On the client it was `undefined`, and the two are not the same value. Reads survive by luck for as long as they are TESTS (`undefined > 0` is false, which is what 0 would have said), and then the first ARITHMETIC turns it into NaN. `Math.max(width, undefined)` reaches the stylesheet as `width:NaNpx`, a rule the CSS parser drops whole: the class is computed, hashed, emitted, put on the element, and does nothing. It shows up on client-RENDERED pages only, never on SSR or a direct load, because the server computes the same property in C# where it was 0 all along. So the two targets disagree about one field and the page that proves it is the one nobody reloads. Every non-nullable value type now carries its default (`0`, `false`, an enum's zero member). Nullable ones do not, because there `null` IS the C# answer, and inventing a zero would be the same divergence pointing the other way. ### A primary-constructor parameter is instance STATE *Since **0.2.0-preview.21*** ```csharp public sealed class TocEntry(Action onSeen, string id) : StatelessComponent { public override VisualNode Build(ComponentContext context) => new InView(Heading(id), visible => onSeen(id, visible)); // this.onSeen, this.id } ``` Roslyn models the capture as an `IParameterSymbol`, so every place that asks "is this a parameter?" answers yes about something that behaves like a field. Emitted bare it compiles, the page renders, and the `ReferenceError` arrives whenever the callback finally fires, surfacing from inside the reconciler as a `TypeError` about something else entirely. ## Plain models cross too A component is not the only C# a page needs. The document model behind an editor, a small state machine, a parser: none of them are components, and all of them have to run on both targets. They transpile the same way, as their own modules, and the rules below are what makes the emission CHECKED rather than merely present. ### Ranges are slices ```csharp line[start..end] β†’ line.slice(start, end) line[2..] β†’ line.slice(2) line[..^1] β†’ line.slice(0, -1) line[..^n] β†’ $eq.slice(line, 0, false, n, true) ``` The last shape is the one JavaScript cannot say directly: `^0` means the END, while `slice(0, -0)` is `slice(0, 0)`, which is empty. Anything but a positive literal after `^` therefore resolves against the length the way `Index.GetOffset` does. A `Range` stored as a VALUE is reported (EQ2004): nothing on the other side receives one, and indexing at the point of use is what it is for. ### `out` and `ref` JavaScript has neither. A method that declares them returns an OBJECT (its own value under `$`, each out and ref under its name) and its body moves inside a closure so every `return` in it keeps meaning what it meant. The call site unwraps with an arrow, which works in any expression position including inside an `if`: ```csharp var next = document.Replace(range, text, out var caret); ``` ```js let caret: any; let next = ($o => (caret = $o.caret, $o.$))(document.replace(range, text)); ``` `out` leaves the JS parameter list (it is not passed IN); `ref` stays, because it is read before it is written. `out _` assigns nothing. ### Collections: capacity is not contents `new List(x)` means two opposite things depending on what `x` is, and only the resolved constructor can say which: `new List(other.Count)` is an empty list sized ahead, `new List(other)` is a copy. The first emits `[]`, the second `[...other]`. ### Char arithmetic computes on code units A C# `char` in `+ - * / %` promotes to int and computes on the code unit, while a transpiled char is a 1-length string. When the RESULT type is numeric, char operands lower to code units (constant literals fold to the number; expressions read `charCodeAt(0)`), so `text[i] - '0'` is the digit and `'A' + col` is a number, exactly as in .NET. `(char)numeric` lowers to `String.fromCharCode`. `char + string` stays concatenation: its result type is string, so the numeric branch never sees it. ### A value from the server crosses a TYPED boundary JavaScript has no `decimal` and no 64-bit integer, so the wire sends them as strings β€” a `long` as "9007199254740993", a `decimal` as "0.1", the date family as ISO text. The compiler knows the C# type of every state field and every Server Action's return, so it writes that knowledge into the twin as a `static $hydration` map, and the runtime coerces ONCE, where the value arrives, instead of every use site coercing defensively. A record names its own map, a list is `[spec]`, a dictionary's values are `{ dict: spec }`, and a tuple is positional (`{ tuple: [...] }`) because it crosses as an array. Nothing is emitted where hydration would be the identity, which is most fields. ### A default is decided by the TYPE, and is never null for a value type `new int[0].SingleOrDefault()` is 0 in .NET, not null, and the same goes for `FirstOrDefault`, `LastOrDefault`, `ElementAtOrDefault` and `DefaultIfEmpty`. A field declared without an initializer takes the same default: an `int` is 0, a `bool` false, a `long` 0n, and an ENUM its zero-valued member β€” which is a member-NAME string on this side, so a field left unset would otherwise render nothing at all. One table answers for both, by symbol, so a type reached through an alias (`using Amount = decimal;`) is not a different answer from the type itself. ### A second OrderBy restarts the ordering `xs.OrderBy(a).OrderBy(b)` sorts by `b`. The earlier ordering does not stay the primary key β€” it survives only as the tiebreak a stable sort gives it β€” so it is a different result from `xs.OrderBy(a).ThenBy(b)` whenever `b` has ties that `a` would break. Both cross faithfully: a chained `OrderBy` sorts its source first and sorts that, and `ThenBy` composes its key into the same comparison. ### A half rounds to the EVEN neighbour, like .NET `Math.round(32.5)` is 33 in JavaScript and 32 in .NET, which rounds a half to even. Everywhere the runtime mirrors a `MathF.Round` it uses the .NET rule, so a value computed in the browser is the value the server computed β€” this is what keeps a type scale's line height, and any layout derived from one, identical across hydration rather than a fraction off. ### The LINQ surface is a table, and the numeric BCL is another Most LINQ operators are one shape each β€” `Where` is `filter`, `Aggregate` is `reduce` with its arguments swapped β€” so they live as entries in a table keyed by operator and argument count, gated once, with the IR writer punctuating and binding any receiver used twice. An operator that has to REASON keeps a strategy of its own: the OrDefault family reads the element type, `Sum` picks a seed by it, `Cast` and `OfType` test types, `Contains` chooses between identity and structural equality. The same shape holds for the numeric BCL, where the modern surface (`Double.AcosPi` and its family) is a table of templates rather than a method each. Where those tables have no entry, the call is a build ERROR naming the member, never a guess: a name emitted with nothing behind it is a `ReferenceError` at load, and a page that never renders. ### Fixed-width integers settle by their type A C# `byte` past 255 wraps; a JavaScript number keeps counting. The compiler reads the RESULT type of every `+ - * <<` (and of `++`, `--` and the compound forms) and settles the value where C# would: `byte`, `sbyte`, `short`, `ushort` and `uint` ALWAYS wrap (`& 0xFF`, `<< 16 >> 16`, `>>> 0` β€” packed values and hashes rely on it, so `h *= 16777619` on a `uint` is the FNV step it is in .NET, through `Math.imul`). `int` and `long` wrap only where you wrote `unchecked` (`unchecked(a * a)` is 0 for 65536; a `long` wraps through `BigInt.asIntN`), because every plain `i + 1` in a UI would otherwise carry a `| 0` for an overflow that is a bug anywhere else β€” so a plain `int.MaxValue + 1` keeps the double's count, a documented limit. A `checked` context β€” the block, the expression, or the project-wide setting, read from the bound tree's `IsChecked` rather than from the syntax β€” throws an overflow exactly where C# throws it. A `float` result is rounded to single precision (`Math.fround`) and prints as the shortest decimal that reads back as the same single, so `0.1f + 0.2f` is "0.3" on both sides. `char++` steps the character; an enum in arithmetic (`day + 1`, `a.CompareTo(b)`) computes on the value behind the member name. ### Implicit conversions are settled by the bound tree The syntax never shows an implicit conversion β€” `int i = c` with a char, `Twice(c)`, `a[c]`, `long l = n` β€” and a rule written against syntax covers only the shapes its author remembered. The compiler reads them from Roslyn's bound tree instead: after every expression is translated, the conversion the bound tree wraps it in is applied (`ValueFlow`), at every site C# applies it β€” initializers, arguments, indexes, returns, comparisons. A char promoted to a number becomes its code unit (a constant folds: `'A' + col` is `65 + col`); an int flowing into a `long` becomes a BigInt (`1` β†’ `1n`, which is also why `TimeSpan.FromSeconds(90)` emits `90n`: .NET 9's overload takes a long). Two chars compared stay characters β€” JavaScript orders 1-length strings by the same code units β€” and a user-defined implicit operator passes the value through, because the framework's wrappers (`SizeValue`, `Index`, `ColorToken`) are their primitive on this side. A value on its way into TEXT is settled the same way: boxed into a concatenation, a string operand of one, or a plain interpolation hole, it prints as C# prints it β€” null as nothing, a bool as "True", an enum by its member name β€” `s += flag` included, which no syntax rule had seen. ### A type's own operators cross JavaScript cannot overload `+`, so a record or struct you declare with operators carries each one in its twin as a static method, and every site the bound tree shows an operator at calls it: `a + b` is `Money.opAdd(a, b)`, `-m` is `Money.opNegate(m)` (unary and binary `-` are named by arity, so they never collide), `m += other` is `m = Money.opAdd(m, other)`. Conversions too: `implicit operator Money(int v)` becomes `Money.fromInt`, called wherever C# converts β€” a declaration, an argument, the operand of a compound β€” and `explicit operator int(Money m)` becomes `Money.toInt`, called by the cast. Only for types in your source: a framework wrapper such as `SizeValue` or `Index` is its primitive on this side, and its operators pass the value through. The ELEMENT of a `foreach` converts the same way, one item at a time: `foreach (long l in ints)` makes each int a BigInt, `foreach (int code in chars)` each char its code unit, `foreach (Money m in ints)` calls the type's conversion β€” the loop binds a raw `$l` and declares `l` from it. A `using` DECLARATION (`using var r = …;`) owns the rest of its block: what follows runs inside a try whose finally disposes `r` β€” after a return value is taken, when the body throws, in reverse order when several share a block β€” and `await using` awaits `disposeAsync`. ### Dictionaries enumerate as pairs A transpiled primitive-keyed `Dictionary` is a plain object, and not iterable, so `foreach` over one (and `new List>(dict)`) lowers through `$eq.entries(obj, numericKeys)`: pairs that destructure as `[key, value]` AND answer `.key`/`.value` (both C# consumption shapes), with numeric keys restored as numbers (`Object.entries` strings them, and a stringified key would turn the next `key + 1` into concatenation). Record/struct-keyed dictionaries keep their `valueMap` lowering; only primitive keys take this path. ### Generic item annotations defer to inference `new List>(…)` cannot annotate its `let` with the bare C# name (`KeyValuePair[]` names nothing in TS); generic items leave the annotation to inference. ### Statics initialise LAZILY The shared library's modules import each other through one barrel, so a static field initialised at module-evaluation time can see another module's class as `undefined`. Any initialiser that names another module becomes a lazy getter backed by a private slot, which is also the faithful translation, since C# initialises a type's statics on first use. Pure literals stay fields. ### What a type ANNOTATION may name The emitted `.ts` is type-checked (that is the second of the two layers), so a signature must never introduce a name the module cannot resolve: | C# | TypeScript | |---|---| | an enum | `string`, since its runtime representation is the member name | | an interface with no emitted twin | `any` | | `IReadOnlyList<(char, char)>` | `[string, string][]` | | `Action?` | `((t: T) => void) \| null`, parenthesised, or the union binds to the return | | `char` | `string` | | a name nothing can verify | `any`, because a wrong type is worse than an open one | Records carry the same rules, plus their static fields and their computed properties (a record is a value with BEHAVIOUR, not just its positional members). ### The library IS its directory Both the transpiled set and the runtime's export barrel are generated from the source directory, never from a hand-kept roster, so the embedded library can never drift from the code it is built from. ## πŸ“ Conversion Example **C# Source:** ```csharp private void Increment() { Count++; if (Count > 10) Console.WriteLine("Max reached"); } ``` **TypeScript Output:** ```typescript increment() { this.count++; if (this.count > 10) console.log("Max reached"); } ``` ## 🎯 Advanced Features Examples ### Enum Operations **C# Source:** ```csharp public enum OrderStatus { Pending, Processing, Shipped, Delivered } private void HandleStatusChange(string input) { // Parse enum from string (case-insensitive) if (Enum.TryParse(input, out var status)) { Console.WriteLine($"Status changed to: {status}"); } // Get all enum values for dropdown var allStatuses = Enum.GetValues(); foreach (var s in allStatuses) { Console.WriteLine($"Available status: {s}"); } // Validate enum value if (Enum.IsDefined(typeof(OrderStatus), "Shipped")) { Console.WriteLine("Valid status"); } } ``` **TypeScript Output:** ```typescript handleStatusChange(input: string) { // Parse with TryParse if ((status = parseEnum(input, OrderStatus), status !== undefined)) { console.log(`Status changed to: ${status}`); } // Get all values const allStatuses = Object.values(OrderStatus); for (const s of allStatuses) { console.log(`Available status: ${s}`); } // Validate if ((OrderStatus['Shipped'] !== undefined)) { console.log('Valid status'); } } ``` ### Dictionary Operations **C# Source:** ```csharp private Dictionary _settings = new(); private void ManageSettings() { // Add entries _settings.Add("timeout", 5000); _settings.Add("retries", 3); // Check existence if (_settings.ContainsKey("timeout")) { var timeout = _settings["timeout"]; Console.WriteLine($"Timeout: {timeout}"); } // Safe retrieval if (_settings.TryGetValue("maxItems", out var max)) { Console.WriteLine($"Max: {max}"); } // Iterate keys foreach (var key in _settings.Keys) { Console.WriteLine($"{key} = {_settings[key]}"); } // Clear all _settings.Clear(); } ``` **TypeScript Output:** ```typescript private _settings: Record = {}; manageSettings() { // Add entries this._settings['timeout'] = 5000; this._settings['retries'] = 3; // Check existence if (('timeout' in this._settings)) { const timeout = this._settings['timeout']; console.log(`Timeout: ${timeout}`); } // Safe retrieval if ((max = this._settings['maxItems']) !== undefined) { console.log(`Max: ${max}`); } // Iterate keys for (const key of Object.keys(this._settings)) { console.log(`${key} = ${this._settings[key]}`); } // Clear all Object.keys(this._settings).forEach(k => delete this._settings[k]); } ``` ### LINQ Set Operations **C# Source:** ```csharp private void ProcessCollections() { var list1 = new[] { 1, 2, 3, 4 }; var list2 = new[] { 3, 4, 5, 6 }; // Concatenate two lists var combined = list1.Concat(list2); // Result: [1, 2, 3, 4, 3, 4, 5, 6] // Union - unique elements from both var union = list1.Union(list2); // Result: [1, 2, 3, 4, 5, 6] // Intersect - common elements var common = list1.Intersect(list2); // Result: [3, 4] // Except - elements in list1 but not in list2 var difference = list1.Except(list2); // Result: [1, 2] // Complex filtering with set operations var activeUsers = GetActiveUsers(); var premiumUsers = GetPremiumUsers(); // Users that are both active AND premium var activePremium = activeUsers.Intersect(premiumUsers); // Users that are active but NOT premium var activeFree = activeUsers.Except(premiumUsers); } ``` **TypeScript Output:** ```typescript processCollections() { const list1 = [1, 2, 3, 4]; const list2 = [3, 4, 5, 6]; // Concatenate const combined = [...list1, ...list2]; // Union (with Set to remove duplicates) const union = [...new Set([...list1, ...list2])]; // Intersect (common elements) const common = [...new Set(list1)].filter(x => list2.includes(x)); // Except (difference) const difference = [...new Set(list1)].filter(x => !list2.includes(x)); // Complex filtering const activeUsers = this.getActiveUsers(); const premiumUsers = this.getPremiumUsers(); const activePremium = [...new Set(activeUsers)].filter(x => premiumUsers.includes(x)); const activeFree = [...new Set(activeUsers)].filter(x => !premiumUsers.includes(x)); } ``` ### Array Static Methods **C# Source:** ```csharp private void ProcessArrayOperations() { var numbers = new[] { 5, 2, 8, 1, 9 }; var items = new[] { "apple", "banana", "cherry" }; // Sort array in place Array.Sort(numbers); // Result: [1, 2, 5, 8, 9] // Sort with custom comparison Array.Sort(items, (a, b) => b.Length - a.Length); // Result: ["banana", "cherry", "apple"] // Reverse array Array.Reverse(numbers); // Result: [9, 8, 5, 2, 1] // Find operations var firstEven = Array.Find(numbers, n => n % 2 == 0); var firstEvenIndex = Array.FindIndex(numbers, n => n % 2 == 0); var allEvens = Array.FindAll(numbers, n => n % 2 == 0); // Search operations var index = Array.IndexOf(numbers, 5); var lastIndex = Array.LastIndexOf(numbers, 5); // Check operations var hasEven = Array.Exists(numbers, n => n % 2 == 0); var allPositive = Array.TrueForAll(numbers, n => n > 0); // Clear and resize Array.Clear(numbers); Array.Resize(ref items, 5); // Expand to 5 elements } ``` **TypeScript Output:** ```typescript processArrayOperations() { const numbers = [5, 2, 8, 1, 9]; const items = ["apple", "banana", "cherry"]; // Sort numbers.sort(); // Sort with comparison items.sort((a, b) => b.length - a.length); // Reverse numbers.reverse(); // Find operations const firstEven = numbers.find(n => n % 2 == 0); const firstEvenIndex = numbers.findIndex(n => n % 2 == 0); const allEvens = numbers.filter(n => n % 2 == 0); // Search operations const index = numbers.indexOf(5); const lastIndex = numbers.lastIndexOf(5); // Check operations const hasEven = numbers.some(n => n % 2 == 0); const allPositive = numbers.every(n => n > 0); // Clear and resize numbers.splice(0); items.length = 5; } ``` ### LINQ Type Filtering (Cast & OfType) **C# Source:** ```csharp private void FilterByType() { // Mixed type collection object[] mixed = new object[] { 1, "hello", 2, "world", 3.14, true }; // Cast() - assumes all elements are of type T (passthrough in JS) var assumedStrings = mixed.Cast(); // OfType() - filters to only elements of type T var onlyStrings = mixed.OfType(); // Result: ["hello", "world"] var onlyNumbers = mixed.OfType(); // Result: [1, 2] // Works with custom classes too var shapes = new object[] { new Circle(), new Square(), new Circle() }; var circles = shapes.OfType(); // Result: [Circle, Circle] // Primitive type filtering var primitives = new object[] { 1, "text", 2.5, true, null }; var strings = primitives.OfType(); // ["text"] var numbers = primitives.OfType(); // [1, 2.5] var booleans = primitives.OfType(); // [true] } ``` **TypeScript Output:** ```typescript filterByType() { // Mixed type collection const mixed = [1, "hello", 2, "world", 3.14, true]; // Cast - passthrough (JS is dynamically typed) const assumedStrings = mixed; // OfType - filter by typeof for primitives const onlyStrings = mixed.filter(x => typeof x === 'string'); // Result: ["hello", "world"] const onlyNumbers = mixed.filter(x => typeof x === 'number'); // Result: [1, 2, 3.14] // OfType - filter by instanceof for objects const shapes = [new Circle(), new Square(), new Circle()]; const circles = shapes.filter(x => x instanceof Circle); // Result: [Circle, Circle] // Primitive filtering const primitives = [1, "text", 2.5, true, null]; const strings = primitives.filter(x => typeof x === 'string'); // ["text"] const numbers = primitives.filter(x => typeof x === 'number'); // [1, 2.5] const booleans = primitives.filter(x => typeof x === 'boolean'); // [true] } ``` ### Modern C# Operators **C# Source:** ```csharp private void DemonstrateModernOperators() { // Null-coalescing assignment (??=) string? cachedData = null; cachedData ??= LoadDataFromDatabase(); // Only loads if null cachedData ??= "Default"; // Won't execute, already assigned // Property null-coalescing assignment if (user.Settings ??= new Settings()) { Console.WriteLine("Created new settings"); } // nameof operator (useful for property binding, validation) var propertyName = nameof(user.Email); Console.WriteLine($"Validating {propertyName}"); // "Validating Email" var methodName = nameof(ProcessOrder); LogAction(methodName); // "ProcessOrder" // default keyword - type-safe default values int count = default(int); // 0 string? text = default(string); // null bool flag = default(bool); // false DateTime date = default(DateTime); // 1/1/0001 12:00:00 AM // default literal (contextual) int number = default; // 0 (inferred from type) ProcessData(default); // passes default value for parameter type } private void ProcessData(int value = default) { // value defaults to 0 if not provided } ``` **TypeScript Output:** ```typescript demonstrateModernOperators() { // Null-coalescing assignment let cachedData = null; cachedData ?? (cachedData = this.loadDataFromDatabase()); cachedData ?? (cachedData = 'Default'); // Property assignment if (this.user.settings ?? (this.user.settings = new Settings())) { console.log('Created new settings'); } // nameof operator const propertyName = 'Email'; console.log(`Validating ${propertyName}`); const methodName = 'ProcessOrder'; this.logAction(methodName); // default keyword let count = 0; let text = null; let flag = false; let date = null; // default literal let number = undefined; this.processData(undefined); } processData(value = 0) { // value defaults to 0 } ``` ### String Methods - Additional Examples **C# Source:** ```csharp private void StringManipulation() { var text = " Hello World "; // Trimming var trimmed = text.Trim(); // "Hello World" var leftTrim = text.TrimStart(); // "Hello World " var rightTrim = text.TrimEnd(); // " Hello World" // Case conversion var upper = text.ToUpper(); // " HELLO WORLD " var lower = text.ToLower(); // " hello world " var upperInv = text.ToUpperInvariant(); // " HELLO WORLD " var lowerInv = text.ToLowerInvariant(); // " hello world " // Chaining methods var clean = text.Trim().ToLower().Replace("world", "everyone"); // Result: "hello everyone" } ``` **TypeScript Output:** ```typescript stringManipulation() { const text = " Hello World "; // Trimming const trimmed = text.trim(); const leftTrim = text.trimStart(); const rightTrim = text.trimEnd(); // Case conversion const upper = text.toUpperCase(); const lower = text.toLowerCase(); const upperInv = text.toUpperCase(); const lowerInv = text.toLowerCase(); // Chaining const clean = text.trim().toLowerCase().replaceAll("world", "everyone"); } ```