Show/Hide Toolbars

XSharp

In .Net, there are two kind of types that hold data; reference types (or classes), and value types (or structures). The two of them have different semantics in the way they are used, but both can contain other reference and/or value types themselves.

Classes

A class (or a reference type), is the most common way of storing data. Its name comes from the fact that a variable of a reference type (commonly referred to as an instance of the type) does not contain the data directly, but instead points to (references) a memory location where the actual data is stored. A class in X# is defined through the CLASS...END CLASS statement and can INHERIT from another reference type, implement one or more INTERFACES, and may contain fields, properties, constructors, methods, events and other items:

CLASS Customer
  EXPORT name AS INT // exported (public) field
  PROTECT age AS INT // protected field, not visible to code outside the class
END CLASS

Typically, classes and all their members are defined in a single file of code. If it is necessary for class members to be defined in multiple files (for example, when the amount of class members is very big), then the class must be defined in every file as PARTIAL:

PARTIAL CLASS Customer
// class members
END CLASS

Since an instance of a class is only storing a pointer to the data, one or more variables can point to the exact same object in memory. Assigning a variable of a reference type to another variable of the same type results in both representing the same data. Updating data using one reference automatically updates the other reference as well:

FUNCTION Start() AS VOID
  LOCAL one, two AS Customer
  one := Customer{}
  two := one // now both vars point to the same object in memory
  two:name := "Robert"
  ? one:name // also "Robert"

Nested Classes

A class can even contain other types (classes or structures). In this case, the type inside the main type is called a nested type. Nested types can be used by using the name of their container class and their own name, connected by a dot:

CLASS Customer
  CLASS NestedClass
    EXPORT FieldInNestedClass AS INT
  END CLASS
END CLASS
 
FUNCTION Start() AS VOID
  LOCAL oNested AS Customer.NestedClass
  oNested := Customer.NestedClass{}
  oNested:FieldInNestedClass := 100
  ? oNested:FieldInNestedClass

Nested classes are particularly useful for defining helper classes, classes that are only used within the context of the parent class to hold information only relevant to that class. Creating a nested class for this data, rather than using a regular class, results in better-structured code.

Structures

A structure (or a value type), in contrast to reference types, stores its data directly. It shares some similarities with the Visual Objects STRUCTURE feature (renamed to VOSTRUCT in X#), but it is much more powerful than in VO, as it can contain many of the same items as reference types, such as properties, constructors, and methods. However, unlike reference types, value types cannot inherit from other types or implement interfaces. They can, however, contain nested classes or structures. Value types can be defined using the STRUCTURE Statement:

STRUCTURE Vector2D
  EXPORT x AS INT
  EXPORT y AS INT
  METHOD Invert() AS VOID
    SELF:x := - SELF:x
    SELF:y := - SELF:y
END STRUCTURE

Since structures hold their data directly, instantiating them does not involve any additional memory consumption beyond the memory needed for the data itself (unlike reference types, which need memory for both the data and the pointer to the data) or any garbage collector activity. They are mostly suited as lightweight data containers, typically holding a small number of fields, (usually 2-4), but can also represent a single element, such as the System.Int32 (INT) or System.Boolean (LOGIC) data types. These types simply define the INT and LOGIC data types and include several methods for manipulating their data. Other commonly used system-defined structures are System.Drawing.Point and System.Drawing.Rectangle, all of which contain a a small number of data fields.

Structure semantics

Structures also have different semantics when using them compared to regular classes. It is not necessary to instantiate such a variable to use it, since declaring a var of a value type results to its data being allocated directly:

FUNCTION Start() AS VOID
  LOCAL vector AS Vector2D
  vector:x := 10
  vector:y := 20

Although, for convenience, it's possible to also define constructors in value types and instantiate them as with regular classes:

STRUCTURE Vector2D
  EXPORT x AS INT
  EXPORT y AS INT
  CONSTRUCTOR(vec_x AS INT, vec_y AS INT)
    SELF:x := vec_x
    SELF:y := vec_y
END STRUCTURE
 
FUNCTION Start() AS VOID
  LOCAL vector AS Vector2D
  vector := Vector2D{10,20}
  ? vector:x // 10

The most important difference that must always be taken under consideration, is that assigning a value type to another one results to the data of the source being copied to the destination, so unlike what happens with reference types, the data of the two variables are stored in separate memory locations and any changes to one variable will not affect the other:

FUNCTION Start() AS VOID
  LOCAL vec_1,vec_2 AS Vector2D
  vec_1:x := 10 ; vec_1:y := 20
  vec_2 := vec_1
  ? vec_2:x // 10, value was copied from first vector
 
  vec_2:x := 40 // put a new value to second vector
  ? vec_1:x // 10 again, first vector value still has its original value

For this reason structures are not suitable for very large objects, since assigning one to another or passing one as an argument to a method involves copying all data from the source to the destination. On the other hand, with regular classes, only the pointer to the data is passed as an argument to a method.

Equals operator

Another important difference between reference and value types, is the behavior of the equals operator (==). For reference types, the equals operator between two variables only compares the pointers themselves, not the data of the objects. So it returns TRUE only when both variables point to the same object and in all other cases it returns FALSE, even if the data both objects contain is the same:

CLASS ReferenceType
  EXPORT data AS STRING
END CLASS
 
FUNCTION Start() AS VOID
  LOCAL o1,o2 AS ReferenceType
  o1 := ReferenceType{}
  o1:data := "test"
 
  o2 := ReferenceType{}
  o2:data := "test"
 
  ? o1 == o2 // FALSE, because o1 and o2 point to different memory locations
 
  o2 := o1
  ? o1 == o2 // TRUE

On the other hand, by default the == operator cannot be used on structures and the compiler will report an error if you try to do so. It can be made possible to use it though, by defining an OPERATOR method in the structure that implements how the comparison with == should be done. In the sample below, the == operator is implemented to compare the actual data that the two compared structures hold, so that it returns TRUE, when the data is equal:

STRUCTURE ValueType
  EXPORT data AS STRING
  OPERATOR == (a AS ValueType, b AS ValueType) AS LOGIC
  RETURN a:data == b:data // let the equals == operator return true when the data of the two arguments is the same
END STRUCTURE

FUNCTION Start() AS VOID
  LOCAL o1,o2 AS ValueType
  o1:data := "test"
  o2:data := "nothing"
  ? o1 == o2 // FALSE
  o2:data := "test"
  ? o1 == o2 // TRUE

Note that it is possible to compare values of most common system defined structures like System.Int32, System.Boolean, System.Double, because they also have defined equals operator methods like the one in the code above.

Which one to use

Weather to use a class or a structure for holding data depends on the specific needs related to the particular data. For data holding a lot of information (for example a customer object) you would typically use a reference type, as such objects usually don't get instantiated very often, but usually "live" long for the duration of the program. For smaller objects, that are being a created, manipulated and copied between variables a lot of times and in particular in tight loops (like for example an object representing a Complex number, consisting of a real and an imaginary part, which can be used in a lot of calculations), it is' more suitable to use a structure, as this will typically lead to faster execution, with a lot less memory consumption and garbage collector activity. In any case, it's very important to carefully consider their differences in semantics when using value vs reference types.