Passing arrays (dynamic or static) to methods/procedures/functions with open array parameters
,
declaration can look like this:
procedure WorkWithArray( const anArray : array of Integer);
(* or procedure WorkWithArray( var anArray : array of Integer); *)
var
i : Integer;
begin
for i := Low(anArray) to High(anArray) do
begin
// Do something with the "open array" anArray
WriteLn(anArray[i]);
end;
end;
...
var
staticArray : array[0..2] of Integer;
dynArray : array of integer;
dynArrayG : TArray<Integer>;
begin
SetLength(dynArray,10);
SetLength(dynArrayG,10);
WorkWithArray(staticArray); // Using a static array
WorkWithArray(dynArray); // Using a dynamic array
WorkWithArray(dynArrayG); // Using a dynamic generic array
...
end;
Passing arrays like this is a very common idiom used throughout the Delphi RTL, including some very optimized functions/procedures for handling arrays of data.
Suppose we need to call WorkWithArray
with a subrange of our arrays. We can then use the intrinsic Slice()
function.
First without an offset, starting with first index:
Type
// Helper declarations
TIntLongArray = array[0..MaxInt div SizeOf(Integer) - 1] of integer;
PIntLongArray = ^TIntLongArray;
WorkWithArray(Slice(staticArray,2)); // No type cast needed for static arrays
WorkWithArray(Slice(PIntLongArray(@dynArray)^,2));
WorkWithArray(Slice(PIntLongArray(@dynArrayG)^,2));
Note: dynamic arrays does not fit directly into the Slice()
function,
see "Slice does not work with dynamic arrays"
.
So the workaround method with type casting has to be used.
What if we want to work with a subrange not starting from the first element?
Doable as well:
WorkWithArray(Slice(PIntLongArray(@staticArray[1])^,2));
WorkWithArray(Slice(PIntLongArray(@dynArray[1])^,2));
WorkWithArray(Slice(PIntLongArray(@dynArrayG[1])^,2));
Note : the sum of the offset and the slice must not exceed the element count of the array.
I know that using Copy(myArray,x1,x2) could be used in cases where the input is declared as a const, but this will make a copy of the the array, and is ineffiecient for large arrays. (With risk of stack overflow as well).
Finally, my question:
While this demonstrates a way to pass a subrange of an array by reference with a start index and a length specifier, it looks a bit awkward. Are there better alternatives and if so how?
Updated See a bit down for a generics solution.
Here is an alternative that encapsulates the type cast needed for the offset inside a function, which resides in an advanced record declared as a class function. Besides hiding the type cast, the offset is range checked against the high index of the array.
More types can be added if needed.
Note : the sum of the offset and the slice must not exceed the element count of the array.
Example calls:
While this looks better, I'm still not convinced this is the optimal solution.
Update
When writing the above solution, I had a generics solution as the ultimate goal.
Here is an answer that utilizes anonymous methods and generics to implement a
Slice(anArray,startIndex,Count)
method that can be used with both static and dynamic arrays.A straight generics solution would rely on range checking be turned off at every placed where it was used, and that would not be a pretty solution. The reason is that
SizeOf(T)
could not be used to declare a static array type of maximum size:So we would have to use:
instead. And this triggers the range check when it is on, for index > 0.
Solution
But the problem could be solved by another strategy,
callbacks
or a more modern terminology would beInversion of Control
(IoC) orDependeny Injection
(DI). The concept is best explained with, "Don't call me, we call you".Instead of using a direct function, we pass the operational code as an anonymous method together with all parameters. Now the range check problem is contained within the
Slice<T>
frame.Here are some example use cases:
How about avoid open arrays and slice and use something like this ?