18
votes

I just started to play with the new unit in Delphi 2010 IOUtils.pas, and I found they put all the methods inside Records(TFile, TPath, TDirectory) as class functions and procedure.

Is there any benefits of doing that inside records instead of classes? In both cases there's no need for any variables or instance, but I'm not sure if there any real benefits regarding memory consuming or performance improvement.

4
This is a good question.Edwin Yip

4 Answers

23
votes

Class methods in records are used to group different methods into a common namespace. Thus you can have similar named methods for different purposes. For an example in IOUtils.pas look at the Exists function available in TFile and in TDirectory. The older approach was to have distinct function names for FileExists and DirectoryExists (which the implementations actually call).

While class methods inside classes can be used in the same way, they can in addition have another goal: they can be virtual. Called from a class variable this can lead to different implementations depending on the current content of that variable. This is not possible for records. As a consequence class methods in records are always static.

9
votes

I would say that the first question to ask is "Why put such functions inside either a record or a class?" in the first place.

The use of record methods as in IOUtils is in many cases largely arbitrary in Delphi and in part reflects a fetish with following conventions in other languages that are not directly relevant in Delphi.

In some languages (Java and .NET?) "first class" functions are not supported. That is, procedures and functions cannot exist independently of any container class. Hence in those languages if you have functions and procedures they must be contained in a class. If they do not operate on member data then obviously they are declared as class methods to avoid having to construct an instance merely to call what is in fact a classLESS function.

i.e.

TDirectory.Exists(s) and TFile.Exists(s) are syntactic alternatives to DirectoryExists(s) and FileExists(s).

In Delphi first class functions are supported and there is therefore no real reason to use classes or records to discriminate between functions, except as a way of facilitating ease of reference for a developer when writing code and using IDE assistance to browse for available methods.

Without a container record/class you have to know that there is a function called "DirectoryExists()". With a container record, you only have to know that there is a type TDirectory, and the IDE (via code completion suggestions) can then present all of the "methods" available for TDirectory operations.

However, in Delphi there is an alternative made possible by the support for first class functions. Rather than using arbitrary and artificial "containers", you (or rather the authors of IOUtils) could instead have chosen to use the existing container - a Delphi "unit":

  unit IOUtils.Directory;

  interface

  function Exists(s): Boolean;

and

  unit IOUtils.File;

  interface

  function Exists(s): Boolean;

This still enjoys the benefits of IDE support in the form of code completion suggestions and also provides the namespace separation required to support identical function names operating on different things.

The downside of this approach (and it is a big one) is that where a unit uses both IOUtils.File and IOUtils.Directory, it must qualify one or other function name to avoid confusion, but this qualification could be omitted, which is why it is so dangerous:

  uses
    IOUtils.File,
    IOUtils.Directory;
  ..
  begin
    if Exists(s) then  // << tests existence of a directory
    ..
    if IOUtils.File.Exists(s) then  // << ensure we test for a file
    ..
    if IOUtils.Directory.Exists(s) then  // << ensure we test for a directory
  end

This may also be an advantage of course, if your unit contains code that ONLY works with directories, it will use only IOUtils.Directory and need not qualify and repeat that fact anywhere other than in the uses clause.

A potential and very REAL danger here of course is that in future if the unit is extended and subsequently starts using IOUtils.File, then this could easily lead to scoping errors on any unqualified references.

Which approach you prefer in your code will depend largely on personal preference and the possibility for collisions in scoping errors where you have similar/identically named or semantically equivalent functions operating on different entities.

For those reasons, the design choice in IOUtils at least is understandable and arguably a good one, tho ymmv.

But in other cases, if all you are seeking is a way to group related functions that are not at risk of confusion/collision with similar functions, then the use of an artificial and arbitrary container is superfluous. The unit that the functions must reside in may itself provide all the grouping behaviour you need.

As for why a record rather than a class...

I suspect that there may be a small static memory benefit to using a record rather than a class when fabricating these arbitrary containers. I imagine that a class has additional overhead, tho this is going to be relatively small and static in nature (i.e. a bit more overhead for each class, but not related to the number of times it's class methods are used).

5
votes

The static class methods are just a syntactic sugar - you can always implement them as ordinary procedures/functions.

If a class method is implemented inside a record, there is less overhead. Any class declaration requires an additional structure for class info in memory. Records does not have the structure.

2
votes

IMO one would need a very good reason to use records over classes with static methods. Like for TValue when speed matters. For instance it just turns out that TDirectory.CreateDirectory() just fails for UNC Paths cause TPath.DriveExists() fails for an UNC path. (D2010) If it were a class i would just write a helper class implementing my own TDirectory.CreateDirectoryEx(). Since its a record I can't and either copy some code from IOUtils or write my own one. In any case I endup with code-redundancy. So from the viewpoint of exensible code choosing records is a bad idea. (I actually still wonder why the VCL-Team decided not to consider extensibility. I mean compare the VCL with a DevEx's QuantumGrid under this aspect. There are lightyears in between)

Ok, I just found out that you can actually write a helper for a record:

TDirectoryHelper = record helper for TDirectory

end;

But it doesn't help much since you can't access any private static method from TDirectory