2

The question may be a bit confusing, and is best illustrated by an example:

unit Test
interface
type
  TestClass = class()
    Splitter1: TcxSplitter;
    procedure SomeMethod();
  end;

implementation
uses
  cxSplitter;

// Locally-declared child type
type
  TcxSplitterAccess = class(TcxSplitter);

procedure TestClass.SomeMethod()
var
  pos: integer;
begin
  // Access to protected field FPositionBeforeClose by casting
  pos := TcxSplitterAccess(Splitter1).FPositionBeforeClose;
end;

Notice in the implementation section that there is a type TcxSplitterAccess being declared as a child of the TcxSplitter class. In the method SomeMethod(), belonging to the class TestClass, a TcxSplitter object is cast to the locally-declared TcxSplitterAccess class, and then a protected field is accessed on that object.

This is surprising to me as someone coming from a background of languages like Java, C++, C#, etc. In those languages, it is possible to access protected data in an object so long as you are doing it from within that object's type or an inherited type. For example, a method inside of a class ClazzA can access the private fields of other ClazzA objects since access is enforced at the type level rather than the instance level. Declaring a class locally in these languages would not give the containing class access to the local class' protected data (edit: As pointed out in the comments, this is actually not true at least for Java).

In this example, however, the type TestClass is directly accessing a protected field on the TcxSplitter object by first casting to the TcxSplitterAccess type. I am having trouble finding documentation on why this "trick" works. Does Delphi handle access levels fundamentally differently to Java-like languages and allows this sort of thing? Regardless, why does this trick work?

While I stumbled onto this behavior by using a nested, inherited class in order to access fields on the parent class (which breaks encapsulation, and I shouldn't do), the use of inheritance here is unnecessary. If the nested class did not inherit from a class, but instead had its own protected fields defined, TestClass would still be able to access those protected fields.

Zach F.
  • 165
  • 1
  • 8
  • Can't give an accurate answer without seeing the source for TcxSplitter. Is FPositionBeforeClose truly Private, or is it Protected? I'm pretty sure if you create a descendant of a class in Java, it has access to protected fields – Dave Nottage Oct 06 '20 at 23:46
  • @DaveNottage I dug into the source, and you are correct - `FPositionBeforeClose` is actually a protected member. I've updated the question to reflect that. The question still stands however - notice in the example that the protected member is being accessed by `TestClass`, not by `TcxSplitterAccess`. – Zach F. Oct 06 '20 at 23:55
  • 1
    FWIW: Java and C# both allow nested/inner types access to private fields in the containing class. Java can also access the private members of the inner class from the containing class. Private rules (not polymorphic) are entirely separate from protected rules. — https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/nested-types (C#), https://stackoverflow.com/q/1801718/2864740 (Java) – user2864740 Oct 06 '20 at 23:56
  • @user2864740 I was aware of nested classes having access to the containing class' private data, but this situation is the reverse of that. I had no idea that Java could do this as well! If that is the case, then it might simply be that this pattern isn't so unusual and I was just unaware of it. – Zach F. Oct 06 '20 at 23:59
  • *"it is possible to access protected data in an object so long as you are doing it from - within that object's type or an inherited type"* - This is exactly what you're doing here, "TcxSplitterAccess" inherits from "TcxSplitter" and thus it can access protected members of the latter. – Sertac Akyuz Oct 07 '20 at 00:16
  • 1
    @SertacAkyuz Ah, be careful here, as there is a subtle difference that sets this apart from normal inheritance behavior. It is not `TcxSplitterAccess` that is accessing the protected data, it is actually `TestClass` that is accessing the protected member *on* `TcxSplitterAccess` (and by extension, `TcxSplitter`). `TestClass` contains the (nested) definition, it is not actually a parent. – Zach F. Oct 07 '20 at 00:19
  • Indeed I don't understand that difference, LHS is irrelevant - the inherited class is accessing the protected member.. – Sertac Akyuz Oct 07 '20 at 00:28
  • The child class (`TcxSplitterAccess`) is in fact *not* accessing the protected member in this example - the method's full declaration is `TestClass.SomeMethod`, which means that we are operating within the `TestClass` type. So it is in fact `TestClass` that is accessing the protected member `FPositionBeforeClose` *on* the class `TcxSplitterAccess`, from which TestClass is not descended. Consider what would happen if that line was instead, `pos := Splitter1.FPositionBeforeClose;`. This results in a compile-time error since `TestClass` can not access `TcxSplitter` protected members. – Zach F. Oct 07 '20 at 00:46
  • Where we are operating is irrelevant, that was what I attempted to mean by LHS. "pos := Splitter1.FPositionBeforeClose;" this results in a compile time error not because testclass cannot access tcxsplitter protected members but because "splitter1" cannot access protected members because it's of type tcxsplitter. – Sertac Akyuz Oct 07 '20 at 00:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/222627/discussion-between-zach-f-and-sertac-akyuz). – Zach F. Oct 07 '20 at 02:51

1 Answers1

5

A unit has implicit-friendship semantics within itself. Types declared in the same unit are "friends" of each other (similar to friend in C++), and so can access each other's private and protected members (but not strict private or strict protected members).

So, in this case:

  • TcxSplitterAccess derives from TcxSplitter, so TcxSplitterAccess inherits all of the protected (but not private) members of TcxSplitter via normal class inheritance.
  • TestClass is declared in the same unit as TcxSplitterAccess, so TestClass has access to all of the protected members of TcxSplitterAccess, including the protected members of TcxSplitter (and the protected members of its ancestors).
  • FPositionBeforeClose is protected in TcxSplitter.

So, that is why TestClass.SomeMethod() is able to access FPositionBeforeClose when type-casting the Splitter1 object to TcxSplitterAccess.

This is covered by the Delphi documentation:

Private, Protected, Public, and Published Declarations

Classes and Objects (Delphi): Visibility of Class Members

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • `Strict` is the keyword that I was not searching for that would have lead me directly to this conclusion. Thank you for pointing out that exception. – Zach F. Oct 07 '20 at 01:05