I have been away from X++ for a bit (Doing a bit of technical presales, leading an offshore Dynamics team, and Power Platform work), but now I’m back doing F&O development (-:
Being an end customer taught me much about high-availability systems and the importance of creating maintainable enterprise-scale business applications.
This is a fairly minor thing, but it is a clever way to increase the readability of X++ customizations.
Implementation
Have you ever thought about creating custom types in X++? I’m not talking about data contracts, but actual custom types that can be compared and used in collections such as sets and maps.
Custom types enable you to replace some of the containers in your code (i.e., remembering the ordering of fields or creating macros), implement abstractions for your types, and add functionality to your types.
Let’s say you want to implement a specific way to pack or unpack type to a custom format. You can do that if you implement your custom type; Simply add the methods directly to the type.
Custom types will help to make code self-documenting. Let’s say you want to work with couches.
A couch can have many parameters: color, size, fabric type etc.
Now you want to calculate a price for a specific combination, and store it in a map. This is how you could solve it by storing the value in a map.
var couchToCalculate = new Couch();
couchToCalculate.Color = "Green";
couchToCalculate.Leather = NoYes::Yes;
couchToCalculate.Persons = 4;
if (!calculatedCouches.exists(couchToCalculate))
{
calculatedCouches.add(couchToCalculate,
CouchCalculate::calcCouch(couchToCalculate));
}
To get it to work, you need to overwrite getHashCode and Equals from the Object type. These methods ensure that it can work as keys in a collection. Per default, these methods just return the class number and always true: Not good if you want several different keys!
This is an example implementation:
public final class MyCustomType
{
public System.String field1;
public System.String field2;
public System.Int32 field3;
public void new(
str _field1,
str _field2,
int _field3)
{
this.field1 = _field1;
this.field2 = _field2;
this.field3 = _field3;
}
public int getHashCode()
{
return field1.GetHashCode() ^ field2.GetHashCode() ^ field3;
}
public boolean Equals(System.Object _obj)
{
MyCustomType contract = _obj;
return this.field1 == contract.field1
&& this.field2 == contract.field2
&& this.field3 == contract.field3;
}
}
In this example I have made a simple type with two string fields and one integer field.
I’m using .Net type (System.String, System.Int32) because I want to use their hash codes in my getHashCode calculation and XOR them together. It’s not possible if you use the standard X++ types…
The uniqueness of getHashCode is not 100% required, only for the sake of performance. You can think of it as the collection functions will create bundles of each hash code, and then call Equals for the objects in the collection. See these remarks
I have not created parm() methods – I’m just making the variables public so I can uses them as fields.
This is how you can use your custom type with a map. And lookups are exactly the same.
You can of course also store the type in a variable, just as a string…
calculationResults.add(
new MyCustomType(
SomeTable.SomeField,
SomeTable.AnotherField,
AThirdTable.AThirdField),
1000 * collectionIterator);
Performance in collections
One of my initial concerns was the performance of my custom type in collections, so I built a simple benchmark. All it does is create 4 different types of maps: Two with strings as a key, one using StrFmt and one that adds strings together, 1 with a container, and lastly with my custom type.
The benchmark is fairly simple. It adds 1.000.000 key/value pairs with random values to each pair and, after that, performs 1000 lookups again with random values.
It turns out that using a custom type in this scenario is actually faster. I’m not surprised by the containers; they are notorious for being slow.
Conclusion
Custom types are a great way to improve the readability of your code and make it self-documenting. It is also somewhat faster if you work with an insanely high number of objects in your collections, but I doubt it will make it faster in real life.
You can download the example and benchmarking job here to play around: MyCustomType.zip