Tuples, Deconstruction, Extension Methods, Operator Overloading and Indexers

A static method in a static class whose first parameter has this appears as an instance method of that type: "VCU".IsValid().

Concept

Tuples and Deconstruction ValueTuple literals: var result = (Name: "ECU", Id: 10); — lightweight, mutable value types with optional element names.

Deconstruction: var (name, id) = result;, discards with _, and swapping: (x, y) = (y, x);.

Methods return multiple values as tuples: static (int Min, int Max) MinMax(...).

Tuples support == element-wise.

Records and any type with a Deconstruct(out ...) method can be deconstructed.

A static method in a static class whose first parameter has this appears as an instance method of that type: "VCU".IsValid(). LINQ is built this way. They are found through the namespace of the static class (using). C# 14 adds extension members (extension properties and static members in extension blocks).

Types can define operators: public static Voltage operator +(Voltage a, Voltage b). Rules: operators are public static; comparison operators come in pairs (==/!=, </>, <=/>=); when overloading ==, also override Equals and GetHashCode; conversion operators are implicit or explicit.

public T this[int index] { get; set; } lets objects be indexed like arrays. Indexers can be overloaded by parameter type (this[string name]), have multiple parameters (this[int r, int c]) and be read-only.

Other Advanced Features Partial classes/methods (split a type across files — used by source generators), local functions, anonymous types (new { Code, Count }), ref returns and ref locals (ref int first = ref arr[0];), readonly members in structs, in parameters, and ref struct (Module 41).

Why It Matters

These features make domain types feel natural (voltage + offset, signals["EngineSpeed"], bytes.ToHex()), and tuples/deconstruction make returning and consuming multiple values concise.

Syntax

(int A, string B) t = (1, "x");       var (a, b) = t;
public static class Ext { public static R M(this T x) => ...; }
public static T operator +(T a, T b) => ...;
public TValue this[TKey key] { get => ...; set => ...; }

Example

using System.Collections;

// ---- Tuples and deconstruction ----
var result = (Name: "ECU", Id: 10);
var (name, id) = result;
Console.WriteLine($"tuple: {result}, Name = {result.Name}, deconstructed: {name}/{id}");
(int min, int max) = MinMax([7, 2, 9, 4]);
Console.WriteLine($"MinMax -> min {min}, max {max}; tuple equality: {(1, "a") == (1, "a")}");
var (vin, model, _) = new Vehicle("V1", "Aurora", 2025);   // deconstruct a record, discard Year
Console.WriteLine($"record deconstruction: {vin} {model}");
int x = 1, y = 2;
(x, y) = (y, x);                                             // swap
Console.WriteLine($"swapped: x={x}, y={y}");

// ---- Extension methods ----
Console.WriteLine($"\"  \".IsValid() = {"  ".IsValid()}, \"VCU\".IsValid() = {"VCU".IsValid()}");
Console.WriteLine($"new byte[] {{0x1A, 0xF8}}.ToHex() = {new byte[] { 0x1A, 0xF8 }.ToHex()}");
Console.WriteLine($"12.6.Volts() = {12.6.Volts()}");

// ---- Operator overloading ----
var v1 = new Voltage(12.0);
var v2 = new Voltage(0.6);
Console.WriteLine($"v1 + v2 = {v1 + v2}, v1 * 2 = {v1 * 2}, v1 > v2: {v1 > v2}, v1 == new Voltage(12.0): {v1 == new Voltage(12.0)}");
double raw = (double)(v1 + v2);                         
…

Expected Result

tuple: (ECU, 10), Name = ECU, deconstructed: ECU/10
MinMax -> min 2, max 9; tuple equality: True
record deconstruction: V1 Aurora
swapped: x=2, y=1
"  ".IsValid() = False, "VCU".IsValid() = True
new byte[] {0x1A, 0xF8}.ToHex() = 1AF8
12.6.Volts() = 12.6 V
v1 + v2 = 12.6 V, v1 * 2 = 24.0 V, v1 > v2: True, v1 == new Voltage(12.0): True
(double)voltage = 12.6
signals["EngineSpeed"] = 3000, signals[1] = 88, missing = 0
matrix[0,1] = 5
after ref write: 100,1,2
anonymous type: { Code = P0A80, Count = 2 }, type name starts with <>: True
local function: 144

Common Mistakes

Overloading == without != — real compiler error:

Surprising operator semantics (+ that mutates or does something unrelated to addition).

Overloading == without overriding Equals/GetHashCode (inconsistent behavior in collections).

Extension methods that hide instance methods (instance methods always win) or extend object broadly.

Tuples with unnamed elements (Item1, Item2) in public APIs — prefer records.

var v = new Voltage(12);
Console.WriteLine(v + v);

public readonly struct Voltage(double value)
{
    public double Value { get; } = value;
    public static bool operator ==(Voltage a, Voltage b) => a.Value == b.Value;
}
m26_err.cs(7,33): error CS0216: The operator 'Voltage.operator ==(Voltage, Voltage)' requires a matching operator '!=' to also be defined

Best Practices

Use tuples for private/internal multi-value returns; records for public APIs.

Put extension methods in a namespace users opt into; keep them discoverable and focused.

Overload operators only for value-like types where the meaning is obvious (units, vectors, money).

Implement IEquatable<T> and IComparable<T> together with equality/comparison operators.

Interview Relevance

Medium–high: "how do extension methods work?", "rules for operator overloading", "tuple vs record", "what is an indexer?".

Practice Questions

Write static (double Mean, double StdDev) Stats(double[] values).

Add an extension Checksum(this byte[] data) computing an 8-bit sum.

Create a Vector2 struct with +, -, * (scalar) and ==.

Add an indexer to a CanFrame class returning Data[i] with bounds checking.

Quick Quiz

Q1. (Predict the output) var (a, b) = (1, 2); (a, b) = (b, a); Console.WriteLine($"{a}{b}"); Answer: 21.

Q2. (True/False) Extension methods can access private members of the extended type.

Answer: False. They are ordinary static methods and see only accessible members.

Q3. (Multiple choice) Defining operator == requires also defining: A) operator + B) operator != C) operator < D) nothing Answer: B (CS0216, verified).

Q4. (Predict the output) signals["Nope"] in the example, where the getter uses FirstOrDefault(...).Value.

Answer: 0 — the default tuple's Value (verified).

Coding Challenge

Create a readonly record struct Money(decimal Amount, string Currency) with + that throws InvalidOperationException when currencies differ, and an extension Sum(this IEnumerable<Money>).

Money[] items = [new(10.50m, "EUR"), new(4.25m, "EUR")];
Console.WriteLine(items.Sum());
try { _ = new Money(1, "EUR") + new Money(1, "USD"); }
catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); }

readonly record struct Money(decimal Amount, string Currency)
{
    public static Money operator +(Money a, Money b) =>
        a.Currency == b.Currency ? new(a.Amount + b.Amount, a.Currency)
                                 : throw new InvalidOperationException($"cannot add {a.Currency} and {b.Currency}");
    public override string ToString() => $"{Amount:0.00} {Currency}";
}

static class MoneyExtensions
{
    public static Money Sum(this IEnumerable<Money> items) => items.Aggregate((a, b) => a + b);
}