Generic types in X++ (AX 7)

This blog post assumes that you’re familiar with generic types. If it’s not the case, I suggest you look at my previous blog post before continuing.

You can’t create generic types and methods in X++, so why would you care about generics at all? The main reason is that it’s used heavily in .NET codebase and there are many .NET APIs that you may want to use from X++. Without being able to use generic types and methods, a lot of .NET functionality would be inaccessible to you.

When you want to use class or methods that use generics, you have two main options.

Firstly, you can somehow handle it in X++. For example, let’s say you want to get file names with the help of Dictionary::EnumerateFiles(), which returns a generic collection of strings (IEnumerable<string>). You can avoid the problem of declaring generic types by utilizing the var keyword, like here:

var files = System.IO.Directory::EnumerateFiles('c:\\');
var enumerator = files.GetEnumerator();
 
while (enumerator.MoveNext())
{
    info(enumerator.Current);
}

The var keywork is just a shorthand; the real types (such as IEnumerable<string>) are still used, as you can verify in the debugger:

As you can see, you often can work with generic types without explicitly using any syntax specific to generics. Using var is just one of such techniques.

But not everything can be done in X++, or it’s not very easy to do it there. Another option is dealing with generics in a separate library (usually in C#) and exposing a non-generic interface for consumption in X++. It’s not just about generics, but anything that can’t be reasonably done from X++. For example, if you want to find names in a list that are longer than a defined limit, you can use the FindAll() method, which expects a parameter of type Predicate<T>. Rather than trying to deal with it in X++, you can write a facade method in a C# library and later call this simpler method from X++.

public List<string> namesLongerThan(List<string> list, int numOfChars)
{
    return list.FindAll(e => e.Length > numOfChars);
}

It works great and working with such libraries is easier with every version of AX (you can use automatically deployed VS projects in AOT in AX 2012 and project references in AX 7), but the non-generic interface available to X++ will obviously lose all the power of generics.

Support in X++

I was thrilled when I learnt that X++ in AX 7 supports generic .NET classes when declaring variables and creating objects – I didn’t hear about this possibility before. I have many cases when using generics would be very useful and I was keen to explore what I can do with this feature.

Unfortunately, I’m not too impressed so far – I’ll explain why in a moment. There is probably a reason why this feature isn’t loudly promoted; it looks like a work still in progress.

Let’s start with something that actually works.

If you want to create instances of generic classes, you can use the same syntax as in C# (with angle brackets, i.e. <>).

System.Collections.Generic.List<int> users = new System.Collections.Generic.List<int>();

To simplify the code and make it more readable, we can avoid repeating all the namespaces by utilizing the using keyword.

using System.Collections;
…
Generic.List<int> users = new Generic.List<int>();

You can use any type you like for the type parameter, including those defined in AX. For example, here I’m create a queue of buffers of the UserInfo table:

Generic.Queue<UserInfo> userQueue = new Generic.Queue<UserInfo>();

Generic types are not limited to collection classes, of course. One thing I tried and works better than I expected are nullable types. It’s sometimes important to distinguish whether there is no value at all or if there is a value but it happens to have a default value of the given type. For example, whether a calculation shows that a balance is 0 is or whether the balance hasn’t yet been calculated are clearly two very different cases. AX itself isn’t good in dealing with these things, but .NET comes with a solution – nullable types. I can create an instance of Nullable<int> and assign either a null reference or an actual number, and later check if it has a value at all and what it is.

System.Nullable<int> c = null;
info(c.HasValue ? strFmt("Value %1", c.Value) : "No value");
c = 0;
info(c.HasValue ? strFmt("Value %1", c.Value) : "No value");

Bugs and unsupported features

Unfortunately, I’ve run into quite a few problems. (For reference, note that I’m using a demo VM with Platform Update 5.)

Problems with IntelliSense

When I declare a variable of a generic type (e.g. System.Nullable<int> c = 5;) and try to use the variable, IntelliSense doesn’t offer any members (after typing c.). This seems to be a more general problem with IntelliSense for .NET types. But it gets worse – if I try the same after building the project, IntelliSense throws an error:

Name System.Nullable<int> is invalid. Metadata name must be non-null and can not contain whitespace or invalid path characters.

Missing type checks

X++ compiler doesn’t always check types of arguments when working with generic types, which defeats the whole purpose of generic types. This may have the same cause as missing IntelliSense information. For example, the following code compiles without any error, although I’m trying to assign string values to a collection of integers. The runtime will try to convert these strings to integers and throws an error only if it fails. Using wrong types should fail already on compilation.

System.Collections.Generic.List<int> list = new System.Collections.Generic.List<int>();
list.Add(1);
list.Add("2");     // Converts to int
list.Add("hello"); // FormatException at runtime

‘Real’ type can’t be used

As far as I know, you can use any X++ type as the type parameter, with a single exception: real. If you try to write something like this,

new System.Collections.Generic.List<real>();

the compiler will complain that a ‘(‘ is expected (which suggests that it fails already during parsing).

Failure with var

Another problem is with the var keyword. You may want to try to simplify your code by declaring the type of variable as var, like here:

var list = new System.Collections.Generic.List<int>();

Unfortunately, X++ compiler fails horribly on this code – it throws an exception from ILGeneratorSweeper.VisitClrType(), complaining about an unsupported type.

Generic methods can’t be called

A serious flaw is that generic methods can’t be called from X++. It’s great that I can create a generic collection in X++, but what will I do with it if I can’t pass it to any method accepting generic collections?

For example, I may want to extract distinct elements from a collection, which can be easily done in .NET with an extension method Enumerable.Distinct<TSource>(). If I try to call it from X++ (System.Linq.Enumerable::Distinct(list)), X++ compiler seems to ignore the generic parameter and it looks for non-generic Distinct(), which doesn’t exists. It’s also visible in IntelliSense, which finds the method, but it claims it’s Distinct() instead of Distinct<TSource>(). Although IntelliSense will offer you the method, the compiler will later say that it doesn’t exist.

Method 'Distinct(System.Collections.Generic.IEnumerable`1)' is not found on type 'System.Linq.Enumerable'.

Casting

Another serious problem is that you can’t cast generic types to generic interface they implement. For example, you have a generic list of integers(List<int>) and you want to assign it to a variable of IEnumerable<T>.

List<T> implements IEnumerable<T>, so it should be all right, but it fails, saying: Cannot implicitly convert from type ‘System.Collections.Generic.List’ to type ‘System.Collections.Generic.IEnumerable’.

There is a workaround for this, but it means giving up compile-time type control:

CLRObject l1 = new System.Collections.Generic.List<int>();
System.Collections.Generic.IEnumerable<int> l2 = l1;

By the way, assignments to the non-generic IEnumerable interface work fine:

System.Collections.IEnumerable list = new System.Collections.Generic.List<int>();

Extending generic classes

One more thing I tried was inheriting an X++ class from List<str> (inheriting from generic types in this way isn’t uncommon in .NET), but it doesn’t compile in X++. Nevertheless this definitely isn’t the most important feature.

Conclusion

Although the ability to declare and instantiate objects of generic types directly in X++ is nice, it doesn’t work very well in the moment. Also, many related features (such as the support for generic methods) are still missing, therefore if you want to use generics in X++, you still have to understand how to work around all the limitations.

After reading the full specification of C#, I realize how generics (and covariance/contravariance) greatly increases the complexity of language design, but I hope we’ll see more generics in X++ in future. It’s necessary for easy interoperability with .NET code and obviously such a great feature would be very useful in pure X++ as well.

3 Comments

  1. Yes, the support for generics in X++ is purely experimental at this point. We have done nothing to encourage its use, since it is, indeed, not in product quality at this time.

  2. Thank you, great explanation for me have been a long time waiting for generics in x++ because before DAX I worked with c# and I always use generics, collection and linq to object to do everything, doing more simple the code, but seems to be we have to wait more.

    • I absolutely understand why you miss generics and LINQ so much, but the messages I got from Microsoft weren’t exactly encouraging. They’re clearly busy with many other things and I assume that generics is somewhere on a “nice to have” list for this moment.

Leave a Reply

Your email address will not be published.