A D365 set is a unique, unordered collection that contains elements of all the same data type. This is similar to a mathematical set. The allowed values are all of the possible types of the Types base enum. If you try to add a duplicate value, it will not be added. Sets are very useful in keeping track of which records you have and have not processed inside a ‘while loop’. Most of all, there are several helpful methods in the D365 set class that make working with unique lists way easier.
Types of Collection Classes
Before going into the details of a D365 set, remember that the set class is one of the collection classes.
As an overview, there are five different types of D365 collection classes defined in the base Microsoft code. To explain, these special types of classes provide additional functionality that helps developers. They help store, find, and iterate through lists of objects. They each work a little bit differently.
- Array – The Array class must hold values of the same data type. They are often used to hold multiple objects or records.
- List – Used to store sequential values, which are all of the same data type. There are methods to make it easy to add values at the beginning or the end of the list.
- Set – Every value added to a set must be of the same data type. However, values are not stored in the order in which you add them. Instead, they are optimized for checking if the same value already exists in the set. If you try to add a value that already exists, the duplicate value will not be added. This class is great when you are working with a unique set of values.
- Map – Used to store a unique key and an associated value. Similar to a Dictionary in C#. All keys must have the same data type. Additionally, all values must have the same data type.
- Struct – Similar to a class, these objects store multiple values relating to a specific entity together. For example, you might store a customer’s name, birthday, and address as a single item. A struct uses string indexes. A container, which is similar, uses numeric indexes.
To learn more, click on the above links for a detailed explanation of each D365 collection class.
Setup
To test the code shown in the sections below, first create a new project in Visual Studio.
Second, create a runnable class (job). To do this, right-click on the project and select Add>New Item. Then select ‘Runnable Class (Job)‘, and give the job a name. I named mine tutorialList.

Third, copy the code sections below into the ‘main‘ method.

Fourth, right-click on your runnable class (job) and select ‘Set as Startup Object‘. This will enable it to run when you click the ‘Start‘ button.

Finally, click the ‘Start‘ button in Visual Studio. This will first build the code. Then, if there are no errors, this will open a browser, navigate to the D365 runnable class (job), and run the code. View the info message output to see the results.

Defining A Set
First, when a D365 set is created, the data type of the values stored in the list is specified. This cannot be changed.
// Create a set of integers
Set set = new Set(Types::Integer);
Notice, the Types enum is passed into the constructor. These are the various options that the Types enum has.
- AnyType
- BLOB
- Class
- Container
- Date
- Enum
- Guid
- Int64
- Integer
- RString
- Real
- Record
- String
- Time
- UserType
- UtcDateTime
- VarArg
- VarString
- void
Typically, I always pass in a Types enum value. However, optionally use a function to get the value from an Extended Data Type. See this example.
// Create a list of whatever data type the SalesId extended data type uses. Hint: It is of type String.
Set set = new Set(extendedTypeNum(SalesId));
// The above line would be the same as this for the Extended Data Type 'SalesId'.
Set setString = new Set(Types::String);
Add Values To A Set
Second, after instantiating a D365 set, the next step is to add values to the set. Use the ‘add‘ method to add values to the end of the set. See this example where Strings are added to a set.
// Create a set of Strings
Set set = new Set(Types::String);
//Add numbers to the set.
set.add("A");
set.add("B");
set.add("C");
Note, the values will not necessarily be stored in the D365 set in the order you add them. If you need a collection that is ordered, use a List. Instead, sets should be used when you need a unique collection.
Loop Through A Set
Third, now that there are values in the D365 set, loop through and print out the values. In order to loop through a set, we need to use a class named SetEnumerator. See the example below.
// Create a set of Strings
Set set = new Set(Types::String);
SetEnumerator setEnumerator;
str value;
//Add numbers to the set.
set.add("A");
set.add("B");
set.add("C");
//Get the set enumerator from the set object.
setEnumerator = set.getEnumerator();
while (setEnumerator.moveNext())
{
value = setEnumerator.current();
info(strfmt("%1",value));
}
Start by declaring a variable of type SetEnumerator. Next, call the method ‘getEnumerator()‘ on the set to get an instantiated set enumerator. Then, set the result to the set enumerator variable.
Then, create a ‘while‘ loop. Inside the while loop, call the method ‘moveNext()‘ on the set enumerator. If there is a next value in the set, it will move to it and return true. If there are no more values in the set, this method will return false. Note that this will cause the inside of the while loop to no longer be executed.
Next, call the method ‘current()‘ to get the value out of the set and store it in a variable. In this case, we know that all values in the set are strings.
Finally, use the ‘info‘ and ‘strfmt()‘ methods to print out the message.
The result should look like this:

Remove Values From Set
Unlike a list, an element can be removed from a D365 set directly without an iterator. Use the ‘delete‘ method to remove values from the set.
// Create a set of Strings
Set set = new Set(Types::String);
SetEnumerator setEnumerator;
str value;
//Add numbers to the set.
set.add("A");
set.add("B");
set.add("C");
//remove a value from the set if it exists.
set.remove("A");
//Get the set enumerator from the set object.
setEnumerator = set.getEnumerator();
while (setEnumerator.moveNext())
{
value = setEnumerator.current();
info(strfmt("%1",value));
}
The result looks like this:

Check If A Value Exists In A Set
In my opinion, the most useful method on the D365 set is the ‘In‘ method. It is used to check if a value already exists in the set. Use it in a loop to help ensure you only process a unique record once.
Oftentimes, you already know by the uniqueness of a table that the results will be unique. Or, use a ‘group by’ to ensure unique results. However, there are some cases where you can’t write the ‘while loop’ in a way that will result in unique values. In those cases, use a set. See this example from the DocuHistoryForm in D365.
/// <summary>
/// Executes the query to retrieve distinct and latest record of each DocuRefRecId.
/// </summary>
public void executeQuery()
{
DocuHistory docuHistory;
container rangeValueBuilder;
// If this form was navigated to from the document history admin form,
// only look for the one selected attachment to be displayed. Otherwise,
// look for all the attachments linked to the current record.
if (isComingFromAdminForm)
{
DocuHistory docuHistoryLatest;
select firstonly RecId from docuHistory
order by docuHistory.CreatedDateTime desc, docuHistory.RecId desc
where docuHistory.DocuRefRecId == queryDocuRefRecId;
rangeValueBuilder += docuHistory.RecId;
}
else
{
Set docuRefRecIdUniques = new Set(Types::Int64);
while select RecId, CreatedDateTime, DocuRefRecId from docuHistory
order by docuHistory.CreatedDateTime desc, docuHistory.RecId desc
where docuHistory.RefRecId == queryRecId && docuHistory.RefTableId == queryTableId
{
if (!docuRefRecIdUniques.in(docuHistory.DocuRefRecId))
{
docuRefRecIdUniques.add(docuHistory.DocuRefRecId);
rangeValueBuilder += docuHistory.RecId;
}
}
}
QueryBuildRange rangeDocuRefRecId = this.queryBuildDataSource().rangeField(fieldNum(DocuHistory, RecId));
// If record exists in the table DocuHistory, generate range expression.
if (conLen(rangeValueBuilder) > 0)
{
// Set query range value to created range expression.
rangeDocuRefRecId.value(con2StrUnlimited(rangeValueBuilder, ','));
}
else
{
// No values found.
rangeDocuRefRecId.value(queryValue(naInt()));
}
super();
}
Notice, the code uses the set to ensure it is putting together a unique list of RecId’s. Then, it uses those RecId’s to filter the results in a Datasource.
Helper Methods
Additionally, there are other methods in the D365 set class that help with typical mathematical operations. Specifically, see difference, intersection, and union.
| difference(_set1, _set2) | Returns items that are in _set1 but are not in _set2 |
| intersection(_set1, _set2) | Returns a new set that contains only elements present in both _set1 and _set2 |
| union(_set1, _set2) | Returns a new set that contains all the elements of _set1 and _set2. |
Refer to the diagram below, which illustrates the inputs Set1 and Set2, as well as the resulting set after each operation.

Count of Values
Furthermore, there may be times you need to know the number of values in a D365 set. Use the ‘elements‘ method to return the number of values in the list.
Set set = new Set(Types::Integer);
set.add(1);
set.add(2);
set.add(3);
set.add(3);
set.add(3);
set.add(3);
info(strfmt("There are %1 values in the set.", set.elements()));
Notice, even when I try to add the number 3 to the set multiple times, the set will only add it once.
Iterators vs. Enumerators
Interestingly, there are two ways of looping through a D365 set. Use the SetEnumerator class or use the SetIterator class.
As a best practice, you should use the SetEnumerator. It requires less code and performs slightly better.
SetEnumerators cannot be modified while traversing through the values. Whereas a SetIterator can insert or delete elements during traversal. Therefore, there may be rare cases when you need to use a SetIterator. See this example for the different methods involved.
// Create a set of Strings
Set set = new Set(Types::String);
SetIterator setIterator;
str value;
//Add numbers to the set.
set.add("A");
set.add("B");
set.add("C");
//Get the set iterator from the set object.
setIterator = new SetIterator(set);
// Prints A, B, C.
while (setIterator.more())
{
info(strfmt("%1",setIterator.value()));
setIterator .next();
}
Notice, the SetIterator uses the method more(), instead of moveNext() in the while loop. Additionally, the more() method only returns whether there are more elements in the set. It does not move to the next value in the list. Therefore, a call to next() is needed inside the loop to move to the next value. This approach requires one extra line of code compared to SetEnumerator.
Pack and Create
Moreover, a D365 set can be packed into a container object and unpacked. This is helpful when working with certain batch jobs that have existing functionality dependent on containers. See this example.
Set set = new Set(Types::Real);
Set newSet;
container c;
// Fill set
set.add(1.61803);
set.add(3.14159);
// Pack the set into a container
c = set.pack();
// Create a new set from the packed set
newSet = Set::create(c);
Microsoft Documentation
In order to learn more about how to use a D365 set, check out the Microsoft documentation here. On this page, you can see all of the available helper methods on the class.
I also want to thank Tim Fluharty, whom I am learned a great deal from, and who provided information for this series of articles.
Conclusion
Ultimately, the ability to store unique values in a D365 set and then later check if a value exists is invaluable. Without the methods on the set, a developer would need to write several lines of code to accomplish the same thing. They would need to write a loop and an if-statement inside to compare each value and determine if a value already exists. Using a set is very helpful for keeping track of a unique collection of values.
Leave a Reply