Solving circular unit references with class helpers
Sooner or later you run into the circular unit reference problem when using a single unit for each class. There are some solutions (move the common used structs into a separate unit, use type casting, redesign your obvious bad class design, etc.) – this is a solution using class helpers. Recommended reading: “Class Helpers – good or bad ?” and “New Delphi language features since Delphi 7“:
“Class helpers provide a way to extend a class, but they should not be viewed as a design tool to be used when developing new code. They should be used solely for their intended purpose, which is language and platform RTL binding. “
Let’s suppose we have a simple TDog class:
1 2 3 4 5 6 7 8 |
unit Dog; type TDog = class Name: String; end; |
and a corresponding TMaster class:
1 2 3 4 5 6 7 8 |
unit Master; type TMaster = class Name: String; end; |
Let’s create our objects:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
uses Dog, Master; var Dog: TDog; Master: TMaster; begin Dog := TDog.Create; Dog.Name := 'Bobby'; Master := TMaster.Create; Master.Name := 'Joe'; end; |
Works. Now we’re adding the dog’s master in our TDog class:
1 2 3 4 5 6 7 8 9 10 11 12 |
unit Dog; uses Master; type TDog = class Name: String; Master: TMaster; end; |
Ok, compiles. Alright. Now what if we need a reference to our dog in the TMaster class? First approach:
1 2 3 4 5 6 7 8 9 10 11 12 |
unit Master; uses Dog; // Compile error type TMaster = class Name: String; Dog: TDog; end; |
Won’t compile – circular unit reference (unit Dog uses unit Master, and unit Master uses unit Dog). You may getting around by using TObject as the Dog object:
1 2 3 4 5 6 |
TMaster = class Name: String; Dog: TObject; end; |
Well, that compiles – but you always have to type cast your Dog object:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[..] begin Dog := TDog.Create; Dog.Name := 'Bobby'; Master := TMaster.Create; Master.Name := 'Joe'; Master.Dog := Dog; // The Master has a dog Dog.Master := Master; // The dog's master is Master Writeln(TDog(Master.Dog).Name); end; |
Now – how about using class helpers? First we move the Dog object into our protected area:
1 2 3 4 5 6 7 8 9 10 11 |
unit Master; type TMaster = class protected FDog: TObject; public Name: String; end; |
then we introduce a new class helper which “exports” the dog as a TDog class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
unit MasterClassHelper; uses Dog, Master; type TMasterClassHelper = class helper for TMaster private procedure SetDog(Value: TDog); function GetDog: TDog; public property Dog: TDog read GetDog write SetDog; end; function TMasterClassHelper.GetDog: TDog; begin Result := TDog(FDog); // We cast the TObject(FDog) to TDog! end; procedure TMasterClassHelper.SetDog(Value: TDog); begin FDog := Value; end; |
Now we’re able to access the Dog reference of the Master object without typecasting:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
uses Dog, Master, MasterClassHelper; var Dog: TDog; Master: TMaster; begin Dog := TDog.Create; Dog.Name := 'Bobby'; Master := TMaster.Create; Master.Name := 'Joe'; Master.Dog := Dog; // The Master has a dog Dog.Master := Master; // The dog's master is Master Writeln(Master.Dog.Name); // "Bobby" Writeln(Dog.Master.Name); // "Joe"; Writeln(Dog.Master.Dog.Master.Dog.Master.Dog.Name); // "Bobby" end; |
Adding the MasterClassHelper in the implementation part of the Master unit allows us to access the TDog class inside our TMaster class, too:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
unit Master; type TMaster = class protected FDog: TObject; public Name: String; procedure RenameDog; end; implementation uses MasterClassHelper; procedure TMaster.RenameDog; begin Dog.Name := 'Buddy'; end; |
Download Delphi source DogMaster.zip
10 Comments to Solving circular unit references with class helpers
It’s really time to enhance the helpers in delphi to make them work more like c# extensions (i.e. allowing helpers to more than records and classes and, being able to use more than one helper)
I also use class helpers at some places in my own projects but most of the times it kinda smells bad imo.
June 10, 2011
@Stefan, I agree that class helpers needs enhanctmens but it’s far more important that Delphi finally gets proper namespaces instead of units
June 10, 2011
+1 for proper namespace, too. Allowing usage of multiple class helpers shouldn’t be that “compiler complicated” either I’ll guess…
June 10, 2011
There’s really no excuse to have to hack together workarounds for something that the compiler should be doing itself. I understand the single-pass concept in the compiler, which I’m sure was wonderful back when source code was kept on floppy disks. But there’s really no excuse for it to not be able to resolve circular references itself now.
June 11, 2011
@Marcus: The speed of a single pass compiler is still very important. Even with high speed HDD’s. I would get very very impatient if a 1M LOC project would take more than a minute to compile… Fast compiles are an essential requirement for my TDD routine and I couldn’t get anywhere near as efficient with a multi-pass compiler. I’ll more than happily take having to fix circular dependencies if that means the compiler will stay single pass…
June 14, 2011
If the compiler runs into a circular reference, why not adding a second pass just for these units?
June 20, 2011
…or you could just group related classes together in the same unit, the way Delphi developers have been doing since the very beginning, instead of trying to shoehorn a silly Java concept like “one class per file” into a language that doesn’t use Java’s object model. 😛
June 20, 2011
It is really time that C# adds some of the strengths (property helpers anyone?) of Delphi class helpers.
June 22, 2011
@mason:
I really like the “one class per file” java paradigm – it helps to reduce writing monster units. And it’s somewhat easier to write code with many developers involved.
IMHO if your class needs code (so it’s not a “data class”) it should be in a separate unit.
Therefore I’ll vote for a
“uses Classes, Generics.*, MyCrypt.*, Gui.Login.*;”
uses declaration (java inspired of course)
September 20, 2011
This looks like it could be very useful.. we started using base classes to help with the type safety, but automatic casting would simplify a lot of code.
As far as ‘all classes in one file’ goes – We have individual source files over 50,000 lines long because a previous developer thought like that and are having to break everything down again just to stop the delphi compiler falling over so often (it does *not* like large files). It’s also a code maintainability nightmare.
Leave a comment
About Dennis D. Spreen
Search
Recent Posts
- How to compile Lua 5.4.0 for Android as a dynamic library using Android Studio 4
- Please make inline vars usable for production – fix RSP-28892
- How to compile Lua 5.4.0 as a Mac OS X dynamic library
- How to compile Lua 5.4.0 for Linux as a shared library
- How to compile Lua 5.4.0 for Windows
- Daily Wage – a Spigot/Bukkit plugin that pays out a daily wage
- How to compile Lua 5.3.5 for Windows
- Better Collada exporter for Blender with custom properties
- MOS6502-delphi – a MOS 6502 CPU emulator for Delphi
- Pass a multidimensional array as a parameter (with a hidden caveat)
Categories
Tags
Archives
- May 2020
- March 2020
- June 2019
- March 2017
- August 2016
- July 2016
- June 2016
- January 2016
- September 2015
- February 2015
- January 2015
- October 2014
- September 2014
- August 2014
- May 2014
- March 2014
- February 2014
- November 2011
- June 2011
- February 2011
- March 2010
- September 2009
- August 2009
- July 2009
- May 2009
- March 2009
- February 2009
- January 2009
- November 2008
- October 2008
- February 2008
- June 2007
Delphi Feeds
- Habari STOMP Client libraries release 2024.12 December 7, 2024
- Delphi and AI December 7, 2024
- Uses Clause Manager topic added to the GExperts help December 6, 2024
- Upgrading C++Builder 12.2: Tip #4 1/2: Check out compiler safety options December 6, 2024
- Upgrading C++Builder 12.2: Tip #4, Take Advantage of Security and Correctness December 5, 2024
- .NET MAUI: Complete Guide December 4, 2024
- Upgrading C++Builder 12.2: Tip #3, Massive Files with iostreams December 4, 2024
- Log exceptions in your Delphi app in the cloud with StellarDS.io December 4, 2024
- Introducing Signotaur - Remote Code Signing Server December 4, 2024
- Upgrading C++Builder 12.2: Tip #2, Handling Old RTL December 3, 2024
June 10, 2011