Back to articles
X++ Development

Collections (List, Map, Set)

Learn how List, Map, and Set collections work in X++ and when to use each one.

Collections in X++: List, Map, and Set#

When you need to work with multiple values in X++, you have a few different options.

You will still see containers used throughout D365FO, especially in older code and framework methods, but for most new development I normally prefer one of the X++ collection classes:

  • List
  • Map
  • Set

Which one you use depends on what you are trying to store.

The easiest way to think about it is:

  • List — I need a collection of values.
  • Set — I need unique values.
  • Map — I need a key and a value.

Once you understand that difference, choosing between them is pretty straightforward.


When would you use collections?#

Collections come up all the time in normal D365FO development.

You might need to:

  • Store a list of ItemIds.
  • Keep track of records you already processed.
  • Remove duplicate values.
  • Associate one value with another.
  • Pass multiple values between methods.
  • Build a list of records that need additional processing.
  • Collect values while looping through a query.

For example, maybe you are processing InventTrans records and need to keep track of every batch you touched.

You could use a List, but if you only want each batch once, a Set is probably the better choice.

That is really the main thing with collections: use the collection that matches what the code is actually doing.


List#

A List stores multiple values of the same type.

Use a List when you need to maintain a collection of values and duplicates are allowed.

For example:

X++
List itemList = new List(Types::String);

itemList.addEnd("A0001");
itemList.addEnd("A0002");
itemList.addEnd("A0003");

Now itemList contains three values.

You can loop through the list using a ListEnumerator:

X++
ListEnumerator enumerator = itemList.getEnumerator();

while (enumerator.moveNext())
{
    ItemId itemId = enumerator.current();

    info(itemId);
}

The important thing here is that the List is created with a type:

X++
new List(Types::String);

That tells X++ what type of values the list is expected to contain.


When would I use a List?#

A List is a good choice when I simply need to collect multiple values.

For example, maybe a method determines which items need to be recalculated:

X++
List itemList = new List(Types::String);

while select inventTable
    where inventTable.ItemGroupId == "RAW"
{
    itemList.addEnd(inventTable.ItemId);
}

That list could then be passed to another method for processing.

The values stay in the collection and can be enumerated later.

If duplicates are possible and you don't want them, though, I would probably use a Set instead.


Set#

A Set is similar to a List, except it only stores unique values.

This makes it extremely useful when you are collecting values while processing records and don't want to process the same thing twice.

For example:

X++
Set batchSet = new Set(Types::String);

batchSet.add("BATCH001");
batchSet.add("BATCH002");
batchSet.add("BATCH001");

Even though BATCH001 was added twice, the set only contains it once.

You can loop through a set using a SetEnumerator:

X++
SetEnumerator enumerator = batchSet.getEnumerator();

while (enumerator.moveNext())
{
    InventBatchId inventBatchId = enumerator.current();

    info(inventBatchId);
}

This is one of the collection types I use pretty often.


A practical Set example#

Let's say a posting process touches multiple inventory transactions.

Several transactions could belong to the same batch, but you only want to recalculate each batch once.

Instead of doing this:

X++
while select inventTrans
{
    recalculateBatch(inventTrans.ItemId, inventTrans.InventBatchId);
}

you could first collect the affected batches.

If you only needed the batch ID, you could use a Set:

X++
Set batchSet = new Set(Types::String);

while select inventTrans
{
    if (inventTrans.InventBatchId)
    {
        batchSet.add(inventTrans.InventBatchId);
    }
}

Now it doesn't matter if 50 transactions belong to the same batch.

That batch only exists once in the Set.

You can then process each unique batch:

X++
SetEnumerator enumerator = batchSet.getEnumerator();

while (enumerator.moveNext())
{
    InventBatchId inventBatchId = enumerator.current();

    recalculateBatch(inventBatchId);
}

This is much cleaner than manually checking whether you already added a value.


Map#

A Map is different from a List and Set.

Instead of storing individual values, it stores key/value pairs.

For example:

X++
Map itemQuantityMap = new Map(
    Types::String,
    Types::Real);

itemQuantityMap.insert("A0001", 10);
itemQuantityMap.insert("A0002", 25);

Here:

  • A0001 is the key.
  • 10 is the value.
  • A0002 is another key.
  • 25 is its value.

You can then retrieve a value using its key:

X++
Qty qty = itemQuantityMap.lookup("A0001");

info(strFmt("Quantity: %1", qty));

The result would be 10.


When would I use a Map?#

A Map is useful whenever one value needs to point to another value.

For example, maybe you are calculating quantities by ItemId:

X++
Map itemQuantityMap = new Map(
    Types::String,
    Types::Real);

As you process records, you can store the calculated quantity against the ItemId.

Later, instead of querying or recalculating the value again, you can look it up:

X++
Qty qty = itemQuantityMap.lookup(itemId);

Maps are also useful for things like:

  • ItemId → Quantity
  • RecId → Description
  • Account → Balance
  • FieldId → Value
  • Record ID → Processing status

If your requirement sounds like:

"For this key, I need to know this value."

a Map is probably worth looking at.


Looping through a Map#

You can use a MapEnumerator to loop through the values:

X++
MapEnumerator enumerator = itemQuantityMap.getEnumerator();

while (enumerator.moveNext())
{
    ItemId itemId = enumerator.currentKey();
    Qty qty       = enumerator.currentValue();

    info(strFmt(
        "Item: %1, Quantity: %2",
        itemId,
        qty));
}

Unlike a List or Set, you have both the key and the value available while enumerating the collection.


List vs Set vs Map#

Here is the quick version:

CollectionUse it when
ListYou need multiple values and duplicates are okay
SetYou need unique values
MapYou need key/value pairs

For example, if I need to store:

Code
A0001
A0002
A0003

I would use a List or Set depending on whether duplicates matter.

If I need:

Code
A0001 -> 10
A0002 -> 25
A0003 -> 15

I would use a Map.

Don't make it more complicated than it needs to be.


Collections vs containers#

You will also see containers used all over X++.

For example:

X++
container itemIds = [
    "A0001",
    "A0002",
    "A0003"
];

There is nothing inherently wrong with containers. Some D365FO APIs expect them, and there are plenty of places where they make sense.

But containers are immutable.

When you modify a container, X++ creates a new container rather than modifying the existing one.

That means code that repeatedly adds values to a container:

X++
itemIds += itemId;

can become inefficient when dealing with larger amounts of data.

Collections are generally a better fit when you are dynamically building or modifying a group of values.

I wouldn't replace every container I see with a List, Map, or Set.

If an API expects a container or you only have a few values, use the container.

Choose the type that actually fits the requirement.


Best practices#

A few things I normally keep in mind when using collections:

  • Use a List when order matters and duplicates are acceptable.
  • Use a Set when values should be unique.
  • Use a Map when you need key/value relationships.
  • Define the correct type when creating the collection.
  • Use the appropriate enumerator when looping through collections.
  • Don't use a List and manually remove duplicates when a Set already solves the problem.
  • Don't use a Map when you only need a simple collection of values.
  • Use containers when the D365FO framework or API expects a container.

Most importantly, make the collection type tell me something about what the code is doing.

If I see:

X++
Set processedBatches;

I immediately know we probably don't want to process the same batch twice.

That is much better than having to read 30 lines of code to figure it out.


Common mistakes#

Using a List when you really need a Set#

If duplicates should never exist, don't add extra logic to a List to prevent them.

Instead of doing something like:

X++
if (!itemList.in(itemId))
{
    itemList.addEnd(itemId);
}

consider whether a Set is the better collection.

That is exactly what it was designed for.


Using the wrong collection type#

Don't automatically use a List every time you need multiple values.

Think about the data first.

Do duplicates matter?

Do you need to associate one value with another?

Do you need to look values up by a key?

Those questions usually tell you which collection to use.


Forgetting about empty collections#

Don't assume a collection contains values.

For example:

X++
if (batchSet.elements())
{
    // Process batches
}

Checking the collection before performing additional work can make the intent of the code clearer.


Using collections when a query would be better#

Collections are useful, but don't load thousands of database records into a collection just so you can filter them in X++.

If SQL can perform the filtering, joining, grouping, or aggregation for you, let the database do it.

For example, this:

X++
while select inventTrans
    where inventTrans.ItemId == itemId
{
    // Process
}

is generally better than selecting every InventTrans record into a collection and then checking the ItemId in X++.

Collections should support your processing logic, not replace proper queries.


Related articles#

  • X++ Classes Explained
  • X++ Methods Explained
  • Containers in X++
  • Arrays in X++
  • Enums in X++
  • Static vs Instance Methods in X++

Conclusion#

List, Set, and Map solve slightly different problems, and picking the right one makes X++ code much easier to understand.

The simple rule I use is:

List: I need multiple values.

Set: I need unique values.

Map: I need a key that points to a value.

You will still run into containers constantly in D365FO, and sometimes a container is exactly what you need. But when you are dynamically collecting, looking up, or deduplicating data, the X++ collection classes are usually the better tool.

Don't overthink it. Pick the collection that matches the data you are working with and keep the code easy to follow.

Was this article helpful?Sign in to vote

Missing something?

Suggest a topic you'd like to see covered next. The most requested topics help shape future XppForge documentation.

Sign in to suggest a topic