Introduction
X++ is an object-oriented, application-aware, and data-aware programming
language. The language is object oriented because it supports object
abstractions, abstraction hierarchies, polymorphism, and encapsulation.
It is application aware because it includes keywords such as client, server, changecompany, and display
that are useful for writing client/server enterprise resource planning
(ERP) applications. And it is data aware because it includes keywords
such as firstFast, forceSelectOrder, and forUpdate, as well as a database query syntax, that are useful for programming database applications.
You use the Dynamics AX
designers and tools to edit the structure of application types. You
specify the behavior of application types by writing X++ source code
using the X++ editor. The X++
compiler compiles this source code into bytecode intermediate format.
Model data, X++ source code, and intermediate bytecode are stored in
.aod files. The Dynamics AX runtime dynamically composes object types by
loading overridden bytecode from the highest level definition in the
model layering stack.
Jobs
Jobs are model elements
that you create by using the Application Object Tree (AOT). The
following X++ code example provides an example of a job model element
that prints the “Hello World” string to an automatically generated
window. The pause statement stops program execution and waits for user input from a dialog box.
static void myJob(Args _args) { print "Hello World"; pause; }
|
Jobs are globally
defined functions that execute in the rich client runtime environment.
Jobs are frequently used to test a piece of business logic because they
are easily executed from within the MorphX integrated development
environment (IDE), by either pressing F5 or selecting Go on the command
menu. You shouldn’t use jobs as a core part of your application’s core
design.
The Type System
The Dynamics AX
runtime manages the storage of value type data on the call stack and
reference type objects on the memory heap. The call stack is the memory structure that holds data about the active methods called during program execution. The memory heap is the memory area that allocates storage for objects that are destroyed automatically by the Dynamics AX runtime.
Value Types
Value types include the built-in primitive types, extended data types, enumeration types, and built-in collection types:
The primitive types are boolean, int, int64, real, date, utcdatetime, timeofday, str, and guid.
The
extended data types are specialized primitive types and specialized
base enumerations. User-defined extended data types are dynamically
composed from application model layers.
The
enumeration types are base enumerations and extended data types.
User-defined enumeration types are dynamically composed from application
model layers. Dynamics AX runtime enumeration types are exposed in the
system API.
The collection types are the built-in array and container types.
By default, variables
declared as value types are assigned their zero value by the Dynamics AX
runtime. These variables can’t be set to null. Variable values are
copied when variables are used to invoke methods and when they are used
in assignment statements. Therefore, two value type variables can’t
reference the same value.
Reference Types
Reference types include the record types, class types, and interface types:
The record types are table, map, and view.
User-defined record types are dynamically composed from application
model layers. Dynamics AX runtime record types are exposed in the system
API.
Note
Although they are not visible in the AOT, all record types implement the methods that are members of the system xRecord type, a Dynamics AX runtime class type. |
User-defined
class types are dynamically composed from application model layers and
Dynamics AX runtime class types exposed in the system API.
Interface
types are type specifications and can’t be instantiated in the Dynamics
AX runtime. Class types can, however, implement interfaces.
Variables declared
as reference types contain references to objects that the Dynamics AX
runtime instantiates from dynamically composed types defined in the
application model layering system and from types exposed in the system
API. The Dynamics AX runtime also performs memory deallocation (garbage
collection) for these objects when there are no longer any references to
them. Reference variables declared as record types reference objects
that the Dynamics AX runtime instantiates automatically. Class type
objects are programmatically instantiated using the new operator. Copies of object references are passed
as reference parameters in method calls and are assigned to reference
variables, so two variables could reference the same object.
More Info
Not all nodes in the AOT name a type declaration. Some class declarations are merely syntactic sugar—convenient, human-readable expressions. For example, the class header definition for all rich client forms declares a FormRun class type, the class header definition for all rich client reports declares a ReportRun class type, and the class header definition for a Web client form declares a WebFormRun class type. FormRun, ReportRun, and WebFormRun
are also, however, class types in the system API. Allowing their
declarations is syntactic sugar because it is technically impossible to
have two types with the same name in the Dynamics AX class type
hierarchy. |
Type Hierarchies
The X++ language
supports the definition of type hierarchies that specify generalization
and specialization relationships between class types. For example, a
check payment method is a type of payment method. A type hierarchy
allows code reuse. Reusable code is defined on base types defined higher
in a type hierarchy as they are inherited, or reused, by derived types
defined lower in a type hierarchy.
Tip
You can use the Application Hierarchy Tree tool in MorphX to visualize and browse the hierarchy of any type. |
This section introduces
the base types provided by the Dynamics AX runtime and describes how
they are extended in type hierarchies.
Caution
The
Dynamics AX type system is known as a weak type system because X++
accepts certain type assignments that are clearly erroneous and lead to
run-time errors. Be aware of the caveats outlined in the following
sections, and try to avoid weak type constructs when writing X++ code. |
The anytype Type
The Dynamics AX type system doesn’t have a strict type hierarchy with a concrete base type for all types. The anytype type therefore imitates a base type for all types. Variables of the any-type
type behave like value types when they are assigned a value type
variable and like reference types when they are assigned a reference
type variable. You can us the SysAnyType class to explicitly box all types, including value types, and make them behave like reference types.
The anytype
type, shown in the following code sample, is syntactic sugar that
allows methods to accept any type as a parameter or allows a method to
return different types.
static str queryRange(anytype _from, anytype _to) { return SysQuery::range(_from,_to); }
|
You can declare variables by using anytype. However, the underlying data type of an anytype variable is set to match the first assignment, and you can’t change its type afterward, as shown here.
anytype a = 1; print strfmt("%1 = %2", typeof(a), a); //Integer = 1 a = "text"; print strfmt("%1 = %2", typeof(a), a); //Integer = 0
|
The common Type
The common type is the base type of all record types. Like the anytype
type, record types are context-dependent types whose variables can be
used as though they reference single records or a record cursor that can
iterate over a set of database records.
Using the common type allows you to cast one record type to another (possibly incompatible) record type, as shown in this example.
//customer = vendor; //Compile error common = customer; vendor = common; //Accepted
|
Table maps defined in the
AOT are a more type-safe method of capturing commonalities between
record types, and you should use them to prevent incompatible record
assignments. A table map defines fields and methods that safely operate
on one or more record types.
The compiler doesn’t validate method calls on the common type. For example, the compiler accepts the following method invocation even though the method doesn’t exist.
common.nonExistingMethod();
|
For this reason, you should use reflection to confirm that the method on the common type exists before you invoke it, as shown in this example:
if (tableHasMethod(new DictTable(common.tableId), identifierStr(existingMethod))) { common.existingMethod(); }
|
The object Type
The built-in object
type is a weak reference type whose variables reference objects that
are instances of class or interface types in the Dynamics AX class
hierarchy.
The type system
allows programmers to implicitly cast base type objects to derived type
objects and to cast derived type objects to base type objects, as shown
here.
baseClass = derivedClass; derivedClass = baseClass;
|
The object
type allows you to use the assignment operator and cast one class type
to another, incompatible class type, as shown in the following code. The
probable result of this action, however, is a run-time exception when
your code encounters an object of an unexpected type.
//textIO = binaryIO; //Compile error Object = textIO; binaryIO = object; //Accepted
|
Use the SysDictClass class instead of the assignment operator to prevent these incompatible type casts. SysDictClass provides the is method to safely cast derived types of base types and the as method to safely cast base types to derived types.
Keep in mind that the compiler doesn’t validate method calls on the object type. The compiler accepts the following method invocation even though the method doesn’t exist.
object.nonExistingMethod();
|
Extended Data Types
You use the AOT to create extended data types that model concrete data values and data hierarchies. For example, the Name extended data type is a string, and the CustName and VendName extended data types extend the Name data type.
The X++ language
supports extended data types but doesn’t offer any type checking
according to the hierarchy of extended data types. X++ treats any
extended data type as its primitive type; therefore, code such as the
following is allowed.
CustName customerName; FileName fileName = customerName;
|
When used properly, extended data types improve the readability of X++ code; it’s easier to understand the intended use of a CustName data type than a string data type, even if they are both used to declare string variables.
Extended data types are
more than just type definitions to make X++ code more readable. On each
extended data type, you can also specify how the system displays values
of this type to users. Further, you can specify a relationship between
the extended data type and a table field. The relationship serves two
primary purposes. First, it ensures a foreign key relationship is
automatically created for any table field using the extended data type.
Second, it enables the form’s rendering engine to automatically build
lookup forms for form controls using the extended data type, even when
they are not bound to a data source. On string-based extended data
types, you can specify the maximum string size of the type. The database
layer uses the string size to define the underlying columns for fields
using the extended data type. Having the string size defined in only one
place makes it easy to change.