Tuples¶
A tuple is the lightweight way to group or return a few values without declaring a class. You make one with parentheses, read its elements by position (.Item1, .Item2, …) or by name, deconstruct it into separate variables, match it in a switch, compare it, and store it in collections.
var pair = (3, 4); // a 2-tuple (2+ elements: (a, b, c), …)
int sum = pair.Item1 + pair.Item2; // 7
Making and reading tuples¶
A tuple literal is just comma‑separated values in parentheses. Read elements positionally with .Item1, .Item2, … (1‑based).
var p = (10, 20, 30); // 3-tuple
int a = p.Item1; // 10
int b = p.Item2; // 20
int c = p.Item3; // 30
Tuples can hold any types, and can nest:
var mixed = ("Ada", 42, true); // (string, int, bool)
var nested = ((1, 2), 3); // a tuple inside a tuple
int inner = nested.Item1.Item2; // 2
Returning multiple values¶
The headline use: return several values from a method with a tuple return type — no out parameter, no helper class.
static (int, int) MinMax(int[] xs)
{
int lo = xs[0], hi = xs[0];
foreach (int v in xs) { if (v < lo) lo = v; if (v > hi) hi = v; }
return (lo, hi);
}
var (low, high) = MinMax(scores); // deconstruct the result (below)
var r = MinMax(scores); // or keep the tuple
int spread = r.Item2 - r.Item1;
Tuples also work as parameters and fields:
static int Add((int, int) p) { return p.Item1 + p.Item2; } // tuple parameter
class Score : NexusBehaviour
{
(int, int) lastResult; // tuple field
}
Named elements¶
Give the elements names for readability — then read them by name or by position:
static (int count, string label) Summarize() { return (3, "items"); }
var s = Summarize();
Debug.Log(s.count + " " + s.label); // named access
int n = s.Item1; // positional still works
Names are per shape
Element names are a per‑shape convenience — two tuples with the same element types share the same names. That's fine for normal use; just don't expect two different name sets on the same (int, int) shape.
Deconstruction¶
Split a tuple into separate variables. Three forms:
var (x, y) = pair; // 1. infer the element types
(int x2, string y2) = pair; // 2. declare with explicit types
(x, y) = MinMax(scores); // 3. assign into existing variables
Use _ to discard an element you don't need, and deconstruct a method result directly:
var (a, _, c) = (1, 99, 4); // skip the middle element → a=1, c=4
var (low, high) = MinMax(scores); // deconstruct the returned tuple
The number of targets must match the tuple's size (a 2‑tuple needs two targets).
switch on a tuple¶
Tuples shine in pattern matching. Both the expression and statement forms support tuple patterns with _ wildcards.
// Expression form
string Quadrant(int x, int y) => (x, y) switch
{
(0, 0) => "origin",
(_, 0) => "on the x-axis",
(0, _) => "on the y-axis",
_ => "elsewhere"
};
// Statement form
switch ((x, y))
{
case (0, 0): Reset(); break;
case (_, 0): SlideAlongX(); break;
default: FreeMove(); break;
}
Patterns are matched top to bottom; the first match wins. A _ element matches anything; a bare _ arm/default is the catch‑all.
Relational element patterns — an element can be a comparison (> < >= <= == !=) instead of an exact value:
string Sign(int x, int y) => (x, y) switch
{
(> 0, > 0) => "both positive",
(< 0, _) => "x negative",
(0, _) => "x is zero",
_ => "other"
};
when guards — add a boolean condition that must also hold for the arm to match (it can reference any variables in scope). Works on any pattern, including _:
string Compare(int x, int y) => (x, y) switch
{
(0, 0) => "both zero",
(> 0, _) when x > y => "x positive and bigger", // pattern AND guard
_ when x > y => "x bigger",
_ when x < y => "y bigger",
_ => "equal"
};
// also in statement form:
switch ((x, y))
{
case (_, _) when x > y: TakeLead(); break;
default: Hold(); break;
}
Equality¶
Tuples compare structurally with == and != — element by element, not by reference. Two tuples are equal when every element is equal.
if ((x, y) == (0, 0)) { /* exactly at the origin */ }
var a = (1, 2);
var b = (1, 2);
bool same = a == b; // true (same values), even though they're different objects
Tuples in collections¶
Tuples work as collection elements — a list of pairs, a dictionary keyed to points, etc. Named elements work through the collection too.
var points = new List<(int, int)>();
points.Add((3, 4));
points.Add((10, 20));
int firstX = points[0].Item1; // 3
for (int i = 0; i < points.Count; i++)
{
var pt = points[i];
Debug.Log(pt.Item1 + "," + pt.Item2);
}
var spawns = new Dictionary<string, (int count, int total)>();
spawns["wave1"] = (5, 100);
int n = spawns["wave1"].count; // 5 — named access through the dictionary
A worked example¶
A scoreboard helper that returns the leader and reacts to outcomes:
using UnityEngine;
using NexusVM.Unity;
class Scoreboard : NexusBehaviour
{
int red;
int blue;
// Return two related values at once.
(int redScore, int blueScore) Scores() => (red, blue);
string Status()
{
var (r, b) = Scores();
return (r, b) switch
{
(0, 0) => "Kickoff!",
_ when r > b => "Red leads", // when guard for comparisons
_ when b > r => "Blue leads",
_ => "Tied"
};
}
// Tuple patterns are perfect for exact, enumerable states:
string Corner(int x, int y) => (x, y) switch
{
(0, 0) => "bottom-left",
(1, 0) => "bottom-right",
(0, 1) => "top-left",
(1, 1) => "top-right",
_ => "not a corner"
};
}
Gotchas & limits¶
Good to know
- A tuple is a reference object (everything in NexusScript is), so passing one around shares it — reassign with a fresh
(…)for an independent copy. - Tuples need 2+ elements;
(x)is just a parenthesized value, not a tuple. - The lookalike
(a, b) => …is a lambda parameter list, not a tuple — they're unrelated.
Not supported yet
- A tuple stored in a user‑defined generic class's
Tfield (Box<(int,int)>.value) reads elements incorrectly — this is a general generic‑field limitation, not specific to tuples. Collections (List,Dictionary) are fine.
Quick reference¶
| Feature | Syntax |
|---|---|
| Literal | (a, b) · (a, b, c) (2+) |
| Element access | t.Item1 · t.Item2 · named t.count |
| Return type | (int, int) F() { return (a, b); } |
| Parameter / field | void M((int,int) p) · (int,int) f; |
| Deconstruct | var (x,y)=t · (int x,…)=t · (x,y)=t · _ discards |
switch |
t switch { (0,0)=>… , _=>… } · case (0,_): · relational (>0, _) · when guard |
| Equality | a == b · a != b (structural) |
| Collections | List<(int,int)> · Dictionary<string,(int,int)> |
→ Related: Collections · Basics & lifecycle.