X++ Coding Standards
X++ Coding Standards
Have only one successful return point in the code (typically, the last statement), with
the exception of switch cases, or when checking for start conditions.
Keep the building blocks (methods) small and clear. A method should do a single,
well-defined job. It should therefore be easy to name a method.
Put braces around every block of statements, even if there is only one statement in
the block.
Put comments in your code, telling others what the code is supposed to do, and what
the parameters are used for.
Do not assign values to, or manipulate, actual parameters that are "supplied" by
value. You should always be able to trust that the value of such a parameter is the
one initially supplied. Treat such parameters as constants.
Never let the user experience a runtime error. Take appropriate actions to either
manage the situation programmatically or throw an error informing the user in the
Infolog about the problem and what actions can be taken to fix the problem.
Reuse code. Avoid using the same lines of code in numerous places. Consider moving
them to a method instead.
Never use infolog.add directly. Use the indirection methods: error, warning, info and
checkFailed.
X++ layout
Comments
Semicolons
Constants
Arrays
Dates
try/catch statements
throw statements
select Statements
X++ Layout
General Guidelines
Break up complex expressions that are more than one line - make it visually clear.
Add one space between if, switch, for, while and the expressions starting
parentheses. For example:
if (creditNote)
Use braces around all code blocks, except for around case clauses in a switch
statement. Use braces even if there is only one statement in the block.
Indentation
An indentation is equivalent to four (4) characters, which corresponds to one tab in the X++
editor. You must not start a new line in columns 2, 3 or 4.
Put opening and closing braces, { and }, on the same level, on separate lines, and
aligned with the command creating the block. They must be at the start of a line, and
in a tab column position (1, 5, 9 etc.). Braces must be on a dedicated line unless
a opening brace is followed by a semicolon ( {; ) or a closing brace is followed by a
while ( }while ).
The following reserved words should be placed at the beginning of a line: case, catch,
changeCompany, continue, default, else, for, if, retry, return, switch, try, ttsAbort,
ttsBegin, ttsCommit, while.
The exceptions to this rule are:
case: (reserved words in a case statement)
default: (reserved words in a default statement)
else if
}while
If a line of code starts with any other reserved word, or with an alphabetical
character, the line should start in a tab column position (1, 5, 9 etc). The following
reserved words must be placed in a tab column position: case, catch,
changeCompany, continue, default, else, for, if, retry, return, switch, try, ttsAbort,
ttsBegin, ttsCommit, while.
The exceptions to these rules are:
case: (reserved words in a case statement)
default: (reserved words in a default statement)
else if
}while
The reserved word else must have a dedicated line unless you write else if
switch-case statements: indent case and default statements by 1 level (with any
code within these indented a further level) and indent break statements by two
levels.
Indent where and other qualifiers to the select statement by one level.
If Booleans are used in a long expression, put them at the start of an indented new
line.
switch (myEnum)
{
case ABC::A:
...
break;
case ABC::B
...
break;
default:
...
break;
}
Column Layout
Column layout should be used where more than one line has the same structure; for
example, in declarations or assignments.
Do not use column layout when there is only one row, or if consecutive rows do not have the
same structure.
Column format is defined using extra blanks.
The starting parenthesis on method declarations and calls should be the character just after
the method name (no space).
If there are one or two parameters, the parameters can be listed on the same line. If there
are more than two parameters, move each parameter onto a new line, and indent by 4
spaces.
Dates
Names
Aliases
Note
If you put a comment at the start of the method to describe its purpose and use, you can
use block comments (/* */).
Tip
To find comments in the source (both // .. and /* .. */), use the Find dialog to search for
methods containing the text (regular expression): /[/\*]
Follow the best practices rules about using constants. These are designed to make it easier
to maintain X++ code.
Constants
Rule
Error
level
Warning
None
Intrinsic Functions
Global macros
Error
level
User interface text must be in double quotes, and you must always use a label
Error
User interface labels must be complete sentences. Do not build sentences using
more than one label, or other constants or variables under program control (do not
use concatenation).
None
Example:
Description description = "@SYS12345"
Use strFmt to format user interface text.
System-oriented Text
Rule
Error
level
None
Warning
Do not use labels. You will get a warning if a label is used inside single
Warning
quotes.Example:
Copy
#define.Filename('myFile.txt')
Filename filename = #Filename;
Numeric Constants
Rule
Error
level
Always review the direct use of numeric constants, except for 0 meaning null, 1
None
Certain numeric constants are predefined, such as the number of days per week,
and the number of hours per day. For example, see the TimeConstants and
None
Tip
Use the memory option to limit the amount of RAM used to hold the data of the array
when you work with high numbered indexes (and large cells).
If you use all (or nearly all) of the entries in an array, set the memory option to a large
number, or do not set it at all.
If you only use a few of the entries in the array, set the memory option to a small number,
such as 1.
Read Performance
If you consider using the memory option on an array where you use all (or almost all) of the
entries, the look up performance should be considered.
If you traverse an array sequentially, such as with an index of 1, 2, 3, ..., n, you will probably
not experience any read performance problems. The cell data blocks will be read
sequentially from disk and they will be read to the end before the next block is read (disk
reads will be number of entries/memory option size).
If you traverse an array randomly (such as with an index of 300, 20, 5, 250, n, ..., 50) the cell
data will also be read from disk randomly, so you may experience read performance
problems (disk reads could be as high as the number of entries).
Example 1
MyTable myTable;
boolean foundRecord[,1];
;
Example 2
CustTable custTable;
CustAccount foundAccount[];
int i;
;
while select custTable
where custTable
...
{
i++;
foundAccount[i] = custTable.AccountNum;
...
}
Example 3
Name foundName[,100];
int i;
;
while select custTable
where custTable
...
{
i++;
foundName[i] = custTable.Name;
}
Use only strongly typed (date) fields, variables, and controls (do not use str or int).
Note
The date and time can be different on the client and the server.
For user interface situations, use strFmt or date2Str with -1 in all the formatting
parameters. This ensures that the date is formatted in the way that the user has
specified in Regional Settings.
When you let Regional Settings dictate the format, be aware that it can change from user
to user and might not be a suitable format for external communication.
Using str2Date indicates that dates are being used that have had a string format.
Example
Copy
try
{
this.createJournal();
this.printPosted();
}
catch (Exception::Deadlock)
{
this.removeJournalFromList();
retry;
}
Note
Do not use ttsAbort directly; use throw instead.
if...else
If you have an if...else construction, then use positive logic:
Preferred:
if (true)
{
...
}
else
{
...
}
Avoid:
if (!false)
{
...
}
else
{
...
}
It is acceptable to use negative logic if throwing an error, and in cases where the use of
positive logic would make the code difficult to understand.
There should be a space character between the if keyword and the open parenthesis.
Switch Statements
Always end a case with a break statement (or return/throw). If you intentionally want to
make use of the fall-through mechanism supported in X++, replace the missing break
statement with a comment line:
// Fall through
This comment line makes it visually clear to the reader that the fall-through mechanism is
utilized.
Use 3 levels of indentation:
switch (Expression)
{
case: Constant:
Statement;
break;
...
}
Do not put parentheses around cases.
There should not be a space between the case keyword and the colon character.
break;
}
Avoid:
if (myEnum == ABC::A)
{
...
}
else
{
if (myEnum == ABC::B)
{
...
}
else
{
if (myEnum == ABC::C)
{
...
}
else
{
...
}
}
}
Switch Statements
The switch statement is a multi-branch language construct. You can create more than two
branches by using the switch statement. This is in contrast to the if statement. You have to
nest statements to create the same effect.
The general format of a switch statement is as follows.
switch (Expression)
{
case Constant:
Statement;
break;
...
default:
Statement;
break;
}
The switch expression is evaluated and checked against each of the case compile-time
constants. If a constant matches the switch expression, the case statement is executed. If
the case also contains a break statement, the program then jumps out of the switch. If there
is no break statement, the program continues evaluating the other case statements.
If no matches are found, the default statement is executed. If there are no matches and no
default, none of the statements inside the switch are executed.
Each of the previous Statement lines can be replaced with a block of statements by
enclosing the block in {...} braces.
Syntax
Switch statement = switch( expression) { {case } [ default: statement ] }
case = case expression { , expression } : statement
Examples
switch (Debtor.AccountNo)
{
case "1000" :
do_something;
break;
case "2000" :
do_something_else;
break;
default :
default_statement;
break;
}
It is possible to make the execution drop through case branches by omitting a break
statement. For example:
switch (x)
{
case 10:
a = b;
case 11:
c = d;
break;
case 12:
e = f;
break;
}
Syntax
expression1 ? expression2 : expression3
expression1 must be a Boolean expression. If expression1 is true, the whole ternary
statement resolves to expression2; otherwise it resolves to expression3.
expression2 and expression3 must be of the same type as each other.
Example 1
This section describes a code example that returns one of two strings based on a Boolean
return value from a method call. The Boolean expression indicates whether the CustTable
table has a row with a RecId field value of 1.
result = (custTable::find("1").RecId) ? "found" : "not found";
If this Boolean expression is true (meaning RecId != 0), found is assigned to result.
Otherwise, the alternative not found is assigned to result.
Example 2
This section describes a code example that has one ternary statement nested inside another
ternary statement.
Copy
print( (custTable.AccountNum > "1000")
? ( (custTable.AccountNum < "2000")
? "In interval" : "Above 2000"
)
: "low"
);
If AccountNum is not greater than 1000, the expression is equal to the third expression and
low is printed.
If AccountNum is greater than 1000, the second expression is evaluated, and this also
contains a ternary operator. If AccountNum is greater than 1000 and less than 2000, In
interval is printed. If AccountNum is greater than 1000 and greater than or equal to 2000,
Above 2000 is printed.
Transaction Integrity
Microsoft Dynamics AX has two internal checking features to help ensure the integrity of
transactions made by X++ programmers.
If the integrity of transactions is not ensured, it may lead to data corruption, or, at best, poor
scalability with reference to concurrent users on the system.
forUpdate Checking
This check ensures that no record can be updated or deleted if the record has not first been
selected for update. A record can be selected for update, either by using the forUpdate
keyword in the select statement, or by using the selectForUpdate method on tables.
ttsLevel Checking
This check ensures that no record can be updated or deleted except from within the same
transaction scope as it was selected for update. Integrity is ensured by using the following
statements:
ttsBegin: marks the beginning of a transaction. This ensures data integrity, and
guarantees that all updates performed until the transaction ends (by ttsCommit or
ttsAbort) are consistent (all or none).
ttsCommit: marks the successful end of a transaction. This ends and commits a
transaction. MorphX guarantees that a committed transaction will be performed
according to intentions.
ttsAbort: allows you to explicitly discard all changes in the current transaction. As a
result, the database is rolled back to the initial statenothing will have been
changed. Typically, you will use this if you have detected that the user wants to break
the current job. Using ttsAbort ensures that the database is consistent.
Note
It is usually better to use exception handling instead of ttsAbort. The throw statement
automatically aborts the current transaction.
Statements between ttsBegin and ttsCommit may include one or more transaction blocks as
shown in the following example.
ttsBegin;
// Some statements.
ttsBegin;
// Statements.
ttsCommit;
ttsCommit;
In such cases, you should note that nothing is actually committed until the successful exit
from the final ttsCommit.
Examples
Example use of ttsBegin and ttsCommit
Copy
Custtable custTable;
;
ttsBegin;
select forUpdate custTable where custTable.AccountNum == '4000';
custTable.NameAlias = custTable.Name;
custTable.update();
ttsCommit;
Exception Handling
You can write your X++ code to handle errors by using the statements for generating and
handling exceptions.
For example, your method might receive an input parameter value that is invalid. Your
method can throw an exception to immediately transfer control to a catch code block that
contains logic to handle this particular error situation. You do not necessarily need to know
the location of the catch block that will receive control when the exception is thrown.
What is an Exception?
An exception is a regulated jump away from the regular sequence of program instruction
execution. The instruction at which program execution resumes is determined by try - catch
blocks and the type of exception that is thrown.
In X++, an exception is represented by a value of the enum named Exception. A frequently
thrown exception is Exception::error enumeration value. This exception is thrown in a variety
of situations. It is common practice to write diagnostic information to the Infolog before
throwing the exception, and the Global::error method is often the best way to do that.
throw
try
catch
retry
Note
There is no finally statement in X++.
Tip
In X++ code, the static methods on the Global class can be called without the Global::
prefix. For example, the Global::error method can be called simply as error("My
message.");.
Inside a catch block, you can write the throw; statement without specifying anything else in
the statement. This re-throws the same exception value that the catch block caught. You
might re-throw an exception when your method has no other safe way to continue.
Caution
You must prevent your use of retry from causing an infinite loop. The early statements in
your try block must contain an if test of a variable that eventually ends the looping.
The retry statement is used when the cause of the exception can be fixed by the code in the
catch block. The retry statement gives the code in the try block another chance to succeed.
Note
The retry statement erases messages that were written to the Infolog since program
control entered the try block.
Have a try block that contains all your statements in the outermost frame on the call
stack.
Have an unqualified catch block at the end of your outermost catch list.
Do throw the enum value that is returned from one of the following methods on the
Global class (you have the option of omitting the implicit Global:: prefix):
Global::error
Global::warning
Global::info
When you catch an exception that has not been displayed in the Infolog, call the
Global::info function to display it.
Tip
Exception::CLRError, Exception::UpdateConflictNotRecovered, and system kernel
exceptions are examples of exceptions that are not automatically displayed in the
Infolog.
Your code can obtain a reference to the System.Exception instance by calling the
CLRInterop::getLastException method.
Parameter
Description
SysInfoLogStrtxt
URLhelpUrl
"KernDoc:\\\\Functions\\substr"
This parameter value is ignored if _sysInfoAction is supplied.
SysInfoAction_sysInfoAc An instance of a class that extends the SysInfoAction class.
tion
The following list shows the method overrides we recommend for
the child class:
description
run
pack
unpack
catch statements that are outside the transaction block are the first catch statements to be
tested.
Code Samples
The next sections have the following code samples:
throw Exception::Error;
}
catch (Exception::Error)
{
info("Caught 'Exception::Error'.");
}
/********** Actual Infolog output
Message (03:43:45 pm)
In the 'try' block. (j1)
Caught 'Exception::Error'.
**********/
}
;
try
{
info("In the 'try' block. (j3)");
netString.Substring(-2); // Causes CLR Exception.
}
catch (Exception::Error)
{
info("Caught 'Exception::Error'.");
}
catch (Exception::CLRError)
{
info("Caught 'Exception::CLRError'.");
netExcepn = CLRInterop::getLastException();
info(netExcepn.ToString());
}
/********** Actual Infolog output (truncated for display)
Message (03:55:10 pm)
In the 'try' block. (j3)
Caught 'Exception::CLRError'.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an
invocation. ---> System.ArgumentOutOfRangeException: StartIndex cannot be less than
zero.
Parameter name: startIndex
at System.String.InternalSubStringWithChecks(Int32 startIndex, Int32 length, Boolean
fAlwaysCopy)
at System.String.Substring(Int32 startIndex)
.");
.");
}
ttscommit;
}
catch (Exception::Error)
{
info("Catch_2: Expected, caught in the innermost 'catch' that is outside of the
transaction block.");
}
}
catch (Exception::Error)
{
info("Catch_3: Unexpected, caught in 'catch' far outside the transaction block.");
}
info("End of job.");
/********** Actual Infolog output
Message (04:12:34 pm)
Throwing exception inside transaction.
Catch_2: Expected, caught in the innermost 'catch' that is outside of the transaction block.
End of job.
**********/
}
double-clicks the Infolog message, the SysInfoAction.run method is run. You can write code
in the run method that helps to diagnose or fix the problem that caused the exception.
The object that is passed in to the Global::error method is constructed from a class that you
write that extends SysInfoAction.
The following code sample is shown in two parts. The first part shows a job that calls the
Global::error method, and then throws the returned value. An instance of the
SysInfoAction_PrintWindow_Demo class is passed into the error method. The second part
shows the SysInfoAction_PrintWindow_Demo class.
{
str m_sGreeting; // In classDeclaration.
List of Exceptions
The exception literals shown in the following table are the values of the Exception System
Enumeration.
Exception literal
Description
Break
CLRError
CodeAccessSecurity
DDEerror
Deadlock
DuplicateKeyException
DuplicateKeyExceptionNotReco
vered
Error
Info
Internal
Numeric
Sequence
(TBD)
UpdateConflict
UpdateConflictNotRecovered
Warning
Exception Categories
Exceptions in Enterprise Portal are divided into three categories. These exception categories
are defined in the enumeration AxExceptionCategory. They are described in the following
table.
Exception
Category
Description
NonFatal
AxFatal
SystemFatal
Indicates a serious error has occurred and the request must be aborted.
Errors of this kind often cause an HTTP error code 500.
Your code directly calls methods from the Enterprise Portal framework that can throw
exceptions. For instance, the EndEdit method for a DataSetViewRow object can
encounter exceptions as a result of the edit operation. If your code called this method
directly, it must handle the exceptions.
Your code calls X++ methods through the proxy, and the X++ methods throw
exceptions. Your code must handle the exceptions.
The controls for Enterprise Portal have built-in functionality to perform standard actions such
as editing a row in a grid or saving the contents of a form. The code for this built-in
functionality properly handles exceptions. If the action started with the built-in functionality
of a control, such as a user clicking the OK button for an AxForm, you do not need to add
code to handle exceptions for that action.
if (exceptionCategory == AxExceptionCategory.NonFatal)
{
// Application code to properly respond to the exception goes here.
}
Note
The Enterprise Portal framework will automatically display the exception message in the
Infolog Web part on the page.
The code that runs in response to the exceptions should leave Enterprise Portal in a valid
state. For example, if a validation exception occurs when the user clicks the OK button to
save the values on a page, data will not be saved. The code in the exception handler should
prevent Enterprise Portal from redirecting to a different page. This allows for the user to see
the error on the page, correct it, and then perform the action again.
Exception Example
The CalculateGrade method is an X++ method that is part of the Test class added to
Microsoft Dynamics AX. The method takes a numeric grade value and returns the
corresponding letter grade. If the input value is outside the valid range (0 to 100), an
exception is thrown. The proxy was used to make this method available to User Controls.
The following example is the click method for a button added to a User Control. It calls the
CalculateGrade method to retrieve a letter grade based on a test score. The code handles
the exception that occurs if the input value is out of range. In this case, it displays text that
indicates the grade is out of range.
Copy
protected void Button1_Click(object sender, EventArgs e)
{
string letterGrade;
// Method can throw an exception, so an exception handler is used.
try
{
letterGrade = Test.CalculateGrade(this.AxSession.AxaptaAdapter,
Convert.ToInt16(TextBoxScore.Text));
TextBoxGrade.Text = letterGrade;
}
catch (System.Exception ex)
{
AxExceptionCategory exceptionCategory;
if(AxControlExceptionHandler.TryHandleException(this, ex, out exceptionCategory) ==
false)
{
// The exception was fatal, so rethrow it.
throw;
}
if(exceptionCategory == AxExceptionCategory.NonFatal)
{
if (ex.InnerException.Message.Equals("Grade is out of range"))
{
TextBoxGrade.Text = "<<Grade out of range>>";
}
}
The xRecord.insert method generates values for RecId and system fields, and then inserts
the contents of the buffer into the database.
The method operated as follows:
Only the specified columns of those rows selected by the query are inserted into the
named table.
The columns of the table being copied from and those of the table being copied to
must be type compatible.
If the columns of both tables match in type and order, the column-list may be omitted
from the insert clause.
The insert method updates one record at a time. To insert multiple records at a time, use
array inserts, insert_recordset, or RecordSortedList.insertDatabase.
To override the behavior of the insert method, use the doInsert method.
Example 1
The following code example inserts a new record into the CustTable table, with the
AccountNum set to 5000 and the Name set to MyCompany (other fields in the record will be
blank).
Copy
CustTable custTable;
;
ttsBegin;
select forUpdate custTable;
custTable.AccountNum = '5000';
custTable.Name = 'MyCompany';
custTable.insert();
ttsCommit;
Note
You cannot catch a duplicate key exception caused by a set based operation such as
insert_recordset.
This example depends on two tables TableNumberA and TableNumberB. Each has one
mandatory Integer field, named NumberAKey and NumberBKey respectively. Each of these
key fields has a unique indexed defined on it. The TableNumberA table must have at least
one record in it.
static void JobDuplicKeyException44Job(Args _args)
{
TableNumberA tabNumA; // Has one record, key = 11.
TableNumberB tabNumB;
AddressState tabAddressState;
int iCountTries = 0
,iNumberAdjust = 0
,iNewKey
,ii;
container ctNotes;
;
// Empty the B table.
delete_from tabNumB;
// Insert a copy of one record.
insert_recordset tabNumB (NumberBKey)
select firstOnly NumberAKey from tabNumA
order by NumberAKey asc;
ttsBegin;
try
{
iCountTries++;
ctNotes += strFmt
("---- Inside the try block, try count is %1. ----"
,iCountTries);
while select * from tabNumA
order by NumberAKey asc
{
tabNumB .clear();
iNewKey = tabNumA .NumberAKey + iNumberAdjust;
tabNumB .NumberBKey = iNewKey;
ctNotes += strFmt
("-- %1 is the key to be tried. --" ,iNewKey);
tabNumB .insert();
ctNotes += "-- .insert() successful. --";
break; // Keeps demo simple.
}
ttsCommit;
}
catch (Exception ::DuplicateKeyException
,tabNumB) // Table is optional.
{
ctNotes += "---- Inside the catch block. ----";
ctNotes += infolog .text();
if (iCountTries <= 1)
{
ctNotes += "-- Will issue retry. --";
iNumberAdjust = 1;
retry; // Erases Infolog.
}
else
{
ctNotes += "-- Aborting the transaction. --";
ttsAbort;
}
}
for (ii=1; ii <= conLen(ctNotes); ii++)
{
info(conPeek(ctNotes ,ii));
}
/*********** Actual output
Message (10:53:13 am)
---- Inside the try block, try count is 1. ----- 11 is the key to be tried. ----- Inside the catch block. ---Cannot create a record in TableNumberB (TableNumberB).
The record already exists.
-- Will issue retry. ----- Inside the try block, try count is 2. ----- 12 is the key to be tried. --- .insert() successful. -***********/
Note
Use this option with caution, as it could lead to excessive disk swapping or excessive
memory usage.
A dynamic array is sized according to the largest index ever used in the array. For example,
if you use an index of 1000 in an array, the size of the array is set to 1000. If you then use
an index of 500, the array size remains 1000.
The general rules are:
If you use all or nearly all entries in an array, set the memory option to a large
number or do not set the option at all.
If you use few, non-consecutive entries in the array, set the memory option to a small
number, such as 1.
If you use record IDs as indexes, set the memory option to 1. Record IDs are typically
very large integers. When you use them as indexes, the size of your dynamic arrays
grows unacceptably large.
For example:
MyTable myTable;
boolean foundRecord[,1];
;
while select myTable
where myTable ...
{
foundRecord[myTable.RecId] = true;
...
}
Declaration of Variables
All variables must be declared before they can be used. X++ does not allow variable
declarations to be mixed with other X++ statements; variables must be declared before X+
+ statements.
You should separate variable declarations from the rest of your code with a semi-colon (;) on
a separate line, at the end of the declarations:
int i;
;
// Other code
The syntax for the declaration of each variable type is described in the help topics for the
Primitive Data Types and Composite Data Types.
When a variable is declared, memory is also allocated and the variable is initialized to the
default value. The only exception to this is for objects, where you need to manually allocate
memory by using the new method. For more information, see Classes as Data Types.
real pi = 3.14159265359;
Another syntax is needed to initialize objects because they are initialized by invoking the
new method on the class:
// Simple call to the new method in the Access class
Access accessObject = new Access();
Multiple Declarations
X++ allows you to declare more than one variable in the same declaration statement. For
example:
// Declares 2 integers, i and j
int i,j;
// Declares array with 100 integers with 5 in memory and b as integer with value 1
int a[100,5], b=1;
Summary
The possible variable declarations in X++ are shown by using EBNF (Extended Backus Naur
Form) grammar, in the following table:
Declaration
DatatypeVariable{ ,Variable} ;
Datatype
Variable
Typeoptions
Option
Arrayoption | Initialization
Arrayoption
[ Length , Memory ]
Initialization
expression
X++ composite
data types
Description
Arrays
An array is a list of items with the same data type and the same
nameonly the index differs.
Containers
Classes as Data
Types
The Collection classes allow you to create arrays, lists, sets, maps, and structs that can hold
any data type, including objects.
Data
type
Default Description
Array
as data
The default value on an array is that all items have their normal
type
default value.
Containe empty
Class
null
A class is only a definition for objects, and all objects are null when
they are declared.
Table
empty
Code is easier to read because variables have a meaningful data type. For example,
Name instead of string.
The properties you set for an extended data type are used by all instances of that
type, which reduces work and promotes consistency. For example, account numbers
(AccountNum data type) have the same properties throughout the system.
You can create hierarchies of extended data types, inheriting the properties that are
appropriate from the parent and changing the other properties. For example, the
ItemCode data type is used as the basis for the MarkupItemCode and
PriceDiscItemCode data types.
Extended declaration
ExtendedtypeVariable{ ,Variable} ;
Variable
Identifier [ option ]
Option
arrayoptions | initialization
where Extendedtype is the name of the extended data type in the AOT.
// A UserGroupID (integer) variable is declared and initialized to 1
UserGroupID groupID = 1;
// An Amount (real) variable is declared
Amount currency;
Automatic Conversion
Because extended data types are normal data types but with a specific name and
properties, all standard conversions are performed.