Data types
- Last Updated: January 18, 2024
- 33 minute read
- OpenEdge
- Version 12.8
- Documentation
The data type of a data element defines what kind of data the data element can store. ABL supports the following basic kinds of data types:
- ABL built-in primitive types, including mappings to corresponding .NET primitive types
- Object types, which include both ABL and supported .NET object types, including both built-in and user-defined class, enumeration, and interface types
- ABL handle-based objects
- ABL arrays, including one-dimensional arrays of ABL primitive types, ABL object types, or .NET object types
ABL primitive types (see Table 1) are built-in data types that can hold values with relatively simple content and that support relatively simple operations that can typically be applied using built-in ABL operands, such as arithmetic or relational operands (for example, the + Addition operator, or the EQ or = operator). The values for all ABL primitive types, other than the MEMPTR, BLOB, CLOB, and LONGCHAR, are limited in memory size to 32KB. BLOB and CLOB fields can be up to 1GB in size. MEMPTR and LONGCHAR variables can be up to 2GB - 1 byte in size, but are limited by available memory.
Note that you can define BLOB and CLOB data types only for table or temp-table fields, and,
unlike most other ABL primitive types, the operations they support do not have built-in ABL
operands, but are available using built-in ABL functions (for example, the COPY-LOB statement). Note that
COPY-LOB file operations involving LONGCHAR variables
are limited to 1GB - 1 byte.
You can use a primitive data type keyword in the following ABL syntax:
- DEFINE PARAMETER statement
- DEFINE PROPERTY statement
- DEFINE VARIABLE statement
- Fields of a table from an OpenEdge RDBMS using Progress Developer Studio for OpenEdge or the Data Dictionary.
- Fields of a temp-table using the DEFINE TEMP-TABLE statement or methods of a Temp-table object handle.
- FUNCTION statement (return type)
- METHOD statement (return type)
- Parameter definition syntax (for a user-defined method or function)
ABL object types (see Table 2) are complex types that function according to object-oriented principles. They can be broadly divided into two categories:
- Classes — ABL supports a set of built-in object
types and also allows you to create your own user-defined object types using using the
CLASS statement and
the INTERFACE statement.
All ABL class types ultimately inherit from the ABL root class, Progress.Lang.Object class. Each object type
encapsulates a set of data and behavioral elements (members). ABL class members can
include implementations for data members, properties, and methods.
You can create instances of classes (class-based objects) at run time using the NEW function (classes), and you can reference each instance and its PUBLIC members using an object reference, which is an ABL data element defined to reference a specific object type. You can define an object reference for any kind of ABL data element that you can define as an ABL primitive type (except a database table field). However, you can define a field of a temp-table as an object reference to the ABL root class (Progress.Lang.Object class).
-
Enumerations — An enumeration (also known as enumerated types or
enums) is made up of a list of strongly typed, named constants called members. The value
of a variable defined as an enumeration is restricted to the list of members defined for
that enumeration. As with classes, ABL supports a set of built-in enumeration types and
also allows you to create your own user-defined enumerations using the ENUM statement. You can define two types of enum. In the
standard version, a variable defined as an enum can only be assigned one value at a time.
You can also define a flag enum. Any variable defined as a flag enum can be assigned any
combination of the enum's members at one time.
Enumerations are implicitly class based, but unlike classes defined using the CLASS statement, enumeration types cannot be explicitly instantiated using the NEW function. To assign a value to a variable, property, or parameter defined as an enumeration type, you use Type-name syntax to access the members defined in the enumeration type. See the Enumeration member access reference entry for more information.
For information on the built-in ABL class, interface, and enumeration types, see the Class, Interface, and Enumeration Reference.
.NET primitive types (see Table 4) include data types that are built-into .NET languages. (Note that each
language uses its own nomenclature. Compare, for example, the C# int versus the Visual Basic
Integer.) .NET also aliases (maps) a standard set of object
types (.NET mapped object types, for example, System.Int32) to these primitive types. All .NET languages can
reference each such type as either the primitive type that the particular language supports
or as the alias for the corresponding .NET object type that every language supports.
ABL also references both the .NET primitive types and their corresponding mapped object types by mapping each ABL primitive type to a given set of .NET primitive and object type mappings (see Table 4). Thus, ABL documentation refers collectively to both the .NET primitive types and the corresponding .NET mapped object types as .NET mapped data types.
.NET object types (see Table 2) include all
class types (and their derivatives) that derive from the .NET root class, System.Object, and .NET interface types, which can inherit from
other .NET interface types, but otherwise function for a class much like an ABL interface
type. You can reference a .NET object type like an ABL object type, by using an ABL object
reference defined as that object type, or by referencing members of a .NET class. .NET
object types also consist of two basic kinds of types:
-
Value types — Objects that .NET creates, passes, and assigns by
value. Value type objects all inherit from the .NET class,
System.ValueType. In ABL, when you access a value type from .NET, you access a new copy of the object that is separate from the one that is maintained by .NET. If you then change object data in ABL, these changes do not appear in any copy of the object maintained by .NET. In addition, the ABL object reference to the ABL copy of the object has no affect on the .NET garbage collection of any .NET copy of the object. - Reference types — Objects that .NET creates, passes, and assigns by reference. In ABL, when you access a .NET reference type, you access the same copy of the object that is maintained by .NET. If you then change object data in ABL, these changes also appear in .NET, because .NET references the same object. In addition, the ABL object reference is counted as a reference to the object for .NET garbage collection.
ABL also provides limited support for .NET abstract classes and .NET generic types. A .NET abstract class is similar to an ABL abstract class. A generic type has a type definition that can function as one of several different types, depending on type parameters used to complete the effective type name. .NET generic types are briefly described further in this entry. For more information on the basic kinds of .NET object types that ABL supports, see the notes section of this reference entry. OpenEdge also provides a set of built-in .NET class and interface types to support access to .NET object types. For information on the built-in .NET class and interface types, see the Class, Interface, and Enumeration Reference.
Within certain restrictions, an ABL class can inherit from a .NET class and implement .NET interfaces, similar to inheriting from an ABL class or implementing an ABL interface, respectively. When an ABL class inherits from a .NET class, any of its methods that override methods in the .NET class hierarchy can be called polymorphically on the .NET super class from both ABL and .NET. As a result, any ABL class that inherits from a .NET class becomes an ABL-derived .NET class. In fact, when an ABL-derived .NET class is instantiated in an ABL session, an instance with a corresponding .NET class type is also instantiated in the .NET context with reference to any ABL-overridden methods.
Similarly, when an ABL class implements a .NET interface, all of the ABL-implemented properties and methods can be called polymorphically on the interface type from both ABL and .NET. As a result, any ABL class that implements a .NET interface becomes an ABL-extended .NET class. In fact, when an ABL-extended .NET class that implements .NET interfaces is instantiated in an ABL session, an instance with a corresponding .NET class type is also instantiated in the .NET context with reference to the ABL-implemented properties and methods that might be accessed from .NET on each of the implemented interface types.
ABL handle-based objects (see Table 2) include a set of complex, weakly-typed objects, some of which ABL provides as built-in system objects (such as, the SESSION system handle), and others that ABL supports as a pre-defined set of objects that you can create as needed (such as, a FRAME widget or a record buffer). Handle-based objects exist independently and have no inheritance hierarchy like class-based objects. However, like class-based objects, handle-based objects have members consisting of a set of attributes (data) and methods (behavior). Depending on the object, you can create a handle-based object as a compile-time (static) object using an appropriate DEFINE statement or as a run-time (dynamic) object using an appropriate CREATE statement or other executable statement. You can reference system objects using the built-in system handle (keyword) pre-defined for them. You can reference static handle-based objects by name, using appropriate syntax for each type, and you can reference dynamic or static handle-based objects using a common primitive data element known as a handle, which you define as the HANDLE data type. Because of the weak typing of these objects, you can reference all static and dynamic handle-based objects that you define or create using the same handle. ABL also provides some system handles that provide access to particular types of pre-defined handle-based objects that are in a given state (such as, the CURRENT-WINDOW system handle for accessing a particular WINDOW widget).
ABL arrays (see Table 2) are limited to one dimension and can include elements of any primitive or object type that you can define for a variable (see the DEFINE VARIABLE statement reference entry). ABL also provides support for mapping ABL arrays to one-dimensional .NET array objects of the same element type. This means that while you can access all .NET arrays as class instances, you can also make direct array assignments and pass routine parameters between ABL arrays and equivalent one-dimensional .NET array objects. This also includes .NET arrays whose elements are .NET mapped data types (referred to as .NET arrays of mapped types), where assignments to or from ABL arrays work using the rules of implicit data type mapping. The element types supported for a .NET array of mapped types are identical to the .NET data types that ABL implicitly maps to ABL primitive types (see Table 4). For more information on support for both ABL arrays and .NET arrays, see the notes section of this reference entry.
The following table describes the primitive types supported in ABL.
| Primitive type | Description |
|---|---|
| BLOB | BLOB (Binary Large OBject) specifies a database table or temp-table field that contains a BLOB locator, which points to the associated BLOB data stored in the database. You must use a MEMPTR to manipulate the binary contents of a BLOB field in ABL. |
| CHAR | CHAR data consists of numbers, letters, and special characters. CHAR is a synonym for the CHARACTER data type. |
| CHARACTER | CHARACTER data consists of numbers, letters, and special characters. |
| CLOB | CLOB (Character Large OBject) specifies a database table or temp-table field that contains a CLOB locator, which points to the associated CLOB data stored in the database. You must use a LONGCHAR to manipulate the character contents of a CLOB field in ABL. |
| COM-HANDLE | A COM-HANDLE is a handle to a COM object (ActiveX Automation object or ActiveX Control). |
| DATE | DATE fields contain dates. |
| DATETIME | DATETIME data has two parts: an ABL date and an ABL time. The unit of time is milliseconds from midnight. |
| DATETIME-TZ | DATETIME-TZ data has three parts: an ABL date, an ABL time, and an integer representing the time zone offset from Coordinated Universal Time (UTC). The unit of time is milliseconds from midnight. The unit of time zone offset is minutes. |
| DECIMAL | DECIMAL data consists of decimal numbers up to 50 digits in length including up to 10 digits to the right of the decimal point. |
| HANDLE | A HANDLE is a pointer to an ABL handle-based
object. This can be a compile-time defined static object or a run-time defined
dynamic object. Note: HANDLE and WIDGET-HANDLE can be assigned to each
other and used interchangeably. WIDGET-HANDLE is supported only for backward
compatibility. |
| INT | An INT consists of 32-bit data (whole numbers). INT is a synonym for the INTEGER data type. |
| INT64 | An INT64 consists of 64-bit data (whole numbers). |
| INTEGER | An INTEGER consists of 32-bit data (whole numbers). |
| LOGICAL | LOGICAL data evaluates to TRUE or FALSE (or YES or NO). |
| LONGCHAR | A LONGCHAR consists of CHARACTER data that can be up to 2GB - 1 byte in size. You can use a LONGCHAR to manipulate the character contents of a CLOB database or temp-table field in ABL. |
| MEMPTR | A MEMPTR contains a sequence of bytes in memory. You can use a MEMPTR to manipulate the contents of a BLOB database or temp-table field in ABL. |
| RAW | RAW data can be any kind of data, even data from non-OpenEdge databases. It is not converted in any way. |
| RECID | A RECID is a unique internal identifier for a
record within a single database storage area. Note: RECID is supported mainly for backward compatibility.
For most applications, use ROWID instead. |
| ROWID | A ROWID is a unique internal identifier for a record within a single database storage area. For partitioned tables, it is also unique across all partitions of a given table. |
| WIDGET-HANDLE | A WIDGET-HANDLE is a pointer to an ABL
handle-based object. Note: HANDLE and WIDGET-HANDLE can be assigned to each
other and used interchangeably. WIDGET-HANDLE is supported only for backward
compatibility. |
The following table describes the non-primitive (complex) types supported in ABL.
| Complex type | Description |
|---|---|
| Array | An ABL array type is complex type that specifies a
one-dimensional array of elements of the same scalar data type with a 1-based index,
where a scalar data type is any data type that is
not, itself, an array. The elements of an ABL array can contain scalars of any
supported ABL primitive type, any ABL object type, or any .NET object type (other
than a .NET mapped object type). The type definition for an ABL array is specified
by the type definition for the array element with the addition of the EXTENT option
(or with the Extent option selected in OpenEdge database tools). Thus, you can
define an ABL array data element using similar features and syntax used to define
primitive and object-type data elements. However, you cannot define ABL array types
for the BLOB or CLOB primitive type. Also, note that unlike ABL
arrays, .NET arrays are objects with an object type, just like any other .NET
type. However, all .NET array object types derive from the In ABL, you must also enclose any .NET array
object type name in double-quotes in order to handle the square brackets and any
commas, which are special characters in ABL names, for example, For more information on specifying array types, see the Type-name syntax reference entry. |
| Class | Classes encapsulate a set of data and behavioral
elements (members), including implementations for data members, properties, and
methods. Class definitions can also implement interfaces, which define a common set
of prototypes for methods, properties, and events that classes can implement. For more information on classes and interfaces, see Develop Object-oriented ABL Applications. |
| Enumeration | An enumeration is made up of a list of strongly typed, named constants called members. The value of a variable defined as an enumeration type is restricted to the list of members defined for that enumeration type. |
| Handle-based object | A handle-based object has a built-in and inherent
ABL type that provides data and behavior of varying complexity depending on the
purpose of the object. A few examples include:
Note: While each type of handle-based object is unique,
because of their weak typing, you can reference all such objects using the same
primitive type, HANDLE (see Table 1).
For more information on each type of handle-based object, see the reference entry for its type in the Widget Reference or the Handle Reference, and see the reference entry for its respective DEFINE, CREATE, or other instantiating executable statement. |
The following table lists the default data formats and initial values for ABL primitive and object types.
| Data type | Default initial value | Default display format |
|---|---|---|
| BLOB1, 3 | Unknown value (?) |
See footnote 1. |
| CHARACTER | "" (an empty string) | X(8) |
| CLASS2, 3, | Unknown value (?) |
See footnote 1 . |
| CLOB1, 3 | Unknown value (?) |
See footnote 1. |
| COM-HANDLE3 | Unknown value (?) |
>>>>>>9 |
| DATE | Unknown value (?) (displays as blanks) |
99/99/99 |
| DATETIME | Unknown value (?) |
99/99/9999 HH:MM:SS.SSS |
| DATETIME-TZ | Unknown value (?) |
99/99/9999 HH:MM:SS.SSS+HH:MM |
| DECIMAL | 0 | ->>,>>9.99 |
| HANDLE3 | Unknown value (?) |
>>>>>>9 |
| INT64 | 0 | ->,>>>,>>9 |
| INTEGER | 0 | ->,>>>,>>9 |
| LOGICAL | no | yes/no |
| LONGCHAR1 | "" (an empty string) | See footnote 1. |
| MEMPTR1, 3 | A zero-length sequence of bytes | See footnote 1. |
| RAW1, 3 | A zero-length sequence of bytes | See footnote 1. |
| RECID | Unknown value (?) |
>>>>>>9 |
| ROWID1, 3 | Unknown value (?) |
See footnote 1. |
For more information on using the built-in ABL primitive types, see Develop ABL Applications and the Web paper, ABL Data Types.
As noted previously in this entry, ABL supports references to .NET types in two basic ways:
- You can make direct and explicit reference to .NET object types using
similar syntax that is supported for referencing ABL user-defined class and interface
types. For supported .NET object types, this includes both the instantiation of a .NET
class in ABL and the derivation of the .NET class by an ABL user-defined class
(ABL-derived .NET class), and it includes the implementation of supported .NET interfaces
by an ABL user-defined class (ABL-extended .NET class). To integrate the .NET class
hierarchy with the ABL class hierarchy, ABL views System.Object as an immediate subclass
of the ABL root class (Progress.Lang.Object class). In this way, you can manage .NET object types in ABL using many of
the same mechanisms that you use for managing ABL class and interface types. However, you
must observe the following limitations:
- You cannot directly reference any .NET object type that is supported as a .NET mapped data type, except to define a .NET array of such types. You can only reference .NET mapped data types as their equivalent ABL built-in primitive types. For more information on .NET mapped data types, see the immediately following Step 2.
- You cannot use
System.Threading.Thread, or any derived class—ABL is single-threaded. - You cannot use
System.MulticastDelegate, or any derived class (otherwise referred to as delegates) to provide handlers for .NET events. ABL provides its own event handling model for .NET events. For more information, see the Class Events Reference. - You cannot define an ABL interface that inherits from a .NET interface.
- ABL imposes additional requirements on the .NET classes you can extend and the .NET interfaces you can implement in an ABL user-defined class. For more information, see the CLASS statement and INTERFACE statement reference entries.
For more information on the requirements for accessing .NET object types, see the notes section of this reference entry and Use .NET Classes in ABL Applications.
- You can make implicit access to all .NET primitive types and their associated mapped object types by using the ABL built-in primitive types that are mapped to them. Because .NET mapped object types and .NET primitive types, together, represent the complete set of .NET mapped data types, the implicit mapping between .NET mapped data types and ABL primitive types allows you to access .NET method parameters, fields (data members), and properties using the corresponding ABL primitive types without direct reference to their .NET data type equivalents. In fact, ABL does not allow you to directly reference either the .NET primitive types or the .NET mapped object types as scalars without raising a compile-time error. (The exception is when defining a .NET array of mapped types. For more information, see the notes section of this reference entry.)
The following table shows the implicit mappings supported between .NET mapped data types and ABL built-in primitive types, showing the corresponding primitive types from C#.
| Implicit .NET object type | Implicit C# primitive type | ABL primitive type |
|---|---|---|
System.Boolean |
bool |
LOGICAL |
System.Byte |
byte
|
INTEGER4, 5 |
System.SByte
|
sbyte
|
INTEGER4 |
System.DateTime
|
N/A | DATETIME |
System.Decimal
|
decimal
|
DECIMAL6, 7, |
System.Int16
|
short
|
INTEGER4 |
System.UInt16
|
ushort
|
INTEGER4, 5 |
System.Int32
|
int
|
INTEGER7 |
System.UInt32
|
uint
|
INT648, 5 |
System.Int64
|
long
|
INT647 |
System.UInt64
|
ulong
|
DECIMAL5, 9 |
System.Double
|
double
|
DECIMAL10 |
System.Single
|
float
|
DECIMAL10 |
System.Char
|
char
|
CHARACTER11 |
System.String
|
string
|
CHARACTER7 or LONGCHAR7, 12 |
Thus, instead of using an object reference to the corresponding .NET mapped object type, you must provide or access all .NET primitive (or mapped object type) values for .NET methods, data members, and properties as ABL primitive types. Similarly, when you reference any data element or value defined as a .NET mapped data type, ABL evaluates the .NET value to its corresponding ABL primitive value. ABL checks for .NET/ABL type compatibility at compile time, except in rare cases where data type narrowing is allowed, in which case the AVM checks for data overflow or underflow at run time.
System.Drawing.Size), you can and must use object
references to the value type objects..NET supports a concept
known as boxing. Boxing is the process of converting a value
type (such as a C# int or .NET System.Int32) to a reference type object. Boxing a value type
wraps its value inside a System.Object. Unboxing extracts the value from the System.Object as the original value type. In .NET, boxing and unboxing between a
value type and a System.Object occurs during assignment or
parameter passing.
So, in addition
to implicitly mapping its native primitive types to their corresponding .NET mapped data
types, ABL also supports boxing between its primitive or array types and a .NET System.Object or array object. ABL performs boxing operations
automatically in two cases:
- When you assign values between a .NET
System.Objector one-dimensional array object and a compatible ABL primitive or array type - When you pass parameter values for .NET methods and constructors
between a .NET
System.Objector one-dimensional array object and a compatible ABL primitive or array type
However, as described further in this entry, ABL does not support automatic boxing operations when passing parameters to ABL routines.
When ABL does automatic boxing that involves ABL
primitive types or arrays of elements containing primitive types, it also does implicit
conversion between these types and the corresponding .NET mapped types (see Table 4). For example, if you assign an ABL INTEGER to a System.Object, ABL
converts the ABL INTEGER to a System.Int32, which the
System.Object accepts as a subclass value. Similarly, if
you assign an ABL INTEGER array to a System.Object, ABL
converts the ABL INTEGER array to a "System.Int32[]",
which the System.Object accepts as a subclass value. The
same occurs when you pass an ABL INTEGER or INTEGER array to a System.Object INPUT parameter of a .NET method.
In
reverse, when you assign an appropriate System.Object to
an ABL INTEGER or INTEGER array, ABL unboxes the System.Object by determining the .NET mapped type that the System.Object represents, converts that value to its equivalent
ABL primitive or primitive array value, and attempts to assign the result to the ABL INTEGER
or INTEGER array (which is validated at run time). For example, if the System.Object represents the System.Decimal subclass and you are assigning it to an ABL INTEGER, ABL converts
the System.Decimal value to an ABL DECIMAL and attempts to
assign it to the ABL INTEGER.
In a similar manner, ABL also does
automatic boxing directly between compatible ABL arrays and one-dimensional .NET array
objects. For example, if you assign or pass .NET method parameters between a "System.Windows.Forms.Button[]" array object and an ABL array of
System.Windows.Forms.Button elements, ABL automatically
does the required boxing and unboxing to convert between the different array types. A
similar boxing and unboxing operation occurs between an ABL primitive array and a compatible
.NET array of mapped types, for example, between an ABL array of INTEGER and a .NET "System.Int16[]" array object. For more information on boxing
and unboxing between ABL and .NET arrays, see the notes section in this reference entry on
working with .NET arrays.
However in the following four ABL contexts, automatic ABL boxing or unboxing is either not supported or might not be supported as you require:
- When you use a
System.Objectdirectly in an expression, ABL does not unbox theSystem.Objectinto a compatible ABL primitive type. - When you assign an ABL primitive value (or primitive array) to a
System.Objectand the ABL primitive type maps to multiple .NET data types, it automatically boxes the ABL primitive value (or primitive array elements) as the default matching .NET mapped object type, which might not be the .NET data type mapping that you require. - ABL does no automatic boxing or unboxing when you pass an ABL primitive or array type to a compatible .NET object parameter of an ABL routine (ABL method, procedure, or user-defined function). Similarly, ABL also does no automatic boxing or unboxing when you pass a compatible .NET object argument to the ABL primitive or array parameter of an ABL routine.
- ABL does no boxing or unboxing of array elements when you box or unbox
an ABL array. For example, ABL cannot assign between a
"System.Object[]"and an ABL INTEGER array, because it does not handle boxing and unboxing between the correspondingSystem.Objectand the INTEGER array elements. For more information on ABL support for array assignments, see the notes section of this reference entry.
When you use a System.Object
directly in an expression, ABL raises a compile-time error because ABL does not support
automatic unboxing of a System.Object in an expression.
Instead, you can use the ABL built-in UNBOX function in the expression to
explicitly unbox the value. This function accepts the System.Object as input and returns an ABL primitive value that is equivalent to
the .NET mapped object type value (subclass) represented by the specified .NET System.Object instance.
When you assign
an ABL primitive value or primitive array to a System.Object, ABL always boxes the value or array into a particular .NET mapped
type or array of mapped types, which might not be the .NET type you want. In Table 4, several ABL primitive types implicitly map to more than one .NET
mapped data type. For each ABL primitive type that maps to multiple .NET data types, ABL
uses one of these mappings as the .NETdefault match for the ABL primitive type (indicated by a footnote 7 in Table 4). Thus, when you assign an ABL primitive value or primitive array to a
System.Object, ABL automatically boxes the value or
array using the .NET default match for the specified ABL data type. For example, by default
an INTEGER automatically boxes as a System.Int32, and an
INTEGER array automatically boxes as a "System.Int32[]".
However, you can explicitly box the ABL
value or array using a .NET mapped type other than the default match with the ABL built-in
BOX function. This
function accepts an ABL primitive value or array as input and, by default, returns a boxed
.NET type according to the .NET default match for the ABL data type of the input value or
array. In order to box the value using a mapped type other than the .NET default match, you
can pass an ABL keyword as a string to the function that indicates the explicit .NET type
you want to use. For example, an ABL DECIMAL value can represent both a .NET System.Decimal (the default match) and a System.Double (among other possible types). If you need to box
the ABL DECIMAL as a .NET System.Double, you can
explicitly indicate this to the BOX function. Similarly, if you need to box an ABL DECIMAL
array as a "System.Double[]", you can use the same
indication.
System.Object using the UNBOX function, you cannot similarly
specify a particular ABL primitive or primitive array type as the result. ABL always unboxes
any System.Object using the default matching ABL
type.Another case for which you must use the BOX function or the UNBOX function is when you pass parameters between compatible ABL primitive or array types and .NET object types in the parameters of ABL methods, procedures, and user-defined functions. ABL raises a compiler error if you try to pass these types to each other directly in ABL routine parameters. Appropriate use of the BOX function or UNBOX function allows this type of parameter passing to occur without a compile-time error. Note, again, that ABL does support the automatic boxing and unboxing of .NET objects in parameter passing for .NET method calls.
Similarly, three additional cases exist (other than the need for explicit boxing) where you must specify the .NET data type mapping you want for a given ABL primitive type:
- When a method parameter is overloaded by multiple implicit .NET data type mappings for a passed ABL primitive type. You must specify the exact .NET data type when you pass the parameter to the method.
- When you override a method inherited from a .NET class, or when you implement (or override) a .NET interface (or abstract) method, property, or event, and the types of any associated parameters, properties, or return values are .NET mapped data types. You must specify the exact .NET data type in the definition of each .NET mapped parameter, property, and return value.
- When you pass an ABL primitive value (not an
array) to a .NET method or constructor parameter that is a
System.Object, and you want the result to be a .NET mapped type other than the default match. You must indicate the explicit mapped type on the passed ABL primitive argument. A common use case is theSetValue( )method of theSystem.Arrayclass, which sets the value of a .NET array element. If the .NET type of the array element is other than the default match, you must indicate the .NET mapped type for the value parameter to match the array definition. - When you reference a constructed .NET generic type (described further in this entry) using type parameters that include a .NET mapped type.
To indicate a non-default .NET mapped type in the previous cases where you want an explicit mapped type to be used, the syntax for the following ABL elements allows you to specify an appropriate ABL keyword:
- BOX function
- DEFINE PROPERTY statement
- METHOD statement
- Parameter definition syntax
- Parameter passing syntax
This keyword is referred to as an AS data type, because you specify it for a passed parameter using the AS option. So, for example, when you override a .NET method, you must explicitly specify .NET data type for each .NET mapped parameter, property, or return type. If the .NET data type is a default match, you must simply use the matching ABL data type. Otherwise, you must indicate the appropriate AS data type keyword for the data type of the method parameter, return type, or property definition.
Table 5 lists each explicit .NET data type mapping for a given ABL primitive type. For each listed .NET data type, you indicate this explicit mapping either by using the corresponding ABL primitive type (for a default match) or by using the appropriate option to specify the AS data type that corresponds to the explicit .NET data type you want to map. For more information on specifying the AS data type option when using the BOX function or when calling overloaded .NET methods, see the reference entries for the BOX function and Parameter passing syntax in this book. For more information on specifying AS data types when overriding a .NET method, or when implementing (or overriding) a .NET interface (or abstract) method, property, or event, see the METHOD statement, the DEFINE PROPERTY statement, the DEFINE EVENT statement, or the Parameter definition syntax reference entry, as appropriate.
| Explicit .NET object type | Explicit C# primitive type | ABL primitive type | ABL AS data type |
|---|---|---|---|
System.Boolean
|
bool
|
LOGICAL13 | – |
System.Byte
|
byte
|
INTEGER | UNSIGNED-BYTE14 |
System.SByte
|
sbyte
|
INTEGER | BYTE |
System.DateTime
|
N/A | DATETIME13 | – |
System.Decimal
|
decimal
|
DECIMAL13 | – |
System.Int16
|
short
|
INTEGER | SHORT |
System.UInt16
|
ushort
|
INTEGER | UNSIGNED-SHORT |
System.Int32
|
int
|
INTEGER13 | – |
System.UInt32
|
uint
|
INT64 | UNSIGNED-INTEGER |
System.Int64
|
long
|
INT6413 | – |
System.UInt64
|
ulong
|
DECIMAL | UNSIGNED-INT64 |
System.Double
|
double
|
DECIMAL | DOUBLE |
System.Single
|
float
|
DECIMAL | FLOAT |
System.Char
|
char
|
CHARACTER | SINGLE-CHARACTER |
System.String
|
string
|
CHARACTER/ LONGCHAR13 | – |
A .NET generic type is a class or
interface defined so that it functions as one of several different types, depending on how
you reference its type name. A reference to a .NET generic type name includes one or more
type parameters, each of which specifies a data type that the generic type can use in its
implementation. When you reference the generic type name in ABL, you substitute a specific
data type for each type parameter defined for the generic type. This reference then
identifies the generic type as a constructed type. The
notation for a generic type that you see in .NET documentation or in a class browser, where
the type parameters are not resolved, is called an open type.
An open type reference contains only placeholders for the parameters in the type name, such
as <T>, which defines the single parameter for the
following generic type:
System.Collections.Generic.List<T>
|
In ABL, you can only reference a .NET generic type as a constructed type using a type name that has the following syntax:
|
The namespace is a .NET
namespace and the syntax from object-name up to and
including the right angle bracket (>) forms the
dotNET-object-name as described in the Type-name syntax reference entry. The left and
right angle brackets (<>) are the part of .NET
generic type name references that enclose the type parameters, as shown in the previous
example. Each type-parameter in the parameter list
represents a placeholder for a specific .NET data type. The number of type parameters and
the data type that you can specify for each type-parameter in a constructed type reference depends on the generic type
definition. However, you can never specify an ABL object type or an ABL-extended .NET class
type as the data type of any type-parameter; it can only
be a pure .NET type. The quotes are required in order to allow for the angle brackets and
any spaces in the type name.
The definition for each type-parameter in a .NET generic type definition can specify constraints that determine the .NET types you can substitute for a given parameter when you reference the constructed type. If these constraints on a type-parameter allow you to specify one or more .NET mapped types, you must specify an appropriate explicit mapping for each such type when you specify the type-parameter in ABL, as described in Table 5.
System.Collections.Generic.List<T> that is
constructed as a list of System.Int16, you might use one
of the following ABL statements:
|
You can also reference an array of a generic type and define generic types with a type parameter that is an array. For more information, see the information on .NET arrays in the notes of this reference entry.
You can use a .NET generic type in all the same contexts as any other .NET type except to define an ABL class that:
- Inherits from a .NET generic class
- Implements a .NET generic interface
Also, while you can cast an object reference to a .NET generic type using the CAST function, you cannot cast to a .NET generic type using the DYNAMIC-CAST function.
For more information on how to identify .NET generic types and understand the constraints on their type parameters, see the "Generic type parameters (C# Programming Guide)" in the .NET documentation. For more information on working with .NET generic types in ABL, see Use .NET Classes in ABL Applications.
Notes
- ABL
provides built-in data types, built-in class data types, and user-defined
class data types. References to built-in data types follow these rules:
- Like most other keywords, references to specific built-in data types appear in all UPPERCASE, using a font that is appropriate to the context. No uppercase reference ever includes or implies any data type other than itself.
- Wherever integer appears, this is a reference to the INTEGER or INT64 data type.
- Wherever character appears, this is a reference to the CHARACTER, LONGCHAR, or CLOB data type.
- Wherever decimal appears, this is a reference to the DECIMAL data type.
- Wherever numeric appears, this is a reference to the INTEGER, INT64, or DECIMAL data type.
References to built-in class data types appear in mixed case with initial caps, for example,
Progress.Lang.Object. References to user-defined class data types appear in mixed case, as defined for a given application example. - INT64 support applies to all of the ABL built-in methods and functions that take integer-expression parameters. That is, integer-expression parameters can be either INT64 expressions or INTEGER expressions.
- All intermediate calculations are carried out in 64-bit arithmetic. For example, 2,000,000,000 * 100 / 100 gives the correct result whether the target field is INTEGER or INT64. However, although 2,000,000,000 * 100 does not cause an overflow, you must assign the result to an INT64 field. If you assign it to an INTEGER field, the AVM generates a run-time error.
- When you copy one MEMPTR (M1) to another MEMPTR (M2), the data that M1 points to is also copied. Therefore, MEMPTR M1 points to memory location L1, and MEMPTR M2 points to memory location L2 which contains a copy of the data in L1. You must change the data in both memory locations if you want both MEMPTRs to reflect the change. To clear memory after using the MEMPTRs, you must execute SET-SIZE = 0 on both MEMPTRs to be sure that both memory locations are cleared.
- If you define a MEMPTR, you should deallocate it once it is no longer used, usually before the routine that defines it returns, otherwise you will have a memory leak in your application. Note that, if you define a MEMPTR inside a routine (other than an external .p) and you don’t deallocate it before returning to the caller, that MEMPTR will still contain the data allocated in the previous call, when that routine gets called again.
- Since RAW variables are limited in size to 32K and MEMPTR variables can be up to 2GB - 1 byte in size, if a MEMPTR with a size greater than 32K is copied to a RAW variable, the AVM generates an error.
- Both a primitive type or object type can
be defined as an array. Use the EXTENT option (for the DEFINE VARIABLE statement) or
brackets (for the VAR statement) when defining or creating a field, variable, or object to
establish an array. For example:
DEFINE VARIABLE someIntArray AS INTEGER EXTENT 4. VAR INTEGER[4] someIntArray.The variable is now defined as an array of four integers. Since the size is set to 4, this is a determinate array. You can also define an indeterminate array by omitting the constant integer value after EXTENT (in the DEFINE VARIABLE statement) or within the brackets (for the VAR statement). In this case, the number of elements in the array is undefined.
To refer to an individual element in an array, enclose the INTEGER index (subscript) using bracket syntax. This is known as a subscripted array reference. For example:
someIntArray[2] = 128.Here, 2 references the second element in the INTEGER array.
Arrays can also be manipulated as a whole for array-to-array deep copy operations and to pass or return parameters. By omitting the brackets, a reference to the field, variable, or object name is a reference to the entire array. This is called an unsubscripted array reference. For example:
someIntArray = anotherIntArray.Here, each element of the
anotherIntArraywill be copied into the corresponding element of thesomeIntArray. This is called a deep copy. Note that unsubscripted array references are not supported in expressions or comparison operations. For more information on array assignments, see the Assignment (=) statement reference entry. - ABL supports access to
the following kinds of .NET object types:
- Classes — Viewed and managed like ABL classes with support for additional features that are unique to .NET classes, such as inner classes and indexers for indexed properties. For information on accessing instances of .NET classes, see the Class-based object reference entry.
- Interfaces — Viewed and managed as ABL interfaces with support for additional features that are unique to .NET, such as inner interfaces.
- Structures — Viewed and managed similar to ABL classes.
Structure types are supported using syntax native to each .NET language, for example,
using the struct keyword in C# and C++. For information on accessing instances of .NET
structures, see the Class-based object reference
entry. The essential difference between .NET structures and most other .NET classes is
that structures inherit from
System.ValueTypeand are therefore value types. Thus, all structures are passed within .NET, and between ABL and .NET, by value. However within ABL, structure objects are passed, like all other class instances, by reference. Therefore, when you access a structure from .NET, you reference a copy of the object in ABL that is separate from the object in .NET, and when you pass an ABL reference to a structure back to .NET, .NET gets a copy of the object that is separate from the object that is referenced in ABL. Structures therefore have different object management requirements in ABL than reference type objects. For more information, see the information on ABL support for value types in Use .NET Classes in ABL Applications. - Enumerations — Like ABL enumerations, .NET enumerations
are classes that correspond to a named set of constant values with a single underlying
data type. Each of these constant values corresponds to a member of a given
enumeration class. Each .NET language allows you to define and reference enumeration
members using its own syntax. ABL also provides syntax that allows you to reference
.NET enumerations as object types. For more information, see the Enumeration member access reference entry.
Enumerations inherit from the
System.Enumstructure, which inherits fromSystem.ValueType. Thus, like structures, enumerations are value types that are passed by value between .NET and ABL. However, unlike .NET languages that can view enumerations as values, ABL views enumerations only as objects that are passed by reference, like any other class instance. Enumerations therefore have similar object management requirements to structures in ABL. For more information, see the information on ABL support for value types in Use .NET Classes in ABL Applications.
You can instantiate .NET class or structure instances using the NEW function (classes), as with any ABL class. However, you cannot create an enumeration object. ABL can only reference enumeration objects that have already been defined in .NET. For information on specific .NET object types, see the documentation provided by the vendor for that object type.
- ABL supports widening relationships between certain ABL data types. Widening allows you to pass an argument to a method parameter that has a different data type than the parameter, depending on the data flow (INPUT or OUTPUT). Thus, the target of the data flow can be a different data type if it can hold the largest value provided by the source of the data flow. When passing .NET method parameters or getting and setting .NET property values, ABL supports additional widening relationships between the ABL data type being passed and the .NET data type of the parameter. For more information, see the description of widening for .NET parameters in the Parameter passing syntax reference entry.
- A .NET array is an object that extends the
System.Arrayclass. You can access a .NET array in ABL using an object reference, like any other object. Thus, in ABL, you can access all .NET arrays, of all dimensions, whose element type is either an ABL-supported .NET object type (such asSystem.Windows.Forms.Form) or a .NET mapped data type (such asSystem.Int16or C#short). You can also create .NET arrays directly in ABL by creating instances of theSystem.Arrayclass. Note that while you cannot explicitly define a variable as aSystem.Int16, ABL does allow you to define a .NET array where the element type is aSystem.Int16(or any other mapped object type). .NET arrays have the following class hierarchy in ABL, in order of derivation from the ABL root class:Progress.Lang.ObjectSystem.ObjectSystem.Array- Any array class of a specified element type, for example,
"System.Int32[]", which specifies a one-dimensional array ofSystem.Int32elements
You can define references to .NET arrays or ABL arrays (extents) of .NET array references:
DEFINE VARIABLE buttonArray AS CLASS "System.Windows.Forms.Button[]" NO-UNDO. DEFINE VARIABLE buttonArrayExt AS CLASS "System.Windows.Forms.Button[]" EXTENT 3 NO-UNDO.You can also define references to arrays of a generic type or to generic types that have type parameters that are arrays. For example:
DEFINE VARIABLE shortListArrayObj AS CLASS "System.Collections.Generic.List<SHORT>[]" NO-UNDO. DEFINE VARIABLE shortListArrayExt AS CLASS "System.Collections.Generic.List<SHORT>" EXTENT 3 NO-UNDO. DEFINE VARIABLE buttonArrayList AS CLASS "System.Collections.Generic.List<System.Windows.Forms.Button[]>" NO-UNDO. DEFINE VARIABLE buttonArrayListExt AS CLASS "System.Collections.Generic.List<System.Windows.Forms.Button[]>" EXTENT 3 NO-UNDO.Because all .NET array objects inherit from
System.Array, you can access any .NET array object using the members of theSystem.Arrayclass. To help create .NET array objects, OpenEdge provides a Progress.Util.TypeHelper class to specify theSystem.Typeobject needed for creating .NET array objects. For more information on working with .NET arrays, see the sections on accessing .NET arrays in Use .NET Classes in ABL Applications. - If a .NET array is a multi-dimensional array, you can
only work with it as a .NET array object, using the
System.Arrayaccess mechanisms. However, if a .NET array is a one-dimensional array, you can work with it in two different ways:- Directly as a .NET array object
- As an ABL array by directly assigning the .NET array to an equivalent ABL array, working with the resulting ABL array using ABL mechanisms, and directly assigning the reworked ABL array back to a .NET array
- An array assignment can occur between .NET and ABL arrays
of compatible element types, either by direct assignment of one
array to another using the Assignment (=) statement or the ASSIGN statement,
or by passing array parameters to .NET methods using Parameter passing syntax. In these specific cases, ABL performs automatic boxing and
unboxing between the compatible ABL and .NET array types.
In general, you can assign an ABL or .NET array of .NET value
types (such as
System.Drawing.Size) only to another ABL or .NET array of identical value type elements. ABL makes an exception if the ABL array in the assignment is an array of primitive type elements (such as INTEGER), in which case ABL allows assignment to or from a compatible .NET array of mapped types (such as"System.Int32[]"or"System.Byte[]"). Otherwise (for reference types), elements of the target array must be identical to or higher in the class hierarchy than the elements of the source array. For example, you can assign an array ofSystem.Windows.Forms.Formelements to an array ofSystem.Objectelements. - While ABL does automatically box and unbox entire arrays for supported
array assignments, ABL does not automatically box and unbox
the elements of the source and target arrays. So, for example,
you cannot assign an ABL array of INTEGER elements to a .NET array
of
System.Objectelements ("System.Object[]"). - Given that array element types are compatible, how an array
assignment works, depends on the system context (ABL or .NET)
of the arrays involved. In general, if either or both the target
or source of the assignment is an ABL array, this results in a deep
copy of all the array elements from the source array to the target.
If both the target and source of the assignment is a .NET array,
as with any object assignment, this results in an object reference
copy, where the target references the same array as the source.
Other array interactions depend on the array type of the target.CAUTION: Because assignment between a .NET array and an ABL array requires a deep copy, note the performance impact it might have on your application before coding this operation. For a .NET array, you might prefer to work directly with the object reference, and access individual elements using
System.Arraymechanisms. - For a .NET and ABL array assignment where the target is
an ABL array:
- The source array must be another ABL array,
a one-dimensional .NET array, or a
System.Objectwhose type at run time is a one-dimensional .NET array, and the element data type of the target array must be compatible with the element data type of the source, or the assignment raises an error. In general, the compatibility between source and target element types follows standard ABL rules for data type assignments, both within ABL and between ABL and .NET. For more information on data type compatibility for assignment and parameter passing, see the Assignment (=) statement and the Parameter passing syntax reference entry. - If the target is an ABL array of
System.Objectelements and the source array is an array of .NET mapped types (such asSystem.Int32) or ABL primitive types, ABL raises a compile-time error, because (as noted previously) ABL does not automatically do the unboxing and boxing operations that are required on the elements of each array. - If the target has an indeterminate EXTENT, the elements from the source array are copied to the target, setting its EXTENT to the number of elements in the source array.
- If the target has a set EXTENT and the number of elements in the source array match the target EXTENT, the elements from the source array are copied to the target; otherwise, the assignment raises an error. This is true for both ABL and .NET source arrays.
- The source array must be another ABL array,
a one-dimensional .NET array, or a
- For a .NET and ABL array assignment where the target is a .NET array object or other
object reference:
- The source array can be any ABL array or .NET array with an element data type that
is compatible with the target. If the element data type of the target array is a .NET
value type, a .NET source array must be defined with elements of an identical value
type. If the target array is a .NET array of mapped types, an ABL source array must be
defined with elements of an ABL primitive type that implicitly maps to the target
element type (see Table 4. If the target element type is not mapped (such as
System.Drawing.Size), an ABL source array (like a .NET array) must be defined with elements of an identical value type. Otherwise, assignment to arrays of .NET reference type elements follow standard rules for assigning object references of related class and interface types. - If the target is a
System.Arrayor aSystem.Object, you can assign to it any .NET source array object or any ABL source array that is defined as a supported .NET object type or as an ABL primitive type that maps implicitly to a .NET mapped data type (see Table 4). If the source is a .NET array object, ABL simply assigns it to theSystem.ArrayorSystem.Objectreference. If the source is an ABL array, ABL creates a new .NET array object to hold the ABL array elements and assigns it to theSystem.ArrayorSystem.Objectreference. In addition, if the ABL array elements have a primitive type, ABL automatically maps the ABL array elements into the default matching .NET object type before storing them in the specifiedSystem.ArrayorSystem.Object. - If the target is a .NET array of
System.Objectelements ("System.Object[]"), you can also assign any compatible ABL or .NET array to it. Note (as previously described) that a source array with .NET value type elements (such asSystem.Drawing.Sizeor ABL INTEGER, which resolves to the value-type,System.Int32) is not compatible. .NET requires that the element types must be identical in array assignments involving value type elements. - If the target is a
Progress.Lang.Object, you can assign to it any .NET source array object, but not a native ABL array (which is not an object). - The EXTENT of any ABL source array and the dimensions and size of a .NET source
array do not matter. As noted previously, ABL copies the elements of an ABL source
array to a newly created .NET array object and stores the object into the target
object reference.CAUTION: The index for ABL arrays is 1-based, while the index for .NET arrays is generally 0-based.
- The source array can be any ABL array or .NET array with an element data type that
is compatible with the target. If the element data type of the target array is a .NET
value type, a .NET source array must be defined with elements of an identical value
type. If the target array is a .NET array of mapped types, an ABL source array must be
defined with elements of an ABL primitive type that implicitly maps to the target
element type (see Table 4. If the target element type is not mapped (such as
- You can pass the Unknown value (
?) as a parameter to a .NET method or assign the Unknown value (?) to a .NET property or data member. ABL translates the Unknown value (?) in these cases to the .NET null value. For the numeric and logical .NET primitive types listed in Table 4, when they are set to null, .NET sets a different default value—0, 0.0, or no—depending on the data type. In these cases, ABL also returns the .NET null value as the ABL Unknown value (?). - ABL does not do any mapping between
System.Data.DataSetorSystem.Data.DataTablemethod parameters, properties, or data members on one hand and ABL ProDataSets and temp-tables on the other. ABL supports data binding between ProDataSets or temp-tables (among other data sources) and .NET form controls using theProgress.Data.BindingSourceclass (the ProBindingSource). For more information, see the Progress.Data.BindingSource class reference entry. However, you can always directly access .NETDataSetandDataTableobjects as any other .NET object, using their class members.
See also
Assignment (=) statement, ASSIGN statement, BOX function, CLASS statement, INTERFACE statement, NEW function (classes), Progress.Data.BindingSource class, Type-name syntax, Progress.Util.TypeHelper class, UNBOX function