Chapter 8

Functions and Methods

Method and Functions are very important concepts in any programming language. Whereas in C# every statement is part of a class method, in X# it can be a function as well.

No rules without an exception: C# 9.0 introduced top-level statements that are outside any class or function. This feature is not available in X#. An equivalent is the Start() function which is always called first.

Methods will be discussed in more detail in Chapter 11 (OOP) and I won't go into details about functions because I assume that every reader already knows what they are. This chapter is more aimed at C# developers who might be unfamiliar with the concepts of functions. The rest of this chapter is about optional arguments and arguments passed by reference.

Method definitions

Methods are defined with a Method keyword followed by the name of the method. If the method definition does not contain parameters, the parentheses are optional. But I recommend using them anyway for the sake of consistency. If a Method does not return a value, the return command is an optional tool. But again, for the sake of consistency, it does not hurt to end every method with a Return statement, except for the constructor of a class.

A short distraction with functions

Whereas in C# a function is just another term for a method in X# you can define "real" functions with the function keyword. A function always has a data type. But it can be void as with a Method that returns nothing.

Important: There is much more to a function definition than a name, parameters, and data type. If you don't have much experience with VO, please see the documentation in the help file. I won't discuss any of these special topics although they are important.

Function definitions are not part of class definition so they are not members of a class. They are separate entities that contain code. They use parameters and they possess a return type and therefore a data type.

The classic "Hello, World" is short in an X# console application:

Function Start() As Void
  ? "Hello, World, how dare you?"

Since everything has to be part of a class, the X# compilers create a functions class internally and the function becomes a regular method (you can verify this with tools like IlDasm or better ILSpy in no time).

As already mentioned in the introduction to this chapter, with version 9.0 C# offers so-called top level statements as an alternative to a "dummy" class with a main method for console applications. This application type doesn't even need a function as an entry point.


TIP: Probably in an attempt to make old VO programmers happy, functions in X# can be defined with the Func statement too even in the core dialect.


Going back in time with PROCEDURE

When I started BASIC programming way back in the 70s, there were subroutines and "Unterprogramme". Other languages (like COBOL) had procedures. Modern Visual Basic has the Sub keyword for declaring methods without a return type. These terms sound much more like classic programming than Functions and Methods. Luckily, you can have at least procedures in X# thanks to the Procedure keyword. A Function Start() As Void can be written as Procedure Start (without parentheses) in the Core dialect.


NOTE: Although I had never a need for this, both function and procedures can be made local by using the Local keyword which means they can be part of another function or procedure.


Processing command line arguments

Processing command line arguments within a console application is really easy. All there is to do is to include a parameter of type String[] in the Start function.

**Example 8.1: (XS_CommandLineArgs.prg) **

// Processing command line args

Function Start(Args As String[]) As Void
  ? i"Args: {Args.Length}"
  ForEach var Arg in Args
    ? i"Arg >> {Arg}"
  Next

The arguments have to follow the name of the exe file separated by blanks or commas. Apostrophes or quotes are only required if a single argument value consists of blanks (or commas).

This technique cannot be applied inside a WinForm or WPF application. In this situation, use the GetCommandLineArgs() method of the Environment class of the .Net runtime. Of course, it will work in a Console Application too. But there is a subtle difference. The first argument is always the path of the exe file so there is always at least one argument.

**Example 8.2: (XS_CommandLineArgsEnvironment.prg) **

// Processing command line args with the Environment class

Function Start() As Void
  var args := Environment.GetCommandLineArgs()
  ? i"Args: {args.Length}"
  ForEach var Arg in args
    ? i"Arg >> {Arg}"
  Next

Iterators (functions) and the yield statement

Iterators are a fascinating species in any programming language, thanks to the X# developer team they are also available in X#. An iterator also called a generator function, allows enumerating a (regular) function like a regular list. With each iteration, the function delivers the next "generated" value through the yield statement that precedes the regular return statement. The internal generator keeps its states so that the next "request" by calling the next() method of the internal iterator either delivers the next value or is null.

What the function does, does not matter. The only restrictions are that the return value is either IEnumerable, IEnumerable, IEnumerator or IEnumerator and instead of a return a value is delivered by a yield return statement.

**Example 8.3: (XS_Iterator.prg) **

// Example for an Iterator

using System.Collections.Generic
using System.IO

Function GetIniFiles() As IEnumerable<FileInfo>
   var iniFiles := DirectoryInfo{"C:\Windows"}:GetFiles("*.ini")
   ForEach var iniFile in iniFiles
      yield return iniFile
   Next

Function Start() As Void
   ForEach var iniFile in GetIniFiles()
     ? iniFile:Name
   Next

Defining methods

A method is called a function in other circumstances so it's a simple and well-known subject anyway (that does not imply that functions are an easy subject - If you ever try to understand the concepts of functional programming you will soon find out that a function is not just another name for a "subroutine";)

There is a big difference between the syntax of X# and C#.

A C# method

A method in C# always starts with the data type of the method followed by the name of the method and an obligatory pair of parentheses. The body of the method is always represented in curly braces.

**Example 8.4: (XS_CSharpMethod.cs) **

// An example for a method in C#
using System;
using System.Threading;

class helper
{
  static private string getPassword(int strength=8) {
      string pw = "";
      for(int i=0;i<strength;i++) {
         pw += (char)((new Random(DateTime.Now.Millisecond)).Next(65,91));
         Thread.Sleep(100);
      }
      return pw;
  }
}

The X# version of that method

In X# method is always defined through the Method keyword followed by the name of the method. If the method does not have parameters, the parentheses are optional.

**Example 8.5: (XS_XSharpMethod.prg) **

// Example for a method with a default parameter

using System
using System.Threading

Class Helper

  Static Public Method getPassword(Strength := 8 As Int) As String
      Local pw := "" As String
      For var i := 0 UpTo Strength
         pw += ((char)((Random{DateTime.Now.Millisecond}).Next(65,91))):ToString()
         Thread.Sleep(100)
      Next
      Return pw
    End Method

End Class

Function Start() As Void
   ? Helper.getPassword()

Calling conventions

There are two calling conventions for method definitions: Strict and Pascal. The first is the default and the latter is ignored for managed code. The calling convention only matters when calling a function in an external DLL that is not a .Net assembly, like a C++ library or the Win32 API. In this case, the calling convention had to be used that is expected by the called function.

Optional arguments

Any method (or function) can define optional arguments by setting a default value for the specific parameter. Therefore, an argument can be omitted for this parameter. It's probably debatable if overloaded methods with different parameter sets are a better alternative (I would say so) but since C# got optional arguments together with named arguments (mostly for accessing object interfaces written in other languages) it's OK to use them in X# as well.

Function LogIt(Msg As String, Level := LevelType.Normal As LevelType) As Void

The LogIt function can be called like this:

LogIt("Application has just started normally")

In this case, the Level argument is Normal. Or like this

LogIt("Oh, no! Something went wrong. We are going to crash...", LevelType.Dangerous)

The following example uses the function calls as part of a small console application with a simple log function and a parameter with optional arguments.

**Example 8.6: (XS_OptionalArguments.prg) **

// Optional arguments for method parameters
Using System.IO

Enum LevelType
   Harmless
   Normal
   Dangerous
End Enum

Function LogIt(Msg As String, Level := LevelType.Normal As LevelType) As Void
    var logPath := Path.Combine(Environment.CurrentDirectory, "Logit.log")
    Msg := ie"{Level}: {Msg}\n"
    File.AppendAllText(logPath, Msg)

Function Start() As Void
   LogIt("Application has just started normally")
   LogIt("Oh, no! Something went wrong. We are going to crash...", LevelType.Dangerous)
   LogIt(Level:=LevelType.Harmless, Msg:="Sorry, a false alarm:(")


NOTE: Optional arguments do not have to be the last argument when compiling for non-core dialect. In that case, the argument is stored in a special attribute that is added to the parameter. At compile time (when the function/method is called), the compiler inspects the missing argument and uses the value from that attribute as the default value. This also happens in late-bound code.


Named arguments

In the Core dialect, it is always possible to precede an argument with the name of the parameter. Like in C#. A small advantage is that the order of the arguments does not matter anymore since every argument is "named".

LogIt(Level:=LevelType.Harmless, Msg:="Sorry, a false alarm:(")

**Example 8.7: (XS_NamedArguments.prg) **

// An example for named arguments

Class C

  Static Method Logit(Msg := "Default" 

As String) As Void
    ?Msg
    Return

End Class

Function Start() As Void
  C.Logit("Don't worry, be happy")
  C.Logit(Msg := "Not reason to be concerned")

Named arguments work in other dialects too when the compiler option /name args is used (Allow Named Arguments).

Passing values by reference

Arguments are passed by value as default. For arrays or lists, passing the value by reference can be programmed a little easier, and some methods like TryParse use a ref(reference) parameter and therefore expect a passing by reference.

Passing an argument by reference is easy. Like in C#, it requires the out' keyword both as part of the parameter declaration and the function or method call. In the method or function definition, out replaces the as keyword.


NOTE: For compatibility with Vulcan.Net ref can be used instead of out but the latter is the official way of declaring a reference parameter.


The most popular reason from my experience for using ref parameters is being able to have more than one return value from a method or function call. I would personally prefer using a Structure (or Tuple) as a return value (using "Separation of Concerns" as a design principle), but using ref parameters is shorter, and more practical and it always works without having to define new structures for a return value. An example would be passing a variable for a potential exception object when calling a database method. Instead of depending on the return value only, the exception variable passed as a ref parameter is either null or contains the thrown exception object.

But first, a simple example of how to use TryParse with an ref argument.

To convert a string into a number, every value type of the .Net runtime offers both a Parse() and a TryParse() method. The difference is, that TryParse does not throw an exception if the conversion is not possible for some reason. TryParse just returns True or False. The converted value is "returned" through an ref parameter:

If Int32.TryParse(input, out Number)

**Example 8.8: (XS_TryParseWithOut.prg) **

// Example for ref parameter with the TryParse method

Function Start() As Void
   Console.WriteLine("Enter your code (numbers only)")
   Var input := Console.ReadLine()
   Local InputNumber As Int32
   If Int32.TryParse(input, out InputNumber)
      Console.WriteLine(i"Thank you for the number ({InputNumber})!")
   Else
      Console.WriteLine("How dare you, this was not a number!")
   EndIf


TIP: A nice abbreviation is possible in X# by using the var keyword in combination with out. This avoids a separate variable declaration. This is not possible in C#.


To allow the creation of an object by providing some text value, any class can implement its own TryParse method:

Internal Static Method TryParse(Text As String, Value Out Person) As Logic
    Value := Person{}{Name := Text}
    Return True

The following example uses this technique as part of a complete console application program.

**Example 8.9: (XS_OutParameterExample1.prg) **

// Example for an ref parameter with the TryParse method in a class definition

Class Person
    Internal Property Name As String Auto

    Internal Static Method TryParse(Text As String, Value Out Person) As Logic
        Value := Person{}{Name := Text}
        Return True

    Public Method ToString() As String
        Return i"Name={Self.Name}"

End Class

Function Start() As Void
    Local p := Null As Person
    If Person.TryParse("Hugo", Out p)
      ? p
    Else
      ? "Not working, sorry"
    EndIf

The next example uses an array of numbers that is first passed by value and then by reference to a function that sorts the numbers in the array. If the array is passed by reference with out, there is no need for a return value.

Function SortArray2(numbers out Int[]) As Void
    Array.Sort(numbers)

Since the static Sort() method of the Array class does 'in place sorting', there is no output and no return value either.

**Example 8.10: (XS_OutParameterExample2.prg) **

// Example for a comparison between value and ref parameter

Function SortArray1(numbers as Int[]) As Int[]
    Array.Sort(numbers)
    Return numbers

Function SortArray2(numbers Out Int[]) As Void
    Array.Sort(numbers)

Function PrintNumbers(numbers as Int[]) As Void
    ForEach var n in numbers
        ? n
    Next

Function Start() As Void
    var numbers := <Int>{44,33,11,55,22}
    var numbers1 := SortArray1(numbers)
    printNumbers(numbers1)
    numbers := <Int>{44,33,11,55,22}
    SortArray2(numbers)
    printNumbers(numbers)