Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

foreach

foreach (var <item> in <collection>) { … }

Walks a collection — a query result, a list, or a parent's children. The normal way to iterate; reach for a for loop only when you need the index.

stable6 examples compiled by CIfunctioncontrol-flow

Summary#

foreach walks a collection, binding each element to a local. It is the normal loop: a materialised query result, a List<T>, or a parent's children all iterate the same way.

Signature#

foreach (var <item> in <collection>) { … }

Description#

Walking a query result#

Materialise the query with .ToList(), then walk it:

entity Order {
  [Required] string Code;
  decimal Total;
}

decimal TotalOver(decimal threshold) {
  var sum = 0m;
  foreach (var o in Order.Where(x => x.Total > threshold).ToList()) {
    sum += o.Total;
  }
  return sum;
}

Note the accumulator is seeded 0m, not 0 — see var.

Walking a parent's children#

A parent's collection member iterates directly. This is the connected path through the object graph, and the reason you should never fetch children with a separate filtered query (relations):

entity Invoice {
  [Required] string Number;
  [ForeignKey(Invoice)] InvoiceLine[] Lines;
}

entity InvoiceLine {
  [Required] Invoice Invoice;
  decimal Amount;
}

decimal InvoiceTotal(string number) {
  var inv = Invoice.Single(i => i.Number == number);
  var total = 0m;
  foreach (var line in inv.Lines) {
    total += line.Amount;
  }
  return total;
}

Walking a list#

string Join(string csv) {
  var joined = "";
  foreach (var part in Text.Split(csv, ",")) {
    joined += Text.Trim(part);
  }
  return joined;
}

Walking the characters of a string#

A string is a sequence of one-character strings, so foreach walks it exactly as it walks a list — there is no separate character type to learn, and ch is an ordinary string you can compare, append and pass on:

```osy title="a character at a time, and what ch is" syntax int Commas(string line) { var n = 0; foreach (var ch in line) { if (ch == ",") { n = n + 1; } } return n; }


The loop lowers to `Text.Chars(line)`, which you may also call directly when you want the characters as a
`string[]` rather than a loop — `Text.Chars("abc")` is `["a", "b", "c"]`.

### Walking a stream   {#stream}
A `stream<T>` function hands out its items over time ([yield — a function that produces results over time](/reference/function/yield/)). A loop is how they are read, in any
function or test: `foreach`, or C#'s `await foreach` — the two are the same loop.

```osy title="a function reading a stream" test app=foreach-stream
stream<int> Count(int n) {
  for (var i = 1; i <= n; i = i + 1) { yield return i; }
}

int Sum(int n) {
  var total = 0;
  foreach (var x in Count(n)) { total = total + x; }
  return total;
}
[Test]
void a_test_reads_a_stream_with_a_loop() {
  List<int> seen = [];
  foreach (var x in Count(3)) { seen.Add(x); }
  Assert.Equal(3, seen.Count);
  Assert.Equal(6, Sum(3));
}

The loop reads the stream as it comes, in every body that runs on the server: the body runs once per item, as each arrives. So a loop can leave early — break, return — and the stream stops being read, and a stream that never ends (a folder being watched, a program's output) can be read up to the item you were waiting for:

string FirstError(Guid program) {
  foreach (var line in Host.Process.Watch(program)) {
    if (line.IsError) { return line.Text; }      // the rest is not read
  }
  return "";
}

One thing changes that: a body that has to wait. When the code inside the loop waits on something outside the run — a function that runs in the browser, a person's answer, a durable delay — the run is put away and picked up later, and a half-read stream cannot be put away. At that moment the rest of the stream is read to its end, and the loop carries on over what arrived. Nothing is lost and nothing is repeated; but a stream that never ends cannot be waited in the middle of, so do the waiting before or after such a loop. Inside a stream<T> function this never happens — a producer does not wait that way.

A component does not loop over a stream in its own code at all: it binds it to a live var and renders it (The reactivity & lifecycle model).

Can the loop variable have a type?#

Yes — foreach (int x in arr) and foreach (char c in "abc") are C#'s typed loop variables, and the type is checked against the elements as a declaration's is. var remains the usual spelling.

int Total(int[] xs) { int t = 0; foreach (int x in xs) { t += x; } return t; }

When you need the index#

foreach gives you the element, not its position. When the position is what you are after, use a for loop.

How do I stop part-way through?#

break and continue work as they do in C# — see break / continue.

See also#

Related

for

The C-style counting loop. Runs init once, then repeats the body while cond holds, running update after each pass. Any…

break / continue

break leaves the enclosing loop; continue skips the rest of this pass. Inside a switch, break leaves the SWITCH, not…

relations

One entity points at another by declaring it as a member — that is the foreign key. The parent reads its children back…

Where / Single / Count

Query an entity by writing a predicate over it. The query runs in the database — not a filter over rows you already…

yield — a function that produces results over time

A `stream<T>` function produces its results one at a time instead of all at once, and a `live var` bound to one renders…

async / await and Task

C#'s async and await, with C#'s meaning. A call to a Task-returning method that you do not await where you make it…