Does Pass by Value Work in Business Central AL?

The difference between pass-by-reference and pass-by-value is that modifications made to arguments passed by reference in the called function affect the calling function. Therefore, modifications to arguments passed in by value in the called function cannot affect the calling function.

 

And it’s like that in any programing language, right? Well, no, not in every case with Business Central AL. Using a Variant Data Type and Record Ref’s has a little bug. So, let’s check it out.

Let’s define a RecRef and assign it to a Sales Line table, for example, and create a function that accepts input parameter Passed by the value of type Variant, and you can also try with RecordRef.

 

procedure ProcessRecRef()
  var
      RecordRefSalesLine: RecordRef;
      i: Integer;
  begin
      RecordRefSalesLine.Open(Database::"Sales Line");
      RecordRefSalesLine.FindFirst();
      Message('RecordRefSalesLine is ' + Format(RecordRefSalesLine.RecordId, 0, 1) + ' 
Before Pass to function PassRecRefToDoSomethingWithoutDataTypeManagementParameterAsVariant');
      // not passed as var ParameterRecordRefSalesLine
      PassRecRefToDoSomethingParameterAsVariant(RecordRefSalesLine);
      Message('RecordRefSalesLine is ' + Format(RecordRefSalesLine.RecordId, 0, 1) + ' 
After Pass to function PassRecRefToDoSomethingWithoutDataTypeManagementParameterAsVariant');
      //RecordRefSalesLine is Customer even it is not a var RecordRefSalesLine
      RecordRefSalesLine.Close();
    end;


  local procedure PassRecRefToDoSomethingParameterAsVariant(ParameterRecordRefSalesLine: Variant)
  var
      CustomerLocal: Record Customer;
      SourceFieldRef: FieldRef;
      SourceRecordRef: RecordRef;
  begin
      Message('RecordRefSalesLine is ' + Format(ParameterRecordRefSalesLine, 0, 1) + ' 
In Function PassRecRefToDoSomething before use DataTypeManagement.GetRecordRefAndFieldRef with ParameterRecordRefSalesLine');
      SourceRecordRef := ParameterRecordRefSalesLine;
      SourceFieldRef := SourceRecordRef.Field(2);
      Message('RecordRefSalesLine is ' + Format(ParameterRecordRefSalesLine, 0, 1) + ' 
In Function PassRecRefToDoSomething after use DataTypeManagement.GetRecordRefAndFieldRef with ParameterRecordRefSalesLine');
      //Do some processing with SourceFieldRef.Value
      //and now i want to use the same variable SourceFieldRef to do some other processing for some other record
      CustomerLocal.FindFirst();
      SourceRecordRef.Get(CustomerLocal.RecordId);
      SourceFieldRef := SourceRecordRef.Field(2);
      Message('RecordRefSalesLine becomes ' + Format(ParameterRecordRefSalesLine, 0, 1) + ' 
In Function PassRecRefToDoSomething after use DataTypeManagement.GetRecordRefAndFieldRef with Customer');
      //Variant ParameterRecordRefSalesLine becomes Customer
    end;

 

Note that SourceRecordRef and SourceFieldRef are local Variables. Therefore, assigning Field Ref and then reusing the SourceRecordRef again to get the Customer table will change the input parameter to a customer even if it is passed by value. This will happen if the input parameter is RecordRef also.

And in the documentation, it is stated that “If one RecordRef variable is assigned to another RecordRef variable, then they both refer to the same table instance.” But why is it changing pass as value to pass as a reference?

So, the best practice: is “you should Close the record ref before changing the assignment to another table”. And if you do that, the problem will be solved. Nevertheless, this does not change the fact that the Parameter pass-by-value acts like a pass-by-reference, which should never happen, in any case!

Please note that this isolated example problem originated using standard Codeunit Data Type Management.

It should be like this:

 

procedure Process()
    begin
        //How it should be
        Message('How it should be i is 1 before function and after since it is not passed as var');
        i := 1;
        PassVarToDosomething(i);
        Message(Format(i));
  //How it should be i is 1
        //How it should be
        Message('How it should be i is 1 before function and after 5 since it is passed as var');
        i := 1;
        PassVarAsVarToDosomething(i);
        Message(Format(i));
  //How it should be i is 5
    end;
    local procedure PassVarToDosomething(i: Integer)
    var
        j: Integer;
    begin
        j := 5;
        i := j;
    end;
    local procedure PassVarAsVarToDosomething(var i: Integer)
    var
        j: Integer;
    begin
        j := 5;
        i := j;
    end;

 

You can also check out Git Repository, where I played a bit to determine what is happening: SarkeSrb/RecRefBug (github.com).

Additionally, I have reported an issue to Microsoft; you can track the issue on the link; until then, be sure to close RecordRef before assigning it to another Record to avoid the problem.


procedure ProcessRecRef()
  var
      RecordRefSalesLine: RecordRef;
      i: Integer;
  begin
      RecordRefSalesLine.Open(Database::"Sales Line");
      RecordRefSalesLine.FindFirst();
      Message('RecordRefSalesLine is ' + Format(RecordRefSalesLine.RecordId, 0, 1) + '
Before Pass to function PassRecRefToDoSomethingWithoutDataTypeManagementParameterAsVariant');
      // not passed as var ParameterRecordRefSalesLine
      PassRecRefToDoSomethingParameterAsVariant(RecordRefSalesLine);
      Message('RecordRefSalesLine is ' + Format(RecordRefSalesLine.RecordId, 0, 1) + '
After Pass to function PassRecRefToDoSomethingWithoutDataTypeManagementParameterAsVariant');
      //RecordRefSalesLine is Customer even it is not a var RecordRefSalesLine
      RecordRefSalesLine.Close();
    end;

  local procedure PassRecRefToDoSomethingParameterAsVariant(ParameterRecordRefSalesLine: Variant)
  var
      CustomerLocal: Record Customer;
      SourceFieldRef: FieldRef;
      SourceRecordRef: RecordRef;
  begin
      Message('RecordRefSalesLine is ' + Format(ParameterRecordRefSalesLine, 0, 1) + '
In Function PassRecRefToDoSomething before use DataTypeManagement.GetRecordRefAndFieldRef with ParameterRecordRefSalesLine');
      SourceRecordRef := ParameterRecordRefSalesLine;
      SourceFieldRef := SourceRecordRef.Field(2);
      Message('RecordRefSalesLine is ' + Format(ParameterRecordRefSalesLine, 0, 1) + '
In Function PassRecRefToDoSomething after use DataTypeManagement.GetRecordRefAndFieldRef with ParameterRecordRefSalesLine');
      //Do some processing with SourceFieldRef.Value
      //and now i want to use the same variable SourceFieldRef to do some other processing for some other record
      CustomerLocal.FindFirst();
      SourceRecordRef.Get(CustomerLocal.RecordId);
      SourceFieldRef := SourceRecordRef.Field(2);
      Message('RecordRefSalesLine becomes ' + Format(ParameterRecordRefSalesLine, 0, 1) + '
In Function PassRecRefToDoSomething after use DataTypeManagement.GetRecordRefAndFieldRef with Customer');
      //Variant ParameterRecordRefSalesLine becomes Customer
  end;