C# Operators Mental Model — From number % 2 == 0 to LLM‑Ready Code
Most C# tutorials show operators with tiny examples:
int number = 12;
bool isEven = number % 2 == 0;
bool isGreaterThanTen = number > 10;
if (isEven && isGreaterThanTen)
{
Console.WriteLine("Number is even and > 10");
}
Helpful… but not enough if you want to:
- Read JIT‑optimized code with confidence
- Understand what your CPU is really doing
- Ask better questions to LLMs (and verify their answers)
- Become the kind of engineer that can talk about IL, flags, and branch prediction
In this post we’ll build a mental model of C# operators from the source code you write down to:
- IL opcodes like
add,rem,cgt,brtrue - CPU instructions like
idiv,cmp,setcc,cmov - Micro‑architectural concerns like branch prediction and overflow
We’ll use a single file you can drop into your repo:
OperatorsDeepDive.cs
and we’ll walk section by section through what each part teaches you.
If you can run dotnet new console, you can follow this.
Table of Contents
- High‑Level Mental Model: From Syntax to CPU
- OperatorsDeepDive.cs: The Playground
-
Basic Operators:
%,>,&&, and?: - Arithmetic vs Bitwise: Thinking in Bits
- Short‑Circuit Logic and Evaluation Order
- Ternary and Branchless Thinking
- Comparison Operators and CPU Flags
- Checked vs Unchecked Arithmetic
- Precedence, Grouping, and Classic Pitfalls
- Modern Operators and Pattern Matching
- Measuring Operator Performance Like a Scientist
- How This Helps You Use LLMs Better
- Full OperatorsDeepDive.cs
1. High‑Level Mental Model: From Syntax to CPU
When you write:
int number = 12;
bool isEven = number % 2 == 0;
bool isGreaterThanTen = number > 10;
if (isEven && isGreaterThanTen) { ... }
there’s a long pipeline between your editor and the hardware:
-
Roslyn (C# compiler)
- Parses your code into an AST (abstract syntax tree).
- Binds each operator to a semantic meaning: remainder, comparison, logical‑AND, etc.
- Emits IL instructions like
rem,ceq,cgt,brtrue,brfalse.
-
CLR + JIT
- At runtime, the JIT compiles IL into machine code using CPU instructions such as
add,sub,imul,idiv,cmp,test,je,jne,jl,jg,setcc,cmov. - These update the CPU FLAGS register (
ZF,SF,CF,OF), which drives conditional jumps.
- At runtime, the JIT compiles IL into machine code using CPU instructions such as
-
Micro‑architecture
- Short‑circuit operators (
&&,||,??,??=) compile into conditional branches or sometimes branchless sequences. - Those branches hit the branch predictor, the pipeline, and speculative execution.
- Overflow behavior depends on
checked/uncheckedcontext (addvsadd.ovf,mulvsmul.ovf).
- Short‑circuit operators (
Once you see operators as syntax → IL → machine code → CPU behavior, you stop treating them as magic symbols and start treating them as performance and correctness decisions.
2. OperatorsDeepDive.cs: The Playground
Drop this file into your console project and hook it from Program.cs:
partial class Program
{
static void Main()
{
OperatorsDeepDive();
}
}
The deep‑dive file exposes one public entry:
static void OperatorsDeepDive()
{
Console.WriteLine("=== Operators Deep Dive ===");
BasicOperatorSample();
ArithmeticVsBitwise();
ShortCircuitAndEvaluationOrder();
TernaryAndBranchlessThinking();
ComparisonAndCPUFlags();
CheckedVsUncheckedArithmetic();
OperatorPrecedenceAndPitfalls();
PatternMatchingAndModernOperators();
MicroBenchmarkShapeForOperators();
}
Each method is a live demo + commentary you can run, study, and modify.
3. Basic Operators: %, >, && and ?:
We start from your original idea, but comment it like a compiler engineer:
static void BasicOperatorSample()
{
int number = 12;
// `%` → IL `rem` → CPU `idiv` + remainder register (EDX/RDX).
bool isEven = number % 2 == 0;
// `>` → IL `cgt` (compare greater than).
bool isGreaterThanTen = number > 10;
// `&&` is *conditional* AND.
if (isEven && isGreaterThanTen)
{
Console.WriteLine($"Number {number} is even and greater than 10");
}
else if (!isEven && isGreaterThanTen)
{
Console.WriteLine($"Number {number} is odd and greater than 10");
}
else
{
Console.WriteLine($"Number {number} does not match criteria");
}
// Ternary `?:` is an expression operator.
int age = 15;
string category = age > 18 ? "Adult" : "Minor";
Console.WriteLine($"Age {age} → Category: {category}");
}
💡 LLM hint: When you ask an LLM to “optimize” a piece of logic, you now know which parts map to division (%) and which map to cheap comparisons. You’ll also recognize when it introduces an unnecessary branch.
4. Arithmetic vs Bitwise: Thinking in Bits
Arithmetic operators are what you expect:
int x = 42;
int y = 15;
int sum = x + y; // add
int diff = x - y; // sub
int prod = x * y; // imul
int quot = x / y; // idiv
int rem = x % y; // rem → idiv + remainder
Bitwise operators work on individual bits:
int and = x & y; // mask bits
int or = x | y;
int xor = x ^ y;
int notX = ~x;
int shiftLeft = x << 1; // ~ multiply by 2 (non‑negative)
int shiftRight = x >> 1; // ~ divide by 2 (arithmetic)
Key performance fact:
For power‑of‑two divisors,
%can often be replaced by a bit mask.
const int Size = 1024; // power of two
int modulo = value % Size; // division
int masked = value & (Size - 1); // bitmask, one cheap instruction
The JIT can do this optimization automatically in many cases, but knowing why matters when you review LLM‑generated hot‑path code (hash tables, ring buffers, etc.).
5. Short‑Circuit Logic and Evaluation Order
C# guarantees left‑to‑right evaluation and short‑circuiting for && and ||:
static void ShortCircuitAndEvaluationOrder()
{
int leftEvaluations = 0;
int rightEvaluations = 0;
bool Left()
{
leftEvaluations++;
Console.WriteLine("Left() evaluated");
return false;
}
bool Right()
{
rightEvaluations++;
Console.WriteLine("Right() evaluated");
return true;
}
bool result = Left() && Right();
Console.WriteLine($"Result = {result}");
Console.WriteLine($"Left() calls = {leftEvaluations}");
Console.WriteLine($"Right() calls = {rightEvaluations}");
}
Output clearly shows that Right() never runs.
Design rules:
- Use short‑circuiting to protect accesses (
obj != null && obj.Prop == 5). - Don’t hide side effects on the right side of
&&or||; they might never run. - When an LLM gives you a boolean expression with
SideEffect()inside, you can immediately see if that’s semantically fragile.
6. Ternary and Branchless Thinking
The ternary operator is more than “short if”. It’s often a hint to the JIT that the logic can be branchless:
int value = 7;
string parity = (value % 2 == 0) ? "even" : "odd";
int clamped = value < 0 ? 0 : value;
On some CPUs, this can become something like:
-
cmp+cmov(conditional move) instead of fullif/elsebranches.
A classic branchless trick (conceptual only):
int signBit = value >> 31; // -1 for negative, 0 for non‑negative
int abs = (value ^ signBit) - signBit;
You rarely need to write this manually in high‑level C#, but knowing the pattern helps you reason about what a “branchless” optimization from an LLM is trying to do.
7. Comparison Operators and CPU Flags
Every comparison sets bits in the FLAGS register:
int a = 10;
int b = 20;
bool less = a < b; // clt
bool equal = a == b; // ceq
bool more = a > b; // cgt
Rough machine code shape:
cmp eax, ebx ; compare a and b
jl Less ; jump if less (SF/OF)
je Equal ; jump if equal (ZF)
Branch prediction:
- Modern CPUs guess which way the branch will go.
- A misprediction flushes the pipeline (dozens of cycles).
- Tight loops with unpredictable
ifconditions can dominate performance.
When an LLM “optimizes” something by adding more branching logic, you now have a mental model to ask: Will this be branch‑friendly or branch‑hostile?
8. Checked vs Unchecked Arithmetic
C# gives you explicit control of overflow behavior:
int max = int.MaxValue;
// Wrap‑around (two’s complement)
int wrapped = unchecked(max + 1);
// Overflow detection
try
{
int willThrow = checked(max + 1);
}
catch (OverflowException)
{
Console.WriteLine("Overflow detected");
}
IL:
-
unchecked→add -
checked→add.ovf
add.ovf uses CPU flags (overflow/carry) and injects a throw when overflow is detected.
Guideline:
- For financial / safety‑critical code, prefer
checkedin boundary‑heavy arithmetic. - For hot paths where you’ve proven ranges or where wrap is intentional,
uncheckedavoids extra checks.
LLM usage:
- When an LLM rewrites arithmetic, check if it silently changes checked/unchecked behavior.
- Ask it explicitly: “Show me the IL or explain whether this uses
addoradd.ovf.”
9. Precedence, Grouping, and Classic Pitfalls
Operator precedence bugs are boring, but they still happen:
int x = 2, y = 3, z = 4;
int result1 = x + y * z; // 2 + (3 * 4) = 14
int result2 = (x + y) * z; // (2 + 3) * 4 = 20
Logical operators:
bool flag = true || false && false; // true || (false && false) → true
bool flagParen = (true || false) && false; // (true || false) && false → false
LLM‑era rule:
Whenever the meaning is even slightly ambiguous, add parentheses. Humans, compilers, and LLMs will all thank you.
Also remember the classic assignment vs comparison bug (C‑style languages):
// if (flag = SomeCheck()) // BUG in some languages
C# protects you a bit (condition must be bool), but when reading pseudo‑code from an LLM, always check that assignments and comparisons are not mixed.
10. Modern Operators and Pattern Matching
Modern C# adds higher‑level “semantic operators”: ??, ??=, is, patterns, and switch expressions.
string? maybeName = null;
// Null‑coalescing
string displayName = maybeName ?? "Unknown";
// Null‑coalescing assignment
maybeName ??= "Initialized";
object obj = 42;
if (obj is int n && n > 10)
{
Console.WriteLine($"obj is int and > 10: {n}");
string classification = n switch
{
< 0 => "negative",
0 => "zero",
< 10 => "small positive",
_ => "large positive"
};
Console.WriteLine($"classification = {classification}");
}
else
{
Console.WriteLine("obj is not an int > 10");
}
Compiler view:
- Pattern matching ultimately becomes combinations of
isinst, comparisons, and branches. - For integral
switch, the JIT may generate a jump table or binary search of cases.
LLMs love using pattern matching syntax. With this mental model you can verify:
- Are the patterns exhaustive?
- Does it introduce extra allocations or hidden boxing?
- Would a simple
ifchain be clearer/faster here?
11. Measuring Operator Performance Like a Scientist
Instead of arguing about % vs &, measure.
const int N = 1_000_000;
const int mask = 1024 - 1;
int Modulo(int i) => i % 1024;
int Bitmask(int i) => i & mask;
// Warm up JIT
for (int i = 0; i < 10_000; i++)
{
_ = Modulo(i);
_ = Bitmask(i);
}
static (TimeSpan elapsed, long alloc) Measure(string label, Func<int, int> func, int iterations)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
long beforeAlloc = GC.GetAllocatedBytesForCurrentThread();
var sw = Stopwatch.StartNew();
int sum = 0;
for (int i = 0; i < iterations; i++)
{
sum += func(i);
}
sw.Stop();
long afterAlloc = GC.GetAllocatedBytesForCurrentThread();
Console.WriteLine($"{label}: sum={sum} time={sw.Elapsed.TotalMilliseconds:F2} ms alloc={afterAlloc - beforeAlloc} bytes");
return (sw.Elapsed, afterAlloc - beforeAlloc);
}
Measure("Modulo ", Modulo, N);
Measure("Bitmask ", Bitmask, N);
In real projects, use BenchmarkDotNet. But the pattern is the same:
- Warmup (trigger JIT).
- Measure time and allocations.
- Compare strategies.
- Repeat across machines / .NET versions.
This is exactly how you should evaluate optimizations suggested by an LLM.
12. How This Helps You Use LLMs Better
LLMs are great at generating code, but they don’t “feel” CPU pipelines or branch predictors. You do.
With this mental model you can:
- Ask specific questions:
- “Can you rewrite this
%with a bitmask, assuming size is a power of two?” - “Show me a
checkedversion of this calculation and explain the IL difference.”
- “Can you rewrite this
- Review answers critically:
- Does this ternary reduce branches or just add complexity?
- Is this pattern matching expression boxing values or widening types?
- Did it accidentally change overflow semantics?
- Teach others:
- Use
OperatorsDeepDive.csin your team as a living doc. - Ask LLMs to extend it with more scenarios (SIMD, intrinsics, hardware popcount, etc.).
- Use
The goal isn’t to memorize every opcode. It’s to connect your intuition:
“This operator choice will likely allocate / branch / overflow / mispredict.”
Once you think this way, you’re operating in the top tier of C# engineers.
13. Full OperatorsDeepDive.cs
// File: OperatorsDeepDive.cs
// Author: Cristian Sifuentes + ChatGPT
// Goal: Explain C# OPERATORS like a systems / compiler / performance engineer.
//
// HIGH-LEVEL MENTAL MODEL
// -----------------------
// When you write:
//
// int number = 12;
// bool isEven = number % 2 == 0;
// bool isGreaterThanTen = number > 10;
// if (isEven && isGreaterThanTen) { ... }
//
// a lot happens under the hood:
//
// 1. The C# compiler (Roslyn) parses your source into an abstract syntax tree (AST)
// and binds each operator to a specific semantic:
// - `%` → integer remainder operator
// - `==` → equality comparison operator
// - `>` → relational operator
// - `&&` → conditional-AND with short-circuit
//
// 2. Roslyn emits IL instructions like:
// rem, ceq, cgt, brtrue, brfalse
// Each IL opcode has a precise stack-machine behavior.
//
// 3. At runtime the JIT turns the IL into machine code using CPU instructions such as:
// add, sub, imul, idiv, cmp, test, je/jne/jl/jg, setcc, cmov
// The CPU then updates FLAGS (ZF, SF, CF, OF) which drive branches.
//
// 4. Short-circuiting operators (&&, ||, ??, ??=) compile to conditional branches
// (or sometimes branchless patterns like `cmov`), which interact with:
//
// - the branch predictor
// - instruction pipelines
// - the CPU’s speculative execution
//
// 5. Overflow behavior depends on checked/unchecked context:
// - `add` vs `add.ovf` IL
// - `mul` vs `mul.ovf`
// This translates to different sequences of machine instructions.
//
// This file is written so you can think about operators the way a **top 1% engineer**
// would: as mappings from syntax → IL → machine code → micro-architectural effects.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
partial class Program
{
// ---------------------------------------------------------------------
// PUBLIC ENTRY FOR THIS MODULE
// ---------------------------------------------------------------------
// Call OperatorsDeepDive() from your main Program to run all demos.
static void OperatorsDeepDive()
{
Console.WriteLine("=== Operators Deep Dive ===");
BasicOperatorSample(); // Your original idea, upgraded
ArithmeticVsBitwise(); // %, /, &, |, ^, shifts
ShortCircuitAndEvaluationOrder();
TernaryAndBranchlessThinking();
ComparisonAndCPUFlags();
CheckedVsUncheckedArithmetic();
OperatorPrecedenceAndPitfalls();
PatternMatchingAndModernOperators();
MicroBenchmarkShapeForOperators();
}
// ---------------------------------------------------------------------
// 0. BASIC SAMPLE – start from your original example, but commented
// ---------------------------------------------------------------------
static void BasicOperatorSample()
{
Console.WriteLine();
Console.WriteLine("=== 0. Basic Operator Sample ===");
int number = 12;
// `%` is the remainder operator.
// For integers it maps to IL `rem` and, on x86/x64, to `idiv` + `edx` remainder.
bool isEven = number % 2 == 0;
// `>` is a relational operator using IL `cgt` (compare greater than).
bool isGreaterThanTen = number > 10;
// `&&` is *conditional* AND:
// - Right side is evaluated only if left side is true.
// - This compiles to branches like:
// if (!isEven) goto elseLabel;
// if (!isGreaterThanTen) goto elseLabel;
if (isEven && isGreaterThanTen)
{
Console.WriteLine($"Number {number} is even and greater than 10");
}
else if (!isEven && isGreaterThanTen)
{
Console.WriteLine($"Number {number} is odd and greater than 10");
}
else
{
Console.WriteLine($"Number {number} does not match criteria");
}
// The ternary `?:` is an *expression* operator, not a statement.
// The compiler often emits something like:
// if (age > 18) category = "Adult";
// else category = "Minor";
int age = 15;
string category = age > 18 ? "Adult" : "Minor";
Console.WriteLine($"Age {age} → Category: {category}");
}
// ---------------------------------------------------------------------
// 1. ARITHMETIC vs BITWISE – think in terms of bits and CPU instructions
// ---------------------------------------------------------------------
static void ArithmeticVsBitwise()
{
Console.WriteLine();
Console.WriteLine("=== 1. Arithmetic vs Bitwise ===");
int x = 42; // 00101010 in binary
int y = 15; // 00001111 in binary
// ARITHMETIC
int sum = x + y; // IL: add → CPU: add
int diff = x - y; // IL: sub → CPU: sub
int prod = x * y; // IL: mul → CPU: imul
int quot = x / y; // IL: div → CPU: idiv
int rem = x % y; // IL: rem → CPU: idiv, remainder in EDX/RDX
Console.WriteLine($"[Arith] {x} + {y} = {sum}");
Console.WriteLine($"[Arith] {x} - {y} = {diff}");
Console.WriteLine($"[Arith] {x} * {y} = {prod}");
Console.WriteLine($"[Arith] {x} / {y} = {quot}");
Console.WriteLine($"[Arith] {x} % {y} = {rem}");
// BITWISE (logical on individual bits)
int and = x & y; // 00101010 & 00001111 = 00001010 (10)
int or = x | y; // 00101010 | 00001111 = 00101111 (47)
int xor = x ^ y; // 00101010 ^ 00001111 = 00100101 (37)
int notX = ~x; // bitwise NOT (two’s complement inversion)
int shiftLeft = x << 1; // multiply by 2 for non-negative x
int shiftRight = x >> 1; // divide by 2 (arithmetic shift)
Console.WriteLine($"[Bit] {x} & {y} = {and}");
Console.WriteLine($"[Bit] {x} | {y} = {or}");
Console.WriteLine($"[Bit] {x} ^ {y} = {xor}");
Console.WriteLine($"[Bit] ~{x} = {notX}");
Console.WriteLine($"[Bit] {x} << 1 = {shiftLeft}");
Console.WriteLine($"[Bit] {x} >> 1 = {shiftRight}");
// SCIENTIST-LEVEL FACT:
// - % with a power-of-two divisor (e.g., number % 8) can often be
// optimized to `number & (8 - 1)` by JIT (bitmask instead of division).
// - Division and modulo are among the slowest scalar integer ops.
// Shifts and bitwise ops are usually 1 cycle and easily pipelined.
//
// So this:
// bool isPowerOfTwoBucket = (value & (size - 1)) == 0;
// can be much faster than:
// bool isPowerOfTwoBucket = value % size == 0;
// when `size` is a power of two.
}
// ---------------------------------------------------------------------
// 2. SHORT-CIRCUIT & EVALUATION ORDER – &&, ||, and side effects
// ---------------------------------------------------------------------
static void ShortCircuitAndEvaluationOrder()
{
Console.WriteLine();
Console.WriteLine("=== 2. Short-Circuit & Evaluation Order ===");
int leftEvaluations = 0;
int rightEvaluations = 0;
bool Left()
{
leftEvaluations++;
Console.WriteLine("Left() evaluated");
return false;
}
bool Right()
{
rightEvaluations++;
Console.WriteLine("Right() evaluated");
return true;
}
// C# guarantees left-to-right evaluation.
// For `&&`:
// - If Left() returns false, Right() will NOT be evaluated.
bool result = Left() && Right();
Console.WriteLine($"[ShortCircuit] Result = {result}");
Console.WriteLine($"[ShortCircuit] Left() calls = {leftEvaluations}");
Console.WriteLine($"[ShortCircuit] Right() calls = {rightEvaluations}");
// WHY THIS MATTERS:
//
// - You can use short-circuit behavior to avoid null-reference accesses:
//
// if (obj != null && obj.Property == 5) ...
//
// - But you must never rely on SideEffect() being executed if it is
// on the right side of &&:
//
// if (flag && SideEffect()) { ... }
//
// Here SideEffect() might never run. A top engineer keeps side effects
// out of boolean expressions when possible, or is extremely explicit.
}
// ---------------------------------------------------------------------
// 3. TERNARY ?: & BRANCHLESS THINKING – map expressions to hardware
// ---------------------------------------------------------------------
static void TernaryAndBranchlessThinking()
{
Console.WriteLine();
Console.WriteLine("=== 3. Ternary ?: & Branchless Thinking ===");
int value = 7;
// Simple ternary:
string parity = (value % 2 == 0) ? "even" : "odd";
Console.WriteLine($"Value {value} is {parity}");
// Sometimes the JIT can turn a ternary into *branchless* machine code
// using `cmov` (conditional move) or bit tricks, which is better for
// misprediction-heavy code.
//
// Example: clamp negative numbers to 0
int clamped = value < 0 ? 0 : value;
Console.WriteLine($"Clamped: {clamped}");
// BRANCHLESS TRICK (conceptual):
//
// int signBit = value >> 31; // -1 for negative, 0 for non-negative
// int abs = (value ^ signBit) - signBit;
//
// This computes absolute value using only arithmetic/bitwise operators,
// which may be faster than a branch-heavy `if` on some hot paths.
//
// As a high-level .NET dev you rarely write this manually, but understanding
// the idea helps you reason about JIT and micro-optimizations.
}
// ---------------------------------------------------------------------
// 4. COMPARISON & CPU FLAGS – what actually drives jumps
// ---------------------------------------------------------------------
static void ComparisonAndCPUFlags()
{
Console.WriteLine();
Console.WriteLine("=== 4. Comparison & CPU Flags ===");
int a = 10;
int b = 20;
bool less = a < b; // IL: clt
bool equal = a == b; // IL: ceq
bool more = a > b; // IL: cgt
Console.WriteLine($"[Cmp] {a} < {b} : {less}");
Console.WriteLine($"[Cmp] {a} == {b} : {equal}");
Console.WriteLine($"[Cmp] {a} > {b} : {more}");
// MACHINE-LEVEL VIEW (simplified):
//
// cmp eax, ebx ; compare a and b
// jl LessLabel ; jump if less (based on SF, OF flags)
// je EqualLabel ; jump if equal (ZF flag)
//
// Comparisons set the CPU FLAGS register; branches read those flags.
//
// BRANCH PREDICTION:
// - Modern CPUs guess which way branches go.
// - A mispredicted branch flushes the pipeline (tens of cycles).
// - Tight loops with unpredictable branches can be much slower.
//
// JIT often reorders and simplifies comparison expressions so the branch
// pattern is friendlier to the predictor.
}
// ---------------------------------------------------------------------
// 5. CHECKED vs UNCHECKED – overflow operators and IL
// ---------------------------------------------------------------------
static void CheckedVsUncheckedArithmetic()
{
Console.WriteLine();
Console.WriteLine("=== 5. Checked vs Unchecked Arithmetic ===");
int max = int.MaxValue;
// unchecked: overflow wraps around (two’s complement)
int wrapped = unchecked(max + 1);
Console.WriteLine($"[Overflow] max = {max}");
Console.WriteLine($"[Overflow] unchecked = {wrapped}");
try
{
// checked: overflow throws System.OverflowException
int willThrow = checked(max + 1);
Console.WriteLine($"[Overflow] checked = {willThrow}");
}
catch (OverflowException)
{
Console.WriteLine("[Overflow] checked → OverflowException");
}
// IL VIEW (conceptual):
//
// unchecked: add
// checked: add.ovf
//
// `add.ovf` performs extra checks and throws if the result cannot be
// represented in the destination type. The JIT translates this into
// hardware sequences that detect overflow (using OF/CF flags).
//
// DESIGN RULE:
// - Use checked arithmetic in code where correctness is critical
// and numbers may approach boundaries (e.g., financial systems).
// - Use unchecked in performance-critical hot paths where you have
// proven that overflow cannot occur or wrapping is intended.
}
// ---------------------------------------------------------------------
// 6. PRECEDENCE & PITFALLS – read operators like a compiler
// ---------------------------------------------------------------------
static void OperatorPrecedenceAndPitfalls()
{
Console.WriteLine();
Console.WriteLine("=== 6. Operator Precedence & Pitfalls ===");
int x = 2;
int y = 3;
int z = 4;
// Multiplication has higher precedence than addition.
int result1 = x + y * z; // 2 + (3 * 4) = 14
int result2 = (x + y) * z; // (2 + 3) * 4 = 20
Console.WriteLine($"[Prec] x + y * z = {result1}");
Console.WriteLine($"[Prec] (x + y)* z = {result2}");
// Logical operators: && has higher precedence than ||.
bool flag = true || false && false; // true || (false && false) → true
bool flagParen = (true || false) && false; // (true || false) && false → false
Console.WriteLine($"[Prec] true || false && false = {flag}");
Console.WriteLine($"[Prec] (true || false) && false = {flagParen}");
// PITFALL: assignment vs comparison
//
// if (flag = SomeCheck()) // BUG; assigns, then tests the value
//
// C# reduces this by requiring bool for if conditions, but it’s still
// good practice to keep comparisons explicit and even use Yoda-style
// equality in some contexts:
//
// if (0 == value) ...
//
// Which avoids accidentally writing `value = 0` in languages that allow it.
}
// ---------------------------------------------------------------------
// 7. MODERN OPERATORS – ??, ??=, pattern matching as "semantic operators"
// ---------------------------------------------------------------------
static void PatternMatchingAndModernOperators()
{
Console.WriteLine();
Console.WriteLine("=== 7. Modern Operators & Pattern Matching ===");
string? maybeName = null;
// Null-coalescing operator:
string displayName = maybeName ?? "Unknown";
Console.WriteLine($"[NullCoalesce] {displayName}");
// Null-coalescing assignment:
maybeName ??= "Initialized";
Console.WriteLine($"[NullCoalesceAssign] {maybeName}");
object obj = 42;
// Pattern matching `is` with type + condition:
if (obj is int n && n > 10)
{
Console.WriteLine($"[Pattern] obj is int and > 10: {n}");
// `switch` expression is also a high-level "operator":
string classification = n switch
{
< 0 => "negative",
0 => "zero",
< 10 => "small positive",
_ => "large positive"
};
Console.WriteLine($"[Pattern] {n} classified as {classification}");
}
else
{
Console.WriteLine("[Pattern] obj is not an int > 10");
}
// COMPILER VIEW:
//
// - Many pattern matching constructs are lowered to chains of `isinst`,
// comparisons, and branches.
// - For integral switches, the JIT can generate jump tables or binary
// search trees, optimizing the dispatch.
//
// These "modern" operators are syntactic sugar over powerful, optimized
// IL constructs – knowing this helps you reason about complexity and speed.
}
// ---------------------------------------------------------------------
// 8. MICRO-BENCHMARK SHAPE – how to measure operator performance
// ---------------------------------------------------------------------
static void MicroBenchmarkShapeForOperators()
{
Console.WriteLine();
Console.WriteLine("=== 8. Micro-benchmark Shape (Conceptual) ===");
// GOAL:
// Compare two operator-based strategies scientifically.
// Example: `%` vs bitwise `&` for power-of-two modulus.
const int N = 1_000_000;
const int mask = 1024 - 1; // power-of-two - 1
int Modulo(int i) => i % 1024;
int Bitmask(int i) => i & mask;
// Warm up JIT
for (int i = 0; i < 10_000; i++)
{
_ = Modulo(i);
_ = Bitmask(i);
}
// NOTE: BenchmarkDotNet is the real tool; this is educational only.
static (TimeSpan elapsed, long alloc) Measure(string label, Func<int, int> func, int iterations)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
long beforeAlloc = GC.GetAllocatedBytesForCurrentThread();
var sw = Stopwatch.StartNew();
int sum = 0;
for (int i = 0; i < iterations; i++)
{
sum += func(i);
}
sw.Stop();
long afterAlloc = GC.GetAllocatedBytesForCurrentThread();
Console.WriteLine($"{label}: sum={sum} time={sw.Elapsed.TotalMilliseconds:F2} ms alloc={afterAlloc - beforeAlloc} bytes");
return (sw.Elapsed, afterAlloc - beforeAlloc);
}
Measure("Modulo ", Modulo, N);
Measure("Bitmask ", Bitmask, N);
// SCIENTIST-LEVEL MINDSET:
//
// - You *hypothesize* that one combination of operators is faster.
// - You design a controlled experiment.
// - You measure time AND allocations.
// - You repeat under different hardware / .NET versions.
//
// Operators are not just syntax; they are choices that propagate all the way
// down to CPU pipelines and cache behavior. Top engineers always validate
// those choices with data.
}
}
Happy optimizing — and may your branches always be predictable. 🔬💻

Top comments (0)