1. Introduction
Reflection
is a programmatic discoverability mechanism of the Microsoft Dynamics
AX application model. In other words, reflection provides APIs for
reading and traversing element definitions. By using the reflection
APIs in the MorphX development environment, you can query metadata as
though it were a table, an object model, or a tree structure.
You
can do interesting analyses with the reflection information. The
Reverse Engineering tool is an excellent example of the power of
reflection. Based on element definitions in MorphX, the tool generates
Unified Modeling Language (UML) models and Entity Relationship Diagrams
(ERDs) that you can browse in Microsoft Office Visio.
Reflection
also allows you to invoke methods on objects. This capability is of
little value to business application developers who construct class
hierarchies properly. For framework developers, however, the power to
invoke methods on objects can be valuable. Suppose, for example, you
want to programmatically write any record to an XML file that includes
all of the fields and display methods. Reflection allows you to
determine the fields and their values and also to invoke the display
methods to capture their return values.
X++
features a set of system functions that you can use for reflection, as
well as three reflection APIs. The reflection system functions follow:
Intrinsic functions A set of functions that allow you to refer to an element’s name or ID safely at compile time
TypeOf system function A function that returns the primitive type for a variable
ClassIdGet system function A function that returns the ID of the class for an instance of an object
These are the reflection APIs:
Table data
A set of tables that contains all element definitions. The tables give
you direct access to the contents of the .aod files. You can query for
the existence of elements and certain properties, such as created by and created datetime. You can’t retrieve information about the contents or structure of each element.
Dictionary A set of classes that provide a type-safe mechanism for reading metadata from an object model. Dictionary
classes provide basic and more abstract information about elements in a
type-safe manner. With few exceptions, this API is read-only.
Treenodes
A class hierarchy that provides the Application Object Tree (AOT) with
an API that can be used to create, read, update, and delete any piece
of metadata or source code. This API can tell you everything about
anything in the AOT. You navigate the tree-nodes in the AOT through the
API and query for metadata in a non-type-safe manner.
2. Reflection System Functions
The
X++ language features a set of system functions that can be used to
reflect on elements. They are described in the following sections.
Intrinsic Functions
You should use intrinsic functions
whenever you need to reference an element from within X++ code.
Intrinsic functions provide a way to make a type-safe reference. The
compiler recognizes the reference and verifies that the element being
referenced exists. If the element doesn’t exist, the code doesn’t
compile. Because elements have their own life cycles, a reference
doesn’t remain valid forever; an element can be renamed or deleted.
Using intrinsic functions ensures that you are notified of any broken
references at compile time. A compiler error early in the development
cycle is always better than a run-time error.
All
references you make using intrinsic functions are captured by the
Cross-reference tool. So you can determine where any element is
referenced, regardless of whether the reference is in metadata or code.
Consider these two implementations.
print "MyClass"; //Prints MyClass print classStr(MyClass); //Prints MyClass
|
They
have exactly the same result: the string “MyClass” is printed. As a
reference, the first implementation is weak. It will eventually break
when the class is renamed or deleted, meaning that you’ll need to spend
time debugging. The second implementation is strong and unlikely to
break. If you were to rename or delete MyClass, you could use the Cross-reference tool to do an impact analysis of your changes and correct any broken references.
Using the intrinsic functions <Concept>Str,
you can reference all the elements in the AOT by their names. You can
also reference elements that have an ID with the intrinsic function <Concept>Num.
Intrinsic functions are not limited to parent objects; they also exist
for class methods, table fields, indexes, and methods. More than 50
intrinsic functions are available. Here are a few examples of intrinsic
functions.
print fieldNum(MyTable, MyField); //Prints 50001 print fieldStr(MyTable, MyField); //Prints MyField print methodStr(MyClass, MyMethod); //Prints MyMethod print formStr(MyForm); //Prints MyForm
|
An
element’s ID is assigned when the element is created. The ID is a
sequential ID dependant on an application model layer. In the preceding
example, 50001 is the ID assigned to the first element created in the USR layer.
Two other intrinsic functions are worth noting: identifierStr and literalStr. IdentifierStr allows you to refer to elements when a more feature-rich intrinsic function isn’t available. IdentifierStr provides no compile-time checking and no cross-reference information. Using the identifierStr
function is much better than using a literal, because the intention of
referring to an element is captured. If a literal is used, the
intention is lost—the reference could be to user interface text, a file
name, or something completely different. The Best Practices tool
detects the use of identifierStr and issues a best practice warning.
The
Dynamics AX runtime automatically converts any reference to a label
identifier to the label text for the label identifier. In most cases,
this behavior is what you want; however, you can avoid the conversion
by using literalStr. LiteralStr allows you to refer to a label identifier without converting the label ID to the label text, as shown here.
print "@SYS1"; //Prints Time transactions print literalStr("@SYS1"); //Prints @SYS1
|
In the first line of the example, the label identifier (@SYS1) is automatically converted to the label text (Time transactions). In the second line, the reference to the label identifier isn’t converted.
TypeOf System Function
The TypeOf system function takes a variable instance as a parameter and returns the primitive type of the parameter. Here is an example.
int i = 123; str s = "Hello world"; MyClass c; Guid g = newGuid();
print typeOf(i); //Prints Integer print typeOf(s); //Prints String print typeOf(c); //Prints Class print typeOf(g); //Prints Guid pause;
|
The return value is an instance of the Types system enumeration. It contains an enumeration for each primitive type in X++.
ClassIdGet System Function
The ClassIdGet
system function takes an object as a parameter and returns the class ID
for the class element of which the object is an instance. If the
parameter passed is Null, the function returns the class ID for the
declared type, as shown here.
MyBaseClass c; print classIdGet(c); //Prints 50001
c = new MyDerivedClass(); print classIdGet(c); //Prints 50002 pause;
|
This
function is particularly useful for determining the type of an object
instance. Suppose you need to determine whether a class instance is a
particular class. The following example shows how you can use ClassIdGet to determine the class ID of the _anyClass variable instance. If the _anyClass variable really is an instance of MyClass, it’s safe to assign it to the variable myClass.
void myMethod(object _anyClass) { MyClass myClass; if (classIdGet(_anyClass) == classNum(MyClass)) { myClass = _anyClass; ... } }
|
Notice the use of the intrinsic function, which evaluates at compile time, and the use of classIdGet, which evaluates at run time.
Because
inheritance isn’t taken into account, this sort of implementation is
likely to break the object model. In most cases, any instance of a
derived MyClass class should be treated as an actual MyClass instance. The simplest way to handle inheritance is to use the is and as static methods on the SysDictClass class. You’ll recognize these methods if you’re familiar with C#. The is method returns true if the object passed in is of a certain type, and the as method can be used to cast an instance to a particular type. The as method returns null if the cast is invalid.
These two methods also take interface implementations into account. So with the as method, you can cast your object to an interface. Here is a revision of the preceding example using the as method.
void myMethod(object _anyClass) { MyClass myClass = SysDictClass::as(_anyClass, classNum(MyClass)); if (myClass) { ... } }
|
Here is an example of an interface cast.
void myMethod2(object _anyClass) { SysPackable packableClass = SysDictClass::as(_anyClass, classNum(SysPackable)); if (packableClass) { packableClass.pack(); } } |