Mike Torrettinni 198 Posted September 24, 2021 I know that empty dynamic array = nil, so you can check if array is empty by: array = nil or Length(array) = 0 If a TList<T> is nil the Count will not return 0. Why did they design it like that? What is the purpose of empty array to be also nil (undefined)? Why the same logic doesn't apply to lists? Any explanation is appreciated! Share this post Link to post
Uwe Raabe 2057 Posted September 24, 2021 A TList<T> is a class, so a variable of type TList<T> being nil is not an empty list, but no list instance at all. In contrast to nil, an existing TList<T> instance can be filled. For nil you first have to create with TList<T>.Create to get a list you can work with. In contrast to that an array is not class. 1 Share this post Link to post
Fr0sT.Brutal 900 Posted September 24, 2021 Probably that was done for perf purpose. I wouldn't rely on this, consider it implementation-specific, just the same as with empty strings or that TDatetime is days+fraction-of-the-day since December 30, 1899, etc. Share this post Link to post
Uwe Raabe 2057 Posted September 24, 2021 33 minutes ago, Fr0sT.Brutal said: I wouldn't rely on this At least it is documented here: https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Structured_Types_(Delphi)#Dynamic_Arrays Quote Dynamic arrays of length 0 have the value nil. Share this post Link to post
corneliusdavid 214 Posted September 24, 2021 (edited) To expand on what Uwe said, an array declaration allocates space for the variable and creates it when it's declared, much like an integer or string. Changing it's length sort-of redeclares it. This is in contrast to the TList<T> where the declaration doesn't allocate space for the variable (instantiate it) so you have to actually call its Create constructor. Before it's "created" then, it's nil. Edited September 24, 2021 by corneliusdavid 1 Share this post Link to post
corneliusdavid 214 Posted September 24, 2021 And to further clarify my "array declaration" statement, a dynamic array declaration doesn't allocate the space for it until its length is set to at least 1. Share this post Link to post
Mike Torrettinni 198 Posted September 24, 2021 1 hour ago, Fr0sT.Brutal said: I wouldn't rely on this, consider it implementation-specific, Hm, I use it a lot. I prefer array <> nil over Length(array) > 0. Share this post Link to post
Stefan Glienke 2002 Posted September 24, 2021 (edited) 1 hour ago, Mike Torrettinni said: Why did they design it like that? Because a pointer to empty would be quite stupid. While you can technically build an allocated array with a capacity of zero with some hacking the RTL always cleans up the memory as soon as an arrays length becomes 0. Fun fact - checking against nil for the purpose of "is it empty or not" opposed to comparing Length to 0 is slightly faster: procedure test(const a: TBytes); begin if a <> nil then Writeln; if Length(a) > 0 then Writeln; end; CheckArray.dpr.12: begin 0041D57C 53 push ebx 0041D57D 8BD8 mov ebx,eax CheckArray.dpr.13: if a <> nil then 0041D57F 85DB test ebx,ebx 0041D581 740F jz $0041d592 CheckArray.dpr.14: Writeln; 0041D583 A1B4254200 mov eax,[$004225b4] 0041D588 E8CF7DFEFF call @WriteLn 0041D58D E8B270FEFF call @_IOTest CheckArray.dpr.15: if Length(a) > 0 then 0041D592 8BC3 mov eax,ebx 0041D594 85C0 test eax,eax 0041D596 7405 jz $0041d59d 0041D598 83E804 sub eax,$04 0041D59B 8B00 mov eax,[eax] 0041D59D 85C0 test eax,eax 0041D59F 7E0F jle $0041d5b0 CheckArray.dpr.16: Writeln; 0041D5A1 A1B4254200 mov eax,[$004225b4] 0041D5A6 E8B17DFEFF call @WriteLn 0041D5AB E89470FEFF call @_IOTest CheckArray.dpr.17: end; 0041D5B0 5B pop ebx 0041D5B1 C3 ret Edited September 24, 2021 by Stefan Glienke 2 1 Share this post Link to post
Mike Torrettinni 198 Posted September 24, 2021 @Stefan Glienke Thanks. Looking on my phone it cut short the lines, so it appears as test jz (test-jay-zee ) 🙂 Share this post Link to post
Mike Torrettinni 198 Posted September 24, 2021 2 hours ago, corneliusdavid said: And to further clarify my "array declaration" statement, a dynamic array declaration doesn't allocate the space for it until its length is set to at least 1. Does any other type behave like this? My little knowledge explains this to me that array is just declared variable that points nowhere, until Length() > 0. While a TList variable points to a reserved space in memory (4 bytes?), but not initialized, yet. And when initialized it will point to memory space reserved on TList<>.Create. 2 hours ago, Stefan Glienke said: Because a pointer to empty would be quite stupid. Isn't that the case for string? When it's empty, it's a pointer to empty, no? Share this post Link to post
corneliusdavid 214 Posted September 24, 2021 16 minutes ago, Mike Torrettinni said: a TList variable points to a reserved space in memory (4 bytes?), but not initialized No, a TList is similar to the dynamic array with no elements in this case, it's just declared but doesn't point to anything yet until the Create constructor is called, THEN it points to the allocated memory for the TList. Even though TList, at that point, doesn't have any items, it still has other properties like Count which is initialized at Create to 0. The dynamic array isn't a class, in other words, it doesn't have other properties, such as Count, which is why you have to use the Length() function. Share this post Link to post
0x8000FFFF 22 Posted September 24, 2021 2 hours ago, Mike Torrettinni said: 4 hours ago, Stefan Glienke said: Because a pointer to empty would be quite stupid. Isn't that the case for string? When it's empty, it's a pointer to empty, no? An empty string is equal to nil pointer. Try this: var s := ''; Writeln(Format('%p', [Pointer(s)])); One of the differences between arrays and lists is that even empty lists can have pre-allocated capacity. This is often an overlook feature of lists which helps reducing reallocations while populating the list if you know (or can estimate) the count in advance. 1 Share this post Link to post
David Heffernan 2345 Posted September 25, 2021 14 hours ago, Stefan Glienke said: Because a pointer to empty would be quite stupid. Not sure I agree. It can be useful to distinguish between null and length 0 array. It can be useful to have these as different conceptual things. 1 Share this post Link to post
Dalija Prasnikar 1396 Posted September 25, 2021 1 hour ago, David Heffernan said: Not sure I agree. It can be useful to distinguish between null and length 0 array. It can be useful to have these as different conceptual things. If you need that distinction you can always wrap it up in nullable type. Delphi implementation of strings and dynamic arrays is fine as it is. Adding that distinction into array or string itself would only complicate working with strings and arrays with no benefits in most of the code. What would be default value of the array or string? nil or empty? If it is nil, welcome to nil nightmare, if it is empty, you need nullable again, or you would just want the ability to nil after it is initialized to empty. This is the fastest road to hell. 1 Share this post Link to post
0x8000FFFF 22 Posted September 25, 2021 (edited) I'm quite happy that I don't need any string.IsNullOrEmpty all over the place in my Delphi code as opposed to C#. Edited September 25, 2021 by 0x8000FFFF 1 2 Share this post Link to post
David Heffernan 2345 Posted September 25, 2021 4 hours ago, Dalija Prasnikar said: If you need that distinction you can always wrap it up in nullable type Only there's no language support for that. 5 minutes ago, 0x8000FFFF said: I'm quite happy that I don't need any string.IsNullOrEmpty Why does C# code ever have those all over the place? Mine doesn't. Is sounds a bit like poorly designed code. Or am I missing something? Share this post Link to post
Mike Torrettinni 198 Posted September 25, 2021 5 minutes ago, 0x8000FFFF said: I'm quite happy that I don't need any string.IsNullOrEmpty all over the place in my Delphi code as opposed to C#. We still need to do: if List <> nil then iterate List ... while arrays you can just loop and no errors. Right? Share this post Link to post
David Heffernan 2345 Posted September 25, 2021 1 minute ago, Mike Torrettinni said: We still need to do: if List <> nil then iterate List ... while arrays you can just loop and no errors. Right? Generally you know that the list has been assigned. Usually they are created in the same function above the use, or passed in to the function and known to be assigned, or created in the constructor. So no its quite rare to see code like that. Share this post Link to post
Dalija Prasnikar 1396 Posted September 25, 2021 23 minutes ago, David Heffernan said: Only there's no language support for that. But you can have your own implementation. 23 minutes ago, David Heffernan said: Why does C# code ever have those all over the place? Mine doesn't. Is sounds a bit like poorly designed code. Or am I missing something? I don't know about C#, but handling strings or arrays in Java is nightmare. You need to check for nil before you can do anything with it. Yes, if you have created it few lines of code before then you don't have, but usually you don't know from where it comes from and you need to check. If I had a dime for every time I though "this string cannot be nil here, so I don't need to check" and then I had exceptions at runtime, I would be on my tropical island right now, drinking pina colada. Share this post Link to post
0x8000FFFF 22 Posted September 25, 2021 22 minutes ago, David Heffernan said: Why does C# code ever have those all over the place? Mine doesn't. Is sounds a bit like poorly designed code. Or am I missing something? Legacy code, libraries, defensive programming, input sanitization and normalization, data from unknown sources, ... Null-coalescing operator used with strings falls into the same category (s ?? ""). Luckily C# has made some improvements in this area by introducing nullable reference types since version 8.0. Share this post Link to post
dummzeuch 1505 Posted September 25, 2021 (edited) 45 minutes ago, Mike Torrettinni said: We still need to do: if List <> nil then iterate List ... while arrays you can just loop and no errors. Right? In theory you could create a method that checks for self <> nil and only then returns Count, zero otherwise, so the code would look simpler: for i:=0 to List.getCount-1 do Even thought the <> nil check is still there. Edited September 25, 2021 by dummzeuch 1 Share this post Link to post
Mike Torrettinni 198 Posted September 25, 2021 12 minutes ago, dummzeuch said: In theory you could create a method that checks for self <> nil and only then returns Count, zero otherwise, so the code would look simpler: for i:=0 to List.getCount-1 do Even thought the <> nil check is still there. So, I would need interposers for the most common used (in my code): TStringList, TList<integer> and TList<rec>. That seems a good suggestion. Share this post Link to post
dummzeuch 1505 Posted September 25, 2021 2 hours ago, Mike Torrettinni said: That seems a good suggestion. Not sure I meant this as a suggestion. It will still fail horribly if the object variable is neither nil nor a valid instance. Share this post Link to post
David Heffernan 2345 Posted September 25, 2021 2 hours ago, Mike Torrettinni said: So, I would need interposers for the most common used (in my code): TStringList, TList<integer> and TList<rec>. That seems a good suggestion. No. Really not a good suggestion. Please don't tell me that you have nil checks every time you use an object. Share this post Link to post
Mike Torrettinni 198 Posted September 25, 2021 14 minutes ago, dummzeuch said: Not sure I meant this as a suggestion. It will still fail horribly if the object variable is neither nil nor a valid instance. OK, I see what you mean. This seems like a simple setup: program Project1; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Classes; type TStringList = class(system.Classes.TStringList) public function GetCount: integer; end; function TStringList.GetCount: integer; begin if not Assigned(Self) then Result := 0 else Result := Self.Count; end; procedure TestWhenNil; var vSL: TStringList; begin vSL := nil; Writeln('Items in SL = ' + vSL.GetCount.ToString); end; procedure TestWhenHasItems; var vSL: TStringList; begin vSL := TStringList.Create; try vSL.Add('test'); Writeln('Items in SL = ' + vSL.GetCount.ToString); finally vSL.Free; end; end; begin TestWhenNil; TestWhenHasItems; Readln; end. And output is: Share this post Link to post