This week I presented a session about .Net in the AL language at Directions EMEA 2019 in Vienna. And of course, I paid attention to the types in AL that represent .Net types, like TextBuilder, List, Dictionary, Http and JSON types. During the preparation of that session, I found a comment in the Microsoft documentation, below the documenation about HTTP, JSON etc., that I thought it could use some explanation:
For performance reasons all HTTP, JSON, TextBuilder, and XML types are reference types, not value types. Reference types holds a pointer to the data elsewhere in memory, whereas value types store its own data.
In this quote from the Microsoft documentation, the List and Dictionary type are not mentioned, but the same applies to these types.
The question is of course: what does it mean that they are reference types? How does it impact your code? For .Net developers, it’s quite obvious, they are used to working with reference types, but C/AL developers certainly are not so used to the concept. So if you move to AL code and use these new variable types, then you might run into unexpected behaviour. I though it would make sense to shine some light on it.
A reference type means that the variable doesn’t hold a value, but a reference to an instance of an object in memory. When you pass such a variable as a parameter to a function, then you are passing a reference to the object in memory. The receiving function works directly on that object in memory through the reference it received. Let me explain it with an example:
codeunit 50100 ListDemo { procedure Demo() var Cust: Record CUstomer; NameList: List of [Text]; Name: Text; begin GetNameList(Cust, NameList); foreach Name in NameList do Message(Name); end; procedure GetNameList(Cust: Record Customer; NameList: List of [Text]) begin if Cust.FindSet() then repeat NameList.Add(Cust.Name); until Cust.Next() = 0; end; }
The NameList variable is not passed by var to the function GetNameList. However, after the function call on line 9, the variable NameList will contain values. This is possible because the NameList parameter just holds a reference and the code on line 19 is working on the very same object in memory.
Let’s add one line of code that will destroy all of this. We add a Clear() command to the beginning of the function GetNameList.
codeunit 50100 ListDemo { procedure Demo() var Cust: Record CUstomer; NameList: List of [Text]; Name: Text; begin GetNameList(Cust, NameList); foreach Name in NameList do Message(Name); end; procedure GetNameList(Cust: Record Customer; NameList: List of [Text]) begin Clear(NameList); if Cust.FindSet() then repeat NameList.Add(Cust.Name); until Cust.Next() = 0; end; }
After the call, the variable NameList in the first function does not contain any value at all. This is the kind of situation that can really drive you nuts. If you debug this, you will see values being added to the parameter on line 20, but when you get back into the calling function the NameList is suddenly empty. While the previous example perfectly worked, this one doesn’t.
The reason is that the Clear() command removes the reference from the parameter. It doesn’t point to the original object instance anymore. And because AL types are automatically instantiated (we don’t have to call a constructor) a new instance will be created for us and the parameter will now hold a reference to that new object. However, the parameter was not passed in by var, so the calling function will not receive the new reference from the parameter. It will still hold the reference to the original object, but nothing happened on that original object. The function GetNameList worked on the newly instantiated object.
Of course, the solution is simple: add var to the parameter, and it works again.
codeunit 5010 ListDemo { procedure Demo() var Cust: Record CUstomer; NameList: List of [Text]; Name: Text; begin GetNameList(Cust, NameList); foreach Name in NameList do Message(Name); end; procedure GetNameList(Cust: Record Customer; var NameList: List of [Text]) begin Clear(NameList); if Cust.FindSet() then repeat NameList.Add(Cust.Name); until Cust.Next() = 0; end; }
Hope this makes a little bit sense to you!