Mastering X++ Containers in D365 F&O

This blog explores how to leverage X++ containers in banking and financial scenarios with practical examples that you can implement in your own solutions. What Are X++ Containers?
What Are X++ Containers?
Containers in X++ are versatile data structures that can store multiple values of different types in a single variable. Unlike arrays or lists that typically store elements of the same type, containers can mix strings, integers, dates, and even record IDs in one collection.
Key characteristics:
Heterogeneous storage (multiple data types)
Dynamic sizing (grow and shrink as needed)
One-based indexing (first element is at position 1)
Built-in manipulation functions with the "con" prefix
Internal Implementation Details
Memory Management: X++ containers use a dynamically allocated memory block that grows as needed. When a container exceeds its current capacity, the runtime allocates a larger block and copies the existing elements.
Type Handling: Containers maintain type information for each element. When you retrieve a value with
conPeek()
, the runtime performs an implicit type conversion if necessary.Serialization Support: Containers can be easily serialized and deserialized, making them ideal for storing in database fields or passing between tiers in the D365 architecture.
Value Semantics: Containers use value semantics (not reference semantics), meaning when you assign one container to another, a complete copy is made:
container c1 = [1, 2, 3]; container c2 = c1; // Creates a complete copy conPoke(c1, 1, 99); // Only affects c1, not c2
- AOT Representation: In X++ metadata, containers are represented using the
Array
type with special handling for heterogeneous contents.
- AOT Representation: In X++ metadata, containers are represented using the
Example : Banking Transaction Batch Processing
//Plz Treat this as Example Code , Not Production Standard
public void processBankTransactions(BankAccountTable _bankAccount)
{
container transactionBatch;
container currentTransaction;
BankTransactionTable bankTrans;
// Build a batch of pending transactions
while select * from bankTrans
where bankTrans.BankAccountId == _bankAccount.RecId &&
bankTrans.Status == BankTransactionStatus::Pending
{
// Store each transaction as a container within our batch container
currentTransaction = [bankTrans.RecId, // Transaction ID
bankTrans.TransDate, // Transaction date
bankTrans.Amount, // Amount
bankTrans.TransactionType, // Type
bankTrans.Reference]; // Reference
transactionBatch += currentTransaction;
}
// Process each transaction in the batch
for (int i = 1; i <= conLen(transactionBatch); i++)
{
currentTransaction = conPeek(transactionBatch, i);
try
{
this.processTransaction(currentTransaction);
this.updateTransactionStatus(conPeek(currentTransaction, 1),
BankTransactionStatus::Processed);
}
catch (Exception::Error)
{
this.updateTransactionStatus(conPeek(currentTransaction, 1),
BankTransactionStatus::Error);
// Log error details
}
}
}
Another Example : Bank Reconciliation Helper
When reconciling bank statements, you often need to temporarily store matches between system records and bank statement lines:
//Plz Treat this as Example Code , Not Production Standard
public container findPotentialMatches(BankStatementLine _statementLine)
{
container matches;
container matchDetails;
LedgerJournalTrans journalTrans;
BankAccountTrans bankTrans;
// Look for matches in journal entries
while select * from journalTrans
where journalTrans.AccountType == LedgerJournalACType::Bank &&
journalTrans.Account == _statementLine.BankAccountId &&
journalTrans.AmountCurDebit == _statementLine.Amount &&
journalTrans.TransDate == _statementLine.TransactionDate
{
matchDetails = ['JournalEntry', // Match type
journalTrans.RecId, // Record ID
journalTrans.Voucher, // Voucher
journalTrans.AmountCurDebit,// Amount
journalTrans.TransDate]; // Date
matches += matchDetails;
}
// Look for matches in bank transactions
while select * from bankTrans
where bankTrans.BankAccountId == _statementLine.BankAccountId &&
bankTrans.Amount == _statementLine.Amount &&
bankTrans.ValueDate == _statementLine.TransactionDate
{
matchDetails = ['BankTransaction', // Match type
bankTrans.RecId, // Record ID
bankTrans.Reference, // Reference
bankTrans.Amount, // Amount
bankTrans.ValueDate]; // Date
matches += matchDetails;
}
return matches;
}
Advanced Container Techniques in Banking Applications
Beyond the basic examples, here are some advanced techniques that can take your container usage to the next level in banking applications:
1. Functional Container Operations
You can implement functional programming techniques with containers:
//Plz Treat this as Example Code , Not Production Standard
public container filterContainer(container _source, IdentifierName _predicateFunctionName)
{
container result;
DictClass dictClass = new DictClass(0); // Properly initialize with an iterator ID
for (int i = 1; i <= conLen(_source); i++)
{
// Call the predicate function to determine if element should be kept
if (dictClass.callStatic(_predicateFunctionName, _source, i))
{
result += conPeek(_source, i);
}
}
return result;
}
// Example usage for transaction filtering
public static boolean isHighValueTransaction(container _transactions, int _index)
{
container transaction = conPeek(_transactions, _index);
real amount = conPeek(transaction, 3);
return amount > 10000; // Consider transactions over 10,000 as high-value
}
// Usage in code
container highValueTransactions = filterContainer(transactionBatch,
identifierStr(isHighValueTransaction));
Performance Analysis: Big O for X++ Containers
Understanding the time complexity of container operations is crucial when working with large datasets in banking applications. Here's a comprehensive analysis of X++ container operations:
Operation | Function | Time Complexity | Description |
Access | conPeek(container, index) | O(1) | Direct access by index is constant time |
Update | conPoke(container, index, value) | O(1) | Updating an existing element is constant time |
Length | conLen(container) | O(1) | Getting container size is constant time |
Add | container += value | O(1)* | Adding to the end of a container is amortized constant time |
\Note: While appending is O(1) amortized, individual operations may occasionally trigger a reallocation, which is O(n).*
Search Operations
Operation | Function | Time Complexity | Description |
Find | conFind(container, value) | O(n) | Linear search through all elements |
Find with Predicate | Custom loop with condition | O(n) | Must check each element until match found |
Modification Operations
Operation | Function | Time Complexity | Description |
Insert | conIns(container, index, value) | O(n) | Requires shifting elements after insertion point |
Delete | conDel(container, index) | O(n) | Requires shifting elements after deletion point |
Concatenate | conCat(container1, container2) | O(n) | Where n is the size of the second container |
Memory Complexity
Memory usage is another critical aspect when dealing with financial data:
Operation | Memory Overhead | Notes |
Container Creation | O(1) | Initial allocation |
Element Addition | O(n) in worst case | When capacity is exceeded, reallocation occurs |
Container Copy | O(n) | Full copy is made |
Nested Containers | O(sum of all elements) | Each container has its own allocation |
Large Dataset Considerations
For very large datasets (e.g., processing thousands of bank transactions), consider these optimizations:
Batch size control: Limit container sizes to avoid memory issues
Database filtering: Filter data at the database level before adding to containers
Selective storage: Store only necessary fields in containers
Memory management: Clear containers when no longer needed:
container myContainer; // ... operations ... myContainer = []; // Clear container
Comparison with Other X++ Collections
Best Practices for X++ Containers
Document container structure: Always document what each position in your container represents.
Consider type safety: For complex scenarios, weigh containers against structured types like classes.
Use constants for indexes: Define constants for container positions to avoid "magic numbers":
#define.CUST_PROFILE_ACCOUNT(1)
#define.CUST_PROFILE_NAME(2)
#define.CUST_PROFILE_BANK_ACCOUNT(3)
// Usage:
str accountNum = conPeek(profile, #CUST_PROFILE_ACCOUNT);
Validate containers: Check container length before accessing elements.
Nested containers: For complex data, consider containers within containers as seen in the transaction example.
Performance monitoring: For large banking operations, monitor memory consumption and processing time when using large containers.
Boundary checking: Always check container boundaries before accessing elements:
if (conLen(container) >= index) { value = conPeek(container, index); }
Container reuse: Consider clearing and reusing containers for repetitive operations to reduce memory allocations:
container reusableContainer;
// In a loop reusableContainer = []; // Clear before reuse // Add new data...
Encapsulate container operations: Create utility methods for common container operations to improve code readability and maintainability.
Balance with other data structures: Use the right tool for the job—containers for heterogeneous collections, Lists for homogeneous collections with frequent insertions, Maps for key-value lookups.
Conclusion
X++ containers provide a flexible and powerful way to handle diverse data types in Dynamics 365 F&O banking modules. They shine in scenarios involving temporary storage, parameter passing, and working with heterogeneous data. While they shouldn't replace proper class design for complex business objects, they offer an elegant solution for many everyday development challenges.
Remember that containers are a tool in your arsenal—not a silver bullet. When used appropriately, they can significantly improve code readability and maintainability while maintaining good performance. But they should be complemented with proper object-oriented design for complex domain models and data structures.
Subscribe to my newsletter
Read articles from Challa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
