13

I have quite a few variables declared as

var
  Something: array of XXX;
begin
  SetLength(Something, 10);
  try
    ...
  finally
    SetLength(Something, 0);
  end;
end;

To what extend is safe to have them replaced:

var
  Something: TArray<XXX>;
begin
  SetLength(Something, 10);
  try
    ...
  finally
    SetLength(Something, 0);
  end;
end;
Gad D Lord
  • 6,620
  • 12
  • 60
  • 106
  • 4
    Just a note: you don't need to use `try`/`finally` to release the memory of a dynamic array type. They're refcounted types, and the memory will be freed automatically once `Something` goes out of scope, unless they're still referenced by another variable. Either way, it behaves the same as your `SetLength` to a zero length. –  Apr 29 '14 at 20:36

2 Answers2

12

As already answered, TArray<XXX> is exactly like any other custom type defined as array of XXX. In fact, TArray<XXX> is a custom type defined as array of XXX.

That said, a custom type defined as array of XXX is not equivalent to array of XXX in the context of a procedure or function parameter. In procedure Foo(x: array of Integer), x is an open array parameter, which can accept any type of integer array. In contrast, procedure Foo(x: TArray<Integer>) takes an actual TArray<Integer> type only. You can see the difference when attempting to pass a fixed-size array, but also when attempting to pass a TDynIntegerArray (a different type, also defined as array of Integer).

So, for variables, sure, if you have array of XXX, change it to TArray<XXX> all you want. Just make sure you don't do a global search and replace.

  • +1 for differentiating between variables and method arguments. However even for the method arguments you cannot decide for one or the other unless you know and analyze the code. While you can pass a `TArray` to an `array of XXX` you cannot pass an `array of XXX` to a `TArray`. So in some cases you might make the argument of `TArray` and in other cases you might want to keep it as `array of XXX`. – Stefan Glienke Apr 29 '14 at 21:00
  • I don't agree with the first paragraph. These types are not identical. They are distinct types and are not assignment compatible. – David Heffernan Apr 30 '14 at 02:01
  • @DavidHeffernan In the first paragraph, there was just a minor problem with my choice of words. The second paragraph had a bigger mistake. Edited my answer to address that. –  Apr 30 '14 at 05:39
  • @hvd It is still not right, in my view. Leaving aside the second paragraph which concerns open arrays rather than dynamic arrays, and so is not directly related to the specific question. There is a huge difference between type compatibility of generic arrays and traditional dynamic arrays. That's what I've tried to explain in my answer. – David Heffernan Apr 30 '14 at 08:46
  • @DavidHeffernan There really isn't, IMO. In `var a: array of Integer; b: array of Integer;`, `a` and `b` also do not have compatible type. Every time you say `array of Integer`, you create a new type, regardless of whether you do it in a variable declaration, or in a type definition. –  Apr 30 '14 at 10:56
  • That's my point. As I expanded in my answer. Generic types get special rules. The generic array equivalent to the code in your comment results in variables with compatible types. – David Heffernan Apr 30 '14 at 11:00
  • @DavidHeffernan In `type TDynIntegerArray = array of Integer; var a: TDynIntegerArray; b: TDynIntegerArray;`, `a` and `b` also have compatible type, even though there are no generic types involved at all. There is no difference in the treatment of `TDynIntegerArray` and of `TArray`. That's what I meant by "any other custom type defined as ...". –  Apr 30 '14 at 11:15
  • That's not a fair comparison. `TDynIntegerArray = ...` is a type alias declaration. You don't use that step with generic arrays. Imagine I use two third party libraries that both define their own version of `TDynIntegerArray`. Now I cannot pass output from one library to the other. If those libraries choose to use `TArray` then I can. It's a very important difference. – David Heffernan Apr 30 '14 at 11:18
  • 2
    @DavidHeffernan That's only because `TArray` happens to be defined in a unit available to both third-party libraries (in your example). The fact that it is generic is not relevant. Two third-party libraries could equally well both add a dependency on JCL, and use `JclBase.TDynIntegerArray`. Anyway, that's not what the question is about, I think we're getting a bit too far off track. –  Apr 30 '14 at 11:22
  • Actually I think the generic array type compatibility rules do matter. So we disagree. – David Heffernan Apr 30 '14 at 11:24
  • @DavidHeffernan I'm always interested in learning, so if you have a concrete example how two libraries using `JclBase.TDynIntegerArray` would result in an incompatibility, that would be resolved by those two libraries both using `TArray`, I *would* love to know. For whatever it's worth, I do agree that now that `TArray` is available in the RTL, that should generally be preferred over `TDynIntegerArray` or a custom type definition, just not for the same reason as you. –  Apr 30 '14 at 11:30
  • There is of course no such example. I never said otherwise. My point is made by the examples in my answer. But your example forces everyone to take a dependency on some other common library. What's more it does not play well with a generic container. What is `TList.ToArray` meant to return. – David Heffernan Apr 30 '14 at 11:36
  • @DavidHeffernan That's good, then we're just miscommunicating. Yes, agreed, an RTL type should be preferred over an otherwise identical third-party type to avoid adding unnecessary library dependencies, and yes, agreed, `TArray` has advantages over specific type definitions for the various values of `T` when combining it with other generic types. –  Apr 30 '14 at 11:41
  • 1
    You guys know that `Types.pas` has `TIntegerDynArray` and several other TXXXDynArray types for almost all intrinsic types declared, do you? There is no need for dependencies to some 3rd party library even without `TArray` at least for most intrinsic types. – Stefan Glienke Apr 30 '14 at 12:07
  • @StefanGlienke Heh, thanks, I remembered it as `TDynIntegerArray` instead of `TIntegerDynArray`, searching for it found it was part of JCL, and figured I must have misremembered. –  Apr 30 '14 at 12:39
  • @StefanGlienke At least for most intrinsic types. Fine for them. And that's what we all did before generic arrays. – David Heffernan Apr 30 '14 at 12:42
  • @DavidHeffernan Obviously not because you were arguing over using the JCL to use some type that already is contained in the RTL. – Stefan Glienke Apr 30 '14 at 12:47
  • @StefanGlienke No I was not! Unless you want to restrict the discussion entirely to arrays of integer. But I was thinking more generally than that. Your comments here make it sound like you prefer `TDynIntegerArray` to `TArray`. Is that so? – David Heffernan Apr 30 '14 at 12:50
  • @DavidHeffernan I prefer what works best. If I had to deal with code that already uses `TDynIntegerArray` I would use that. If I have code that uses `TArray` I would use that because both are again assignment incompatible. Like if you are using `SplitString` it makes no sense to use `TArray` in related code because it returns `TStringDynArray`. – Stefan Glienke Apr 30 '14 at 13:06
5

It is perfectly safe to do this. The compiler will produce identical output.

I personally would, all other things being equal, recommend doing so. The use of the generic array TArray<T> gives you much more flexibility and better type compatibility.

Of course those benefits can only be seen with more realistic code that does some work. You most typically see benefits when using generic containers. But you might also see benefits when trying to build code using multiple different libraries.

The use of generic arrays allows easy type compatibility. Before generic arrays you would define an array type like this:

TIntArray = array of Integer;

If two libraries do this then you have incompatible types. If the libraries agree to use generic arrays then there will be compatibility.

To see this more clearly, consider this fragment:

type
  TIntArray1 = array of Integer;
  TIntArray2 = array of Integer;
....
var
  arr1: TIntArray1;
  arr2: TIntArray2;
....
arr1 := arr2;

This assignment is not valid and fails with a type mis-match compiler error. This is entirely to be expected within the Pascal language. It is after all strongly typed and these are distinct types. Even if they are implemented identically.

On the other hand:

var
  arr1: TArray<Integer>;
  arr2: TArray<Integer>;
....
arr1 := arr2;

is valid and does compile. The documentation for generic type compatibility says:

Two instantiated generics are considered assignment compatible if the base types are identical (or are aliases to a common type) and the type arguments are identical.

LU RD
  • 34,438
  • 5
  • 88
  • 296
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    The compiler will produce identical output... unless you care about RTTI, in which case, unfortunately, `TArray` can fail. (Unless this has been fixed in newer versions?) – Mason Wheeler Apr 29 '14 at 21:44
  • 2
    @MasonWheeler, exactly! F.i. the JSON marshaling used by Datasnap can perfectly work with a field type declared as `array of T`, but fails for `TArray`. So as ever so often the answer is: it depends. – Uwe Raabe Apr 29 '14 at 23:03
  • Surely this has been fixed. `TArray` is fundamental. – David Heffernan Apr 30 '14 at 01:08
  • 1
    @UweRaabe The RTTI can read a `TArray` field just fine (even in Delphi 2010) and properly returns a `TRttiDynamicArrayType` as its FieldType. Its rather the other way around that it has its problems with `array of T` because that is not a predefined type. – Stefan Glienke Apr 30 '14 at 05:52
  • 2
    @StefanGlienke, I did a quick check marshaling an object with a field if TIntArray = array of Integer and TDynArray = TArray using TJSONConverters.GetJSONMarshaler.Marshal: XE5 and XE6 can do it fine, XE3 can not. – Uwe Raabe Apr 30 '14 at 06:43
  • That is a different problem than you described before which is related to using a type alias. `TIntArray = array of Integer` defines a type while `TDynArray = TArray` is just an alias. If you are using array of Integer or TArray directly as type for your field it should work. But yes RTTI had problems with aliases for generic types. – Stefan Glienke Apr 30 '14 at 06:51