FOREACH

Iterate sequentially through elements in a collection

WSupported on Windows
USupported on Unix
VSupported on OpenVMS
NSupported in Synergy .NET
FOREACH [DATA] loop_var IN collection [AS type]
  <iteration processing>
  .
  .
  .

DATA

(optional) Declares a FOREACH loop variable whose scope is limited to the body of the FOREACH loop. See the Discussion for more information.

loop_var

A loop record or variable whose value is set to each element in sequence as FOREACH iterates through them.

collection

One of the following:

AS type

(optional) The data type of the elements in the collection, which you need to specify if the loop_var type doesn’t match the collection type.

Discussion

When the FOREACH statement is executed, it iterates sequentially through all elements in the collection, setting loop_var to each element in turn. If the collection is a dynamic array, the loop variable’s type must match or be an ancestor of the element type of that array; otherwise, a “Type mismatch” error (TYPMISMCH) will occur.

Including the DATA keyword declares the loop variable within the FOREACH statement itself, limiting the scope of that variable to just the body of the FOREACH loop. If DATA is not specified, the loop variable must have been declared previously, such as in a record or a previous local DATA statement.

The loop variable is a copy of the processed element in collection, so it is like an assignment statement. This means that changes to members of loop_var are not reflected back in the original collection. As a result of the implied assignment, FOREACH is less efficient than a FOR loop.

You cannot add an element to the collection, remove an element from the collection, or set a collection item to another handle while the FOREACH statement is being executed. If any of these changes occur, the runtime will report an “Invalid operation: Collection was modified” error ($ERR_INVOPER).

A call to a label in a higher scope is not allowed in the iteration processing section of a FOREACH statement whose loop variable is an object.

If the collection is a Synergex.SynergyDE.Collections.ArrayList, a System.Collections.ArrayList, or a descendent of one of these classes and the items of the collection cannot be cast to the type of the loop variable, the runtime will report an “Incompatible classes” error ($ERR_INCPTCLS).

If the collection is a Synergex.SynergyDE.Select.Select class, the specified record is always retrieved.

The loop variable must be the same type as the elements in the collection, or an “Invalid Cast” exception will occur. You can use the “AS type” syntax to accommodate cases in which the loop variable is different from the collection type and avoid generating an error. For example, if you had the FOREACH statement

foreach mydecimalvar in arraylist

and you declared

ArrayList.Add(myi4var)

the decimal variable would not be an int and an error would occur. However, if your FOREACH statement were as follows, no error would occur:

foreach mydecimalvar in arraylist as @int

Similarly, if the collection contains elements that are type structfield and the loop variable is type a, your FOREACH statement might look like this:

foreach avar in arraylist as @structure_name

FOREACH also won’t work with a mixed-type array list in Synergy .NET, so make sure you either cast as you add items to the array list or use “AS type.” In traditional Synergy, FOREACH does work with mixed-type array lists, so you can use “AS type” to move code designed for traditional Synergy to Synergy .NET.

Note

A non-object cannot be used as the loop variable if the IN variable is a collection, unless “AS type” is used in Synergy .NET to force a possible conversion.

You can also use AS type to specifically declare the loop variable’s type when the DATA statement within a FOREACH is declared. For example, even though the element type of an ArrayList defaults to object, you may want to specifically declare the type of lvar as @struct1.

data alist = new ArrayList()
foreach data lvar in alist as @struct1
begin
  Console.WriteLine(lvar.fld1)
end
Note

In Synergy .NET, the DATA statement within the FOREACH allows the compiler to create a local data variable within the scope of the FOREACH loop whose type is the element type based on the collection (or the AS type if declared). In the example below, the compiler will create an lvar local data variable whose type is based on an element in the collection itself, in this case @string:

data arr, [#]string, new string[#] {"one", "two", "three"}
foreach data lvar in arr
begin
  Console.WriteLine(lvar)
end

This determination of lvar’s type will work on collections that are dynamic arrays or real arrays, any collections that implement IEnumerable, IEnumerable<T>, or IAsyncEnumerable<T>, or any class that implements GetEnumerator(), which would return one of the previously mentioned collection types. For example, List<int> implements IEnumerable<int>, so lvar’s type will be int.

The enumerator created implicitly by the FOREACH statement is destroyed (disposed in .NET) at the end of the loop. In .NET, if the collection implements the dispose pattern and is a new instantiation, the temporary object created by the new instantiation has its Dispose method called automatically at the end of the loop.

Note

Beware, in .NET, when instantiating a Select object using a From object handle:

foreach rec in new Select(fromobj, whereobj)

The Select object will be disposed at the end of the loop along with the From object referenced by fromobj. Subsequent use of fromobj unless reinstantiated will result in a System.ObjectDisposedException. Instead, instantiate the From object from within the FOREACH:

foreach rec in new Select(new From(file, rec), whereobj)

Also, moving the instantiation of the Select outside the FOREACH will turn off the automatic disposal, allowing you to dispose at the appropriate time.

A FOREACH loop can also iterate over an ASYNC collection. Place an AWAIT keyword before the FOREACH statement to iterate over the elements of an IAsyncEnumerable<T>, as shown in the third example below.

Examples

The example below iterates through the alist collection for every customer.

import System.Collections

namespace ns1
    class customer
      public name, string
      public method customer
        parm1, string
      proc
          name = parm1
      endmethod
    endclass
endnamespace
main 
record
    alist       ,@ArrayList
    c1          ,@customer
proc
    alist = new ArrayList()
    alist.Add(new customer("joe"))
    alist.Add(new customer("fred"))
    alist.Add(new customer("fran"))
    open(2, o, "tt:")
    foreach c1 in alist
    begin
      writes(2, c1.name)
    end
    close(2)
end

The example below uses a GetEnumerator extension method.

import System.Collections
namespace ns
    public class Base
    endclass
endnamespace
namespace MyExtensions
    public static class MyExtensionsClass      
        ;Put extension method on base class
        public extension static method GetEnumerator, @System.Collections.IEnumerator
            parm1, @Base
            record
                alist, @ArrayList
        proc
            alist = new ArrayList()
            alist.Add(1)
            alist.Add(2)
            mreturn alist.GetEnumerator()
        endmethod
    endclass
endnamespace
import MyExtensions

main
proc
    data ivar, int
    foreach ivar in new Base()
        Console.WriteLine(ivar)
endmain

The following example shows a FOREACH loop that iterates over an ASYNC collection.

import System.Threading.Tasks
import System.Collections.Generic
namespace ns
    public class class1
      public static async method Create, @IAsyncEnumerable<int>
      proc
        await Task.Delay(1)
        yield mreturn 100
        yield mreturn 200
      endmethod

      public static async method Consume, @Task
      proc
        data ivar, int
        await foreach ivar in Create()
          Console.WriteLine(ivar)
      endmethod
    endclass
endnamespace