Class members

A class member is any item that can be declared within a class declaration, such as a method, a field, a property, another nested class, and so on. This topic discusses the following class members:

Methods

A method is a routine within a class declaration that performs an operation using the fields of that class. When you declare a method, you can specify the following:

Method signatures

A method signature is the fully qualified name of a method, which includes the namespace name(s), class name(s), method name, argument types, and return type, in the form

namespace[.namespace_n].class[.class_n].method(type[, type, ...])rettype

where namespace is the namespace or nested namespace names and class is the class or nested class names containing the method method. Type can be one of the following:

Rettype is the method’s return type, which in addition to the above types, includes VOID.

Overriding a method

Overriding a method means providing a method declaration in an inherited class that supersedes the method declaration with the same signature in the parent class. For every parent class method that you want to override, you must add a method declaration and implementation to the derived class and specify the OVERRIDE modifier in the METHOD statement. The signature for the derived class method must match the signature for the parent class method that’s being overridden. In addition, the parent class method must be marked as VIRTUAL, ABSTRACT, or OVERRIDE.

Note

On OpenVMS, if the class is in a shared image, you must do an OPENELB of the shared image so that the virtual method can be found. (See OPENELB for syntax.)

Overloading a method

A method is overloaded when another method in the same class has the same name but different arguments. Overloading enables you to perform analogous operations on different types of data. Calls to methods are resolved based on the types of the arguments passed in the call, and the best match is selected from overloaded methods.

In Synergy .NET, overloading by calling convention (that is, a BYVAL parameter and a BYREF parameter of the same type) is not supported.

Subroutines and functions

You can add a subroutine or function to a class by enclosing the existing subroutine or function inside a class declaration.

The following rules apply to subroutines and functions within a class:

Class fields, records, groups, and structures

A class can optionally contain one or more field declarations, which may or may not be encapsulated in a record declaration. For example,

public class myclass
    private myfield     ,i4
  public record myrecord
    myfield2    ,i4
    myfield3    ,i4
  endrecord
endclass

A class record can either be named or unnamed. If a record identifier is declared, it represents an instance variable of that record type within the class and can be used to access fields in that record. Field identifiers are optional in a named record. However, if the record is unnamed, fields in that record must have identifiers.

A field declared in a class outside of a record is called a loose field (for example, myfield in the example above). Only loose fields of .NET‑compliant types are accessible from other languages. (Optimally all .NET class fields are defined this way.)

You can also declare a named group within a class record. A named group and its members inherit the accessibility of the record, if specified; otherwise, they default to private.

You can optionally declare any field modifier or access modifier on a record within a class. The specified modifiers apply to all groups and fields in that record unless overridden at the fieldlevel. (See Defining a field for the syntax of a field definition and for more information about field modifiers and access modifiers.)

Neither a common record nor a global data section may be declared within a class.

The ENDRECORD delimiter within a class or routine is optional. If you don’t specify it, the next nonfield member of the class delimits the record.

Upon instantiation of a class that contains fields, all of the fields in that class are initialized. You can get or set an accessible field value from an instantiated class as follows:

value = var1.myfield
var1.myfield = value

The lifetime of a class field, class record, or class group is determined by the lifetime of the class instance. This is called instance data. There is one copy of instance data in each object instance of a given class.

When a data field is designated as STATIC, its value can be accessed (both read and write) even if the class that contains the field hasn’t been instantiated. Only one copy of static data exists, while many copies of instance data may exist. To access a static field for a class, append the class name to the beginning of the field name. For example,

MyClass.myfield = value

Classes can also contain traditional Synergy and Synergy .NET structures, which can have accessibility modifiers to determine when they can be used. The ENDSTRUCTURE delimiter within a class or routine is optional.

Properties and indexers

A property is usually used to provide controlled access to data within a class. In referencing routines, properties can be treated as if they were class fields, with certain restrictions. These restrictions include the following:

A field name and a property in the same class can have the same name as long as the case is different. In this situation, the compiler becomes case sensitive when resolving paths within that class. If the calling path doesn’t exactly match the case of either member, an “Ambiguous symbol” error (AMBSYM) occurs.

To add a property to a class and then use it in a program,

1. Create a new class or edit an existing class.
2. Use the PROPERTY statement to add a property to the class defining a get accessor method
and/or a set modifier method that accesses a field value.

For example,

public class myclass
    private myfield, i4
    public property myproperty, i4
        method get
        proc
            mreturn myfield
        endmethod
        method set
        proc
            myfield = value 
        endmethod
    endproperty
endclass
3. Save the file containing the class declaration and compile it.
4. Add code to use the property. For example,
c1, @myclass
var1, i4
c1 = new myclass
c1.myproperty = 5
var1 = c1.myproperty

An indexer allows methods on an object to be called as though the object is an array. An indexer has the same requirements as a property, with a few additional requirements:

m, @myclass
i, i4
m = new myclass()

m[2] = 23 would run the set accessor, and i = m[2] would run the get accessor.

See Indexer properties for an example.

Delegates

A delegate is a type‑safe function pointer used by the .NET Framework that enables you to pass a method as a parameter value. See DELEGATE‑ENDDELEGATE for more information.

Events

An event is an action that occurs as a result of another action (for example, a mouse click). See EVENT for more information.

Interfaces

An interface is a contract for a class that defines the properties, methods, and events that the class must use. See INTERFACE‑ENDINTERFACE for more information.

Enumerations

An enumeration is a data type that consists of a set of named values. See ENUM for more information.

Nested classes

A nested class is a class declared within another class declaration. There is no limit to the number of nesting levels you can have.

You must use the name of the outer class as well as the nested class when referencing a nested class. In other words, the outer class name acts as if it were a namespace. For example, to create an instance of a nested class named class3, you would do the following:

p1, @class2.class3
p1 = new class2.class3( )

A nested class has access to any of the other members of an outer class, regardless of accessibility.

Instance data would be accessed via an instance of the containing class, while static data would be available using the class itself. For example,

class c1
    static      x       ,i4
    y                   ,i4
    class c2
        method          m1      ,void
        record
            h1                  ,@c1
        proc
            h1 = new c1()
            c1.x = 5
            h1.y = 10
        end
    endclass
endclass

Also see Nested class requirements, along with the rest of the syntax specification for CLASS‑ENDCLASS.

Operators

For a list and explanation of the operators that can be declared within a class, see Object operators.

Constructors and destructors

A constructor is a class member method that is called whenever an instance of the class is created. It has the same name as the class. It is common to put initialization code in a constructor.

Constructors can have parameters, and a class can have multiple constructors as long as the method signatures are different.

For example,

public class class2
  public method class2          ;Default constructor
  endmethod
  public method class2          ;Constructor that takes an i4
      p1,       i4
      endparams
  endmethod

A constructor cannot have a return type. Only access modifiers (PUBLIC, PROTECTED, and PRIVATE) and STATIC are allowed on a constructor. If you don’t declare a constructor, the compiler automatically generates a default constructor that has public accessibility. A static constructor is called on the first reference to the type, which could be when an instance constructor is called or when a static member is accessed directly. The debug name of the constructor is !MyClass. (MyClass is the name of the class containing the static constructor.)

You can also declare a constructor initializer to run in conjunction with a constructor. The initializer can be specified as parent, to run a parent class constructor, or this, to run a current class constructor, followed by zero or more argument values.

To call the sibling constructor (using the specified optional arguments), use this syntax:

this([arg1][, arg2][,...])

To call the base constructor (using the specified optional arguments), use this syntax:

parent([arg1][, arg2][,...])

If you don’t specify an initializer, the default constructor, parent(), is assumed.

For example, if the mychild class extended another class and you wanted to run its constructor that took an i4 value, your code might look like this:

method mychild 
    p1, i4
    endparams
     parent(p1)
proc
endmethod

If this constructor called another constructor within mychild, it might look like this:

method mychild 
    p1, i4
    endparams
     this()
proc
endmethod

The initializer method signature must resolve to a constructor method in the parent class (if parent is used) or in the current class (if this is used). In traditional Synergy, you must not create a circular reference; otherwise, you will create an object that is never destroyed. (See Accessing members of the current instance or base class for more information about this and parent.)

A destructor is a class member function that is called before an instance of the class is destroyed. You can use it to perform any class‑specific cleanup operations before the last reference to the class instance is cleared.

You can optionally declare a single destructor for a class by giving the method the same name as the class with a tilde (~) at the beginning. For example,

public class class2
    method ~class2

By default, a destructor is a virtual method that has protected accessibility.

Only one destructor is allowed for each class. A destructor declares no parameters and cannot return any values.

Important

A destructor must never throw an exception, because it is called as part of the runtime cleanup. If errors in a destructor are not trapped, they can cause the runtime to enter an infinite loop. Therefore, you should always use the ONERROR or TRY‑CATCH statement in a destructor to ensure the destructor exits.

In Synergy .NET, destructors are nondeterministic. Order and timing are at the discretion of garbage collection, and they may even execute after a STOP statement.

See Instantiation and destruction for more information.