First, you have this code:
public class Application
{
HybridObject obj;
public Application()
{
obj = new HybridObject().Core;
obj.Delete();
while (true)
{
Update();
}
}
void Update()
{
if (obj == null) Console.WriteLine("Destroyed"); // I NEED THIS TO WORK
if (obj.Core == null) Console.WriteLine("Destroyed Core"); // THIS WORKS
}
}
Your obj
is a private member field of your Application
class. It's never going to go null
unless you set it to null. It doesn't matter if you call Delete
on it, it will forever reference the object you told it to reference (until you tell it to reference another object (via assignment) or set it to null
(also by assignment).
Then you have this rather strange class:
public class HybridObject
{
protected HybridObject core;
public HybridObject Core => core;
public HybridObject()
{
this.core = this;
}
public void Delete()
{
this.core = null;
}
}
An instance of this class can have two states, normal or deleted. Your Core
property (and its backing core
field) can either point to the object as a whole (the normal (or newly constructed) state, or it can be set to null
(the deleted state). That's a bit odd, but fine.
BUT, in either state, once there are no longer any references to an instance of your HybridObject
class, that particular instance becomes eligible for garbage collection. It doesn't matter that in the normal state, that instance will hold a hard reference to itself (since objects that are eligible for collection do not act to keep objects they refer to alive). Once an object is eligible for collection, it will be collected at the next GC (give or take a few complicated rules that don't affect the logic of your program).
This kind of cyclical reference was a real PITA in languages like C or C++ (where you had to carefully walk the graph of objects you created in order to delete them), or in reference-counted frameworks like the COM-based (pre-.NET) Visual Basic. The .NET GC eats these situations for breakfast.
Just accept that you don't need to worry about deleting everything (in the much greater than 99% case) and live the garbage collected lifestyle.
Update (15-Apr-2023)
You mention "real-time" and "game" in your comments. My advice to you is to forget what you know about managing memory and objects (which I'm guessing you got from experience from C, C++ or some other traditional language).
Then read up on how the .NET garbage collector works, and how managed memory in general works. In particular, bone up on:
- The generational garbage collection (Gen0, Gen1 and Gen2)
- In particular the cost of long-lived objects
- The large object heap
- The
GC.TryStartNoGCRegion
call (and its friends).
- Finalizers (and why you should avoid having them ever run if possible)
- The Dispose pattern
In many ways, a GC-driven program can be faster than a traditional heap program. Memory allocations take no locks on Intel processors, and the only real cost is that the Framework guarantees that memory is zeroed before your program sees it. You also don't need to do any work to clean up any memory-only resources.
Consider a game, where the game object opens a scene object (or a level object or whatever). Within the scene object the program builds a complex graph of thousands of possibly inter-related objects, with lots of hard references between the objects. When the player completes the level/scene/whatever, and you want to tear that all down, all you need to do in the main game code is currentScene = new NextScene(creation, parameters);
Since currentScene
no longer points the previous scene (and, nothing else points to it), the old scene - and all of its contents - are now eligible for collection (without you having to ever call Delete
or something similar on anything).
The cost for this is garbage collection. You don't have to write any code to make it happen, but it does happen. It runs on a separate thread, and it runs at non-deterministic times. If you are writing something with soft-real-time constraints, you need to be thinking about this and how to coerce it to run when it will least affect your application.
For example, although just about everything you read about the GC says "you should almost never call GC.Collect
", if you have a situation like what I described above (with a game object and an abandoned scene object), this is a good example of a time when GC.Collect
makes sense. When you abandon the previous scene, your program is in a state where responsiveness is likely less important, and where you know that there are a ton of objects ready to be collected - that's when a program induced collection make sense.
My point is that if you are writing a game or a soft-real-time system, you really need to have a firm grasp of the garbage collector and the whole managed memory system.