Everybody know that in Delphi there are four types of parameters:
- Standard parameter without any prefix;
- VAR parameters;
- CONST parameters:
- with [Ref] decoration;
- without decoration;
- OUT parameters.
But let me just refresh your memory a bit 🙂
Standard
When no prefix is defined for the parameter the compiler passes it by value (simple types) or by reference (string, fixed array, dynamic arrays and records) with copy on write technique. And we still have an ability to assign a value to the parameter inside of the function/procedure, but that only affects the value in the stack which was pushed there before.
procedure Test(X: Integer);
begin
X := 10;
end;
var
X: Integer = 1;
begin
Test(X);
Writeln(X); // Outputs 1
end.
When we define such a parameters we can pass the constants as well.
Var
Var parameters are passed to the routine always by reference. So once the value is assigned to the parameter it changes the source instance/memory block. You should use the var parameters only when you actually modify the value of the variable you pass to the routine. When you access and modify the fields of the object or interface you do not need to mark parameter as var. When other developer looks at your code and sees the var prefix he will be confused – as var means that the value can be changed from inside of the routine! And also once you define the parameter as var you cannot pass the constants to the routine.
procedure Test(var X: Integer); begin X := 10; end; var X: Integer = 1; begin Test(X); Test(10); // Not allowed Writeln(X); // Outputs 10 end.
Out
The out parameters are the same as var parameters except that the out prefix indicates that the input value is not respected (for managed types, for simple types that will be just assumption as the value is not set to default) and the parameter value is initialized with the default value for managed types (such as string, dynamic array or interface).
procedure TestX(out X: Integer); begin Writeln(X); // Will output 1 X := 10; end; procedure TestS(out X: String); begin Writeln(X); // Will output empty string X := 'Inside'; end; var X: Integer = 1; S: String = 'Initial'; begin TestX(X); TestX(10); // Not allowed Writeln(X); // Outputs 10 TestS(S); Writeln(S); // Outputs 'Inside' end.
Const
The const parameters are almost the same as standard parameters, but have some differences. It is not allowed to set the value to the const parameter inside of the function/procedure either pass it as a var/out parameter to other routines. The const parameters are passed by value (simple types) or by reference (string, fixed array, dynamic arrays and records. However this depends on the size of the parameter. If the size of the parameter is bigger than a certain amount of bytes then it is passed by reference instead) but without copy on write – remember, those are marked read-only and we cannot assign values to them or pass them to var parameters of other routines. You can specify [Ref] before the parameter to force the compiler to pass the value by reference – so it will be the same as var parameter except that the routine cannot modify the value directly via assignment. But be warned that const [Ref] parameters cannot be captured by the anonymous methods (lambdas) – as it is with “Result” in functions.
procedure Test(const X: Integer); begin Writeln(X); X := 10; // Not allowed Readln(X); // Not allowed end; var X: Integer = 1; begin Test(X); Writeln(X); // Outputs 1 end.
But there is one more special thing for const parameters with reference counted types – the reference count is not affected when passing the variables to the const parameters. So if we pass an interface variable to a normal parameter on enter to the routine the reference count will be incremented and on leaving it – decremented. But that does not happen with const parameters! See a demo project in the end of the post.
So there is the demo project demonstrating the reference count management:
program ParameterTypes; {$APPTYPE CONSOLE} type ITest = interface ['{D200C91B-3D4D-45AB-A1E0-A803C6A03292}'] end; TTest = class(TInterfacedObject, ITest) private function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; end; function TTest._AddRef: Integer; begin Writeln(FRefCount + 1); Result := inherited; end; function TTest._Release: Integer; begin Writeln(FRefCount - 1); Result := inherited; end; procedure TestStandard(X: ITest); begin Writeln('Inside standard'); end; procedure TestVar(var X: ITest); begin Writeln('Inside var'); end; procedure TestConst(const X: ITest); begin Writeln('Inside const'); end; procedure TestConstRef(const [Ref] X: ITest); begin Writeln('Inside const'); end; procedure TestOut(out X: ITest); begin Writeln('Inside out'); end; var X: ITest; begin Writeln(StringOfChar('-', 40)); Writeln('Creating and calling with standard parameter type:'); X := TTest.Create; TestStandard(X); X := nil; Writeln(StringOfChar('-', 40)); Writeln('Creating and calling with var parameter type:'); X := TTest.Create; TestVar(X); X := nil; Writeln(StringOfChar('-', 40)); Writeln('Creating and calling with const parameter type:'); X := TTest.Create; TestConst(X); X := nil; Writeln(StringOfChar('-', 40)); Writeln('Creating and calling with const [Ref] parameter type:'); X := TTest.Create; TestConstRef(X); X := nil; Writeln(StringOfChar('-', 40)); Writeln('Creating and calling with out parameter type:'); X := TTest.Create; TestOut(X); X := nil; Writeln(StringOfChar('-', 40)); Readln; end.
The output of this program is following:
Inside standard 1 0 ---------------------------------------- Creating and calling with var parameter type: 1 Inside var 0 ---------------------------------------- Creating and calling with const parameter type: 1 Inside const 0 ---------------------------------------- Creating and calling with const [Ref] parameter type: 1 Inside const 0 ---------------------------------------- Creating and calling with out parameter type: 1 0 Inside out ----------------------------------------
The source is available on GitHub.
Further reading:
You can’t pass const parameters as var or out parameters to any other routine, not just to nested routines.
LikeLike
Did I wrote it differently? Or why do you repeat what is said in the post?
LikeLike
With nested I meant nested calls not sub-routines 🙂 sorry to be not absolutely clear. I will adjust a bit.
LikeLiked by 1 person
“The const parameters are passed by value (simple types) or by reference (string, fixed array, dynamic arrays and records)”
That is not entirely true as it depends on the size of the value. If it is small enough it is indeed being passed by value.
Here is a small example to show that: http://pastebin.com/4HALLNFm
As you can see although I used pointer access to write to the const argument it only got changed for the large record because that one was in fact being passed as reference,
LikeLike
Thank you very much for a feedback. I will extend the post with this information onve I have time!
LikeLike
Adjusted the post to mention this behavior. Thanks.
LikeLike