Do you find C#'s lack of an explicit destructor to be an issue?

Started by
22 comments, last by dmatter 10 years, 8 months ago

Ok. Object 1 owns object 2. This is explicit. Object 3 has a reference to object 2 because it needs to work with some data it has. Object 1 dies, so it kills object 2 as well. This is also explicit.

The two statements marked in bold both represent potential problems with this design:

- Object 1's lifetime is non-deterministic by virtue of being garbage collected, so by tying object 2's lifetime to object 1, you have made object 2's lifetime non-deterministic as well.

- If object 3 stores a reference to object 2, then it has assumed co-ownership of object 2, and object 2 can no longer be solely tied to object 1's lifetime.

There are a couple of questions that are worthwhile to ask at this point:

- Why is the lifetime of object 2 tied to that of object 1, instead of being tied to program lifecycle events (i.e. level unload, application shutdown, etc)?

- Why does object 3 store a reference to object 2? Is the a concrete reason why it could not be passed that reference each time it needs access to the data?

- Why cannot object 3 maintain a reference to object 1 instead?

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Advertisement

Ok. Object 1 owns object 2. This is explicit. Object 3 has a reference to object 2 because it needs to work with some data it has. Object 1 dies, so it kills object 2 as well. This is also explicit.

Now, we still need to inform object 3 that it's reference to object 2 is no longer valid.

if you know ahead of time that the lifetime of #3 is shorter than #1/#2, then there's no problem (in debug builds, I'd use a weak reference and assert that it is never nullified).
If you know that the lifetime of #3 is longer than #2, then the ownership definition may be wrong.
If the lifetimes of #1 and #3 are arbitrary and unrelated, but you want to detach the link between #3/#2 when #1 dies, then use a weak-reference / weak-pointer, or write this behavior explicitly if it's regular business logic instead of resource management ;)
There was an sprite-drawing-example similar to the 3 objects.
I don't see any problems in drawing sprites in C# (or Python, or Java, ...).
In general you will have some kind of ingame environment, eg. a map, a level, a room, or something else. This environment contains all the objects you want to draw, walls, tables, charakters, items, mosnters, and so on. So why don't you try to draw the environment and store the sprites seperated?
There are also other problems arising out of this bad design: the player opens a door and an other sprite has to be drawn at the same position, the player starts walking, the player hides and gets invisible, an item explodes and there has to be some kind of explosion effect, or the drawing order changes (because the player walked behind a wall).
This way you would have to add and remove sprites all the time. By drawing the environment you could always use the objects current states and removing(/destroying) an object is no problem.

My point is that In a managed language like C# there is extra implementation to be done.

And my point is that you should be doing this implementation in C++, too. Skating by with shared ownership semantics only takes you so far.

Ok. Object 1 owns object 2. This is explicit. Object 3 has a reference to object 2 because it needs to work with some data it has. Object 1 dies, so it kills object 2 as well. This is also explicit.

Now, we still need to inform object 3 that it's reference to object 2 is no longer valid.

I am a bit confused about how you think C++ handles this any better than C#?

If Object 3 and Object 1 owned shared_ptrs to Object 2 then you'll have the same cleanup considerations as with C#.
If Object 3 owned a weak_ptr then C# has those too, but with a non-deterministic GC I wouldn't bother, nor would I go that route in C++ either.
If Object 3's lifetime or its reference to Object 2 is short, transient or non-owning (e.g. you re-pass Object 2 along every frame), then you can do that in C# as well.

The only thing I can think at the moment is that your destructor of either Object 1 or Object 2 will call directly into Object 3 to unregister Object 2? If so you have still had to put that 'extra implementation' into C++ too, only in a destructor. Hopefully Object 3 would never through an exception during that unregistering.

A C++ destructor (or C# IDisposable) is really about cleaning up raw resources within that object - not about supporting your business logic to unregister it from the rest of the system.

This topic is closed to new replies.

Advertisement