Show/Hide Toolbars

XSharp

Tuple generic data type

 

A tuple is a data structure that groups multiple data elements in a lightweight data structure. Tuples can be used as an easy way to represent multiple data elements, pass them as argument(s) or receive them as a return value, without needing to declare a complete new class or structure for holding the data set.

 

For example, the following code uses the generic System.Collections.Generic.Tuple type to receive multiple values from a function, without the need to declare multiple REF/OUT parameters or to create a new dedicated type for holding them:

USING System.Collections.Generic
 
FUNCTION GetCustomerData() AS Tuple<STRING, INT, LOGIC>
  LOCAL oCustomer AS Tuple<STRING, INT, LOGIC>
  oCustomer := Tuple<STRING, INT, LOGIC>{"Nikos", 47, TRUE}
RETURN oCustomer
 
FUNCTION Start() AS VOID
  LOCAL oCustomer AS Tuple<STRING, INT, LOGIC>
  oCustomer := GetCustomerData()
  ? "Customer name:", oCustomer:Item1
  ? "Age:", oCustomer:Item2
  ? "Is active:", oCustomer:Item3

TUPLE keyword

 

X# also supports the dedicated keyword TUPLE, which offers an easier syntax for declaring and working with tuples. The above code can be written in a simpler way with the TUPLE keyword syntax:

 

FUNCTION GetCustomerData() AS TUPLE(STRING, INT, LOGIC)
  LOCAL oCustomer AS TUPLE(STRING, INT, LOGIC) // defining the tuple
   // tuple item types must match the types in the Tuple definition
  oCustomer := TUPLE{"Nikos", 47, TRUE}
RETURN oCustomer

FUNCTION Start() AS VOID
  LOCAL oCustomer AS (STRING, INT, LOGIC)
  // the TUPLE keyword in the tuple definition can even be omitted!
  oCustomer := GetCustomerData()
  ? "Customer name:", oCustomer:Item1
  ? "Age:", oCustomer:Item2
  ? "Is active:", oCustomer:Item3

Note that when using the TUPLE keyword, the compiler internally uses the type System.ValueTuple, instead of the generic type System.Collections.Generic.Tuple. Also, when defining the tuple, the TUPLE keyword can be omitted, thus using the more c#-like syntax (type1, type2, ...).

Named tuples

The dedicated TUPLE keyword syntax also supports specifying custom names for the tuple items, instead of using the generic names Item1, Item2, etc. This way, the code can become much more readable and close to what it would be like if using a separate new class for the data:

FUNCTION Start() AS VOID
  LOCAL oCustomer AS TUPLE(Name AS STRING, Age AS INT, IsActive AS LOGIC)
  // provide names for each item of the tuple
  oCustomer := GetCustomerData()
  ? "Customer name:", oCustomer:Name
  ? "Age:", oCustomer:Age
  ? "Is active:", oCustomer:IsActive

To make code even more self-explanatory, a named tuple can also be instantiated by using named arguments:

FUNCTION GetCustomerData() AS Tuple(STRING, INT, LOGIC)
  // defining the named tuple with custom item names
  LOCAL oCustomer AS Tuple(Name AS STRING, Age AS INT, IsActive AS LOGIC)
  // including tuple item names in the tuple instantiation
  oCustomer := TUPLE{Name := "Nikos", Age := 47, IsActive := TRUE}
RETURN oCustomer

Defining tuples with VAR

Tuples can also be defined and instantiated at the same time using the VAR keyword:

 

FUNCTION Start() AS VOID
  VAR oTuple := TUPLE{"Nikos", 47, FALSE}
  ? oTuple:Item1, oTuple:Item2, oTuple:Item3 // "Nikos", 47, FALSE

 

In this case, the data type of each tuple item is inferred from the item types supplied in the code. In the above sample, the item types are (STRING, INT, LOGIC).

 

Tuples with the same item types can be assigned to each other (thus copying the item values from the source tuple to the destination tuple), and the same can be done for tuples defined with either LOCAL or VAR:

FUNCTION Start() AS VOID
  VAR oTuple := TUPLE{"Nikos", 47, FALSE}
  LOCAL oNew AS TUPLE(STRING, INT, LOGIC)
 
  oNew := oTuple
  ? oNew:Item1, oNew:Item2, oNew:Item3 // "Nikos", 47, FALSE

When defining a tuple with the VAR keyword, item names can be provided as well:

FUNCTION Start() AS VOID
  VAR oTuple := TUPLE{Name := "Nikos", Age := 47, IsActive := FALSE}
  ? oTuple:Name, oTuple:Age, oTuple:IsActive // "Nikos", 47, FALSE

Finally, when defining a tuple with VAR by using identifiers for the item values, instead of using literal values, then each item automatically gets a name by the identifier name used for it:

CLASS CustomerInfo
  EXPORT Description := "Customer description" AS STRING
END CLASS
 
FUNCTION Start() AS VOID
  LOCAL name := "Unknown Customer" AS STRING
  LOCAL oInfo := CustomerInfo{} AS CustomerInfo
 
  VAR oTuple := TUPLE{name, oInfo:Description}
  ? oTuple:name // "Unknown Customer"
  ? oTuple:description // "Customer description"

Tuple deconstruction

A tuple can be deconstructed to multiple plain variables in one line of code, using the (var1, var2, ...) syntax:

FUNCTION Start() AS VOID
  LOCAL oCustomer AS TUPLE(Name AS STRING, Age AS INT)
  oCustomer := TUPLE{"Nikos", 47}
 
  LOCAL name AS STRING
  LOCAL age AS INT
 
  (name, age) := oCustomer
  ? name, age // "Nikos", 47

The local variables can also be defined and assigned to the tuple item values in a single line, with a special LOCAL syntax for tuples:

FUNCTION Start() AS VOID
  LOCAL oCustomer AS TUPLE(Name AS STRING, Age AS INT)
  oCustomer := TUPLE{"Nikos", 47}
 
  LOCAL (name AS STRING, age AS INT) := oCustomer
  ? name, age // "Nikos", 47
  // You can also deconstruct into existing local variables without the LOCAL keyword:
  (name, age) := oCustomer

Additionally, the VAR keyword can be used for the tuple deconstruction, in which case the variable types are inferred from the tuple item types:

FUNCTION Start() AS VOID
  LOCAL oCustomer AS TUPLE(Name AS STRING, Age AS INT)
  oCustomer := TUPLE{"Nikos", 47}
 
  VAR (name, age) := oCustomer
  ? name, age // "Nikos", 47