User-defined generics in ABL
- Last Updated: May 22, 2024
- 8 minute read
- OpenEdge
- Version 13.0
- Documentation
Introduction
Generic types can be used in ABL to write classes and interfaces in which the type parameters are abstracted out. This allows the class to defer the specification of such parameters until the class is declared and instantiated by a client of the class. Using a generic type reduces and simplifies code since you do not have to create a class for each distinct type and you do not have to cast references at runtime, ensuring type safety.
Algorithms and data structures are a good use case for generics because data
structures have a type. If the type is extracted out through use of generics, then
the code can be greatly simplified. The built-in
Progress.Collections classes (List,
SortedSet, and HashMap) are examples of such a
use case.
See Generics concepts and terminology in object-oriented ABL for more information on the terminology used for generics in ABL. The rest of this topic describes the mechanics of ABL that allow user-defined generic types.
CLASS statement
You use the CLASS statement to define a generic type by specifying the type
parameter and type constraint within angle brackets (<>). This
is the simplified syntax for the CLASS
statement (additions to support generics are shown in bold):
|
- <type-param AS type-constraint [, type-param AS type-constraint]...>
- Type-param is a type parameter name for a generic type.
Type-param is, by convention, a single uppercase
letter. For instance,
T(for type),K(for Key) andV(for value) are some commonly used type parameter names.
The type-param acts as a type alias for the type arguments supplied by the client of the generic type. The type parameter name has the same naming restrictions that apply to class, interface or enum names (for example, alphanumeric, first character must be alpha, and so on). The following is an example of a user-defined generic type:
|
The type-constraint must be either an object-oriented ABL interface or
class (deriving from Progress.Lang.Object). .NET types are not
allowed as arguments on the user of the type. Primitive types (such as INTEGER,
CHARACTER, ...) are also not allowed. The following examples show invalid
syntax:
|
The type-params may be referenced in the same way any type may be referenced inside the class. The following statements are examples of references to the type parameter:
|
Note that like non-generic classes, generic classes must be in a file with the
.cls extension and the name of the class in the
CLASS statement must match the name of the file. For example,
CLASS SimpleGenericType<T as Progress.Lang.Object> must
be saved in the file SimpleGenericType.cls. This means the
class type names share the same namespace and you cannot have a generic class and
non-generic class with the same name. When compiling such classes, you get a single
r-code (.r) file for the generic type.
You can have more specific type constraints that limit the types of type arguments a client of the generic type can specify. For instance, you can define a type as a specific class type:
|
In this case, the client of this generic type is allowed to specify a type argument
with a type that is compatible with mypackage.myroot. The same rule
applies to an interface type defined as the type constraint.
The type-param may be referenced in the INHERITS and
IMPLEMENTS phrases as a type argument to a generic type. The
following is an example using the built-in
Progress.Collection.IComparable interface:
|
Since type constraints must be object-oriented ABL types, they can also be a generic type with type arguments. For example:
|
In the example above, the client using generic type SimpleGeneric
must specify a type argument that is compatible with
List<Object>.
Non-generic classes can inherit or implement generic types, and vice versa. For example:
|
Note in the third example that the class being defined,
SimpleClass2, is also being passed as the type argument for the
generic class it is inheriting, SimpleGeneric. The argument is the
type that is passed during instantiation. This is comparable to having a variable in
the class having the type of SimpleClass2:
|
The access modifiers (PUBLIC, PROTECTED, and so on) apply to
generics as well. Generic types may also be declared to be either
ABSTRACT or FINAL, just like any other class
type and the existing rules apply.
INTERFACE statement
You use the INTERFACE statement to define a generic type by specifying the
type parameter and type constraint within angle brackets (<>).
This is the simplified syntax for the INTERFACE statement (additions to support generics are shown in
bold):
|
- <type-param AS type-constraint [, type-param AS type-constraint]...>
- Type-param is a type parameter name for a generic type.
Type-param is, by convention, a single uppercase
letter. For instance,
T(for type),K(for Key) andV(for value) are some commonly used type parameter names.
The same rules for type parameters and type constraints described for the
CLASS statement also apply to the INTERFACE
statement.
The following examples show generic interfaces, along with the type parameters and supported inheritance:
|
Non-generic interfaces can inherit generic interfaces and vice-versa as shown in the following example:
|
Type references
ABL supports generic types everywhere an object-oriented ABL reference can be used.
This includes variable and property definitions, parameter definitions, return type
for methods (in class or interface) or user-defined functions, the
NEW expression, the TYPE-OF(),
CAST() and GET-CLASS() functions, the
USING statement, and other places.
Type arguments cannot be primitive types (for example, INTEGER, CHARACTER), .NET types, or handle-based objects. For example:
|
Overloading
The existing rules for method overloading apply to generic classes.
You cannot define more than one method that takes the type parameter as a parameter. This is similar to any object-oriented class. Therefore, the following code is invalid:
|
You should be cautious regarding methods where the type parameter is involved. For example, given a generic class like this:
|
If the client of that generic type passes Employee as the type
argument, then the compiler will generate an ambiguous call error for this piece of
code, since there are two Add() methods that could satisfy the
method call:
|
Now consider the following method defined in the above class, where the
Add() method is called from within another method in the same
class:
|
Because T is defined as a constraint on
Progress.Lang.Object, the compiler matches
obj to T in the first Add()
call. The second one can match to the specific type.
T by
the client of the generic type, to avoid confusion and errors.Overriding
Everywhere object-oriented ABL permits an element (method, property, event) to be overridden, elements within generic types may also be overridden. When an object-oriented ABL element is overridden, it may only be overridden with the exact matching signature. This is the existing rule for object-oriented ABL objects and is no different for elements which have generic types in their signature.
Object serialization
Generic types can also be defined as SERIALIZABLE and the existing rules for
serialization apply, that is, all classes in the hierarchy, and the class types of
all data member used in the class (unless defined as
NON-SERIALIZABLE) must also be serializable (unless the type is
an interface). In addition, the type for the type parameters of a generic class must
also be serializable. Otherwise, you get a compile time error when compiling a class
with the SERIALIAZABLE option. For example:
|
The compiler raises an error if either SimpleGeneric or
myObjType is not marked as SERIALIAZABLE.
Additionally, the type argument of a client of the generic class must also be
SERIALIAZABLE for serialization of the object to occur,
otherwise you get a runtime error when trying to serialize such object.
Reflection
Reflection on user-defined generic types is supported. See Progress.Lang.Class class which contains the following methods: IsGeneric(), GetTypeParameters(), and GetTypeArguments(),
Events
When an event is defined, a signature is specified to be used for the event. Subscribers may subscribe to their own event handler method, which must match the signature defined for the event. The publisher publishes the event using the same signature. That signature may reference object-oriented ABL types, including generic types.
The following example defines an event in eventClass with a generic
parameter myList<custEntity>:
|
The following example subscribes to the above event and the subscribed method:
|
The compiler does the same type compatibility checks for generic types that it does for non-generic types.
Restrictions
See Restrictions of user-defined generics for information on the restrictions of user-defined generics.
Example
For sample code of user-defined generics, see Example: User-defined generics.