I have a simple function that converts a number to a string (similar to how Excel column numbers 1..n
can be converted into column names A-ZZZ
). As I use this a lot, I have removed local variables as a speed optimization and only work with the Result
directly.
When I compile, I get these 2 warnings:
W1047 Unsafe code 'String index to var param'
W1068 Modifying strings in place may not be supported in the future
Here is the shortened function:
function Idx2Code_(Idx: integer): string;
begin
SetLength(Result, 3); // init Result size
if Idx <= 26 then
begin
// single char: A-Z
Result := Chr(64 + Idx);
SetLength(Result, 1);
end
else if Idx <= 676 then
begin
// 2 chars codes: BA-ZZ
Result[1] := Chr((Idx div 26) + 64 + 1); // <- warnings for this line
Result[2] := Chr((Idx mod 26) + 64); // <- warnings for this line
SetLength(Result, 2);
end
else
begin
// 3 chars codes: BAA-ZZZ
Result[1] := Chr((Idx div 676) + 64 + 1); // <- warnings for this line
Result[2] := Chr(((Idx mod 676) div 26) + 64 + 26); // <- warnings for this line
Result[3] := Chr((Idx mod 26) + 64 + 26); // <- warnings for this line
end;
end;
I can use local variables, but its slower, which I do not want.
I don't want to use code that might become "obsolete" in a future Delphi version.
How can I solve the warnings, but keep the simplicity and speed as much as possible?
In mobile development, direct indexing of Delphi's string
type is 0-based by default (this can be disabled with the {$ZEROBASEDSTRINGS OFF}
directive).
In desktop development, direct indexing is still 1-based by default, but may become 0-based in the future.
Because of this discrepancy between platforms, direct indexing of string
is discouraged moving forward, thus the warning messages. To avoid this, there is a 0-based TStringHelper.Chars[]
property available on all platforms. However, this property is read-only, so you cannot use it to modify the content of a string
(at one point, Embarcadero was considering making string
immutable, but then later decided against it, but the warning remains).
In this situation, you will have to either:
disable the warnings, and use platform-appropriate indexing. You can use Low(string)
to help you with that:
{$WARN UNSAFE_CODE OFF}
{$WARN IMMUTABLE_STRINGS OFF}
function Idx2Code_(Idx: integer): string;
begin
if Idx <= 26 then
begin
// single char: A-Z
SetLength(Result, 1);
Result[Low(string)] := Chr(64 + Idx);
end
else if Idx <= 676 then
begin
// 2 chars codes: BA-ZZ
SetLength(Result, 2);
Result[Low(string)] := Chr((Idx div 26) + 64 + 1);
Result[Low(string)+1] := Chr((Idx mod 26) + 64);
end
else
begin
// 3 chars codes: BAA-ZZZ
SetLength(Result, 3);
Result[Low(string)] := Chr((Idx div 676) + 64 + 1);
Result[Low(string)+1] := Chr(((Idx mod 676) div 26) + 64 + 26);
Result[Low(string)+2] := Chr((Idx mod 26) + 64 + 26);
end;
end;
{$WARN IMMUTABLE_STRINGS DEFAULT}
{$WARN UNSAFE_CODE DEFAULT}
use a TStringBuilder
:
uses
System.SysUtils;
function Idx2Code_(Idx: integer): string;
var
SB: TStringBuilder;
begin
SB := TStringBuilder.Create(3);
try
if Idx <= 26 then
begin
// single char: A-Z
SB.Append(Chr(64 + Idx));
end
else if Idx <= 676 then
begin
// 2 chars codes: BA-ZZ
SB.Append(Chr((Idx div 26) + 64 + 1));
SB.Append(Chr((Idx mod 26) + 64));
end
else
begin
// 3 chars codes: BAA-ZZZ
SB.Append(Chr((Idx div 676) + 64 + 1));
SB.Append(Chr(((Idx mod 676) div 26) + 64 + 26));
SB.Append(Chr((Idx mod 26) + 64 + 26));
end;
Result := SB.ToString;
finally
SB.Free;
end;
end;
Alternatively, TStringbuilder
does have its own Chars[]
property that is writable:
uses
System.SysUtils;
function Idx2Code_(Idx: integer): string;
var
SB: TStringBuilder;
begin
SB := TStringBuilder.Create(3);
try
if Idx <= 26 then
begin
// single char: A-Z
SB.Length := 1;
SB[0] := Chr(64 + Idx);
end
else if Idx <= 676 then
begin
// 2 chars codes: BA-ZZ
SB.Length := 2;
SB[0] := Chr((Idx div 26) + 64 + 1);
SB[1] := Chr((Idx mod 26) + 64);
end
else
begin
// 3 chars codes: BAA-ZZZ
SB.Length := 3;
SB[0] := Chr((Idx div 676) + 64 + 1);
SB[1] := Chr(((Idx mod 676) div 26) + 64 + 26);
SB[2] := Chr((Idx mod 26) + 64 + 26);
end;
Result := SB.ToString;
finally
SB.Free;
end;
end;
See Embarcadero's documentation for more information:
Migrating Delphi Code to Mobile from Desktop: Use 0-Based Strings