[Proposal] Deterministic finalizers on structs and classes #3450
Replies: 7 comments
-
I think there's no doubt in anyone's mind that deterministic resource cleanup would be a wonderful thing to have. There's been discussion on this since before .net 1 (see https://docs.microsoft.com/en-gb/archive/blogs/brada/resource-management). The problem is no-ones come up with a really good design for it. If you can come up with one I'm sure the language team will be all ears! |
Beta Was this translation helpful? Give feedback.
-
@YairHalberstadt: Sure, the design is of high importance here. I would suggest to look this simply up how C++ does it. There might be some tweaks necessary, but honestly I have too little insight of what might be the side effects. If a discussion could start here to elborate a solution I'm here? |
Beta Was this translation helpful? Give feedback.
-
@msedi |
Beta Was this translation helpful? Give feedback.
-
It also gives you some insight into the scale and importance of this issue. Microsoft invested months trying to work out a way to do this. That we don't have it isn't for lack of trying out realisation as to its importance. It's because it's a really difficult problem. What's needed now is a good design. |
Beta Was this translation helpful? Give feedback.
-
@YairHalberstadt: Thanks I'll do and then come back ;-) |
Beta Was this translation helpful? Give feedback.
-
I think some design decisions in Rust programming language can help with that. The language has deterministic memory management based on ownership concept without needing a GC. |
Beta Was this translation helpful? Give feedback.
-
Motivation
With the introduction of ArrayPool, MemoryPool (and maybe also ObjectPool) often the question comes up how to
Return
the object to the Pool. There are currently two approaches, one is beReturn
ing the item back to the pool, the other option is by callingDispose
. There is an additional situation with GCHandle, where there is none of the above mechanisms. I have to callFree()
. For streams, I have the choice eitherClose()
orDispose()
.Both solutions have the drawback that if someone doesn't call
Return
orDispose
the Pool produces new objects but ultimately refuses to create new objects or returns an OutOfMemoryException.Even more if a library is used that returns a container that holds object/array/memory which was "created" by a pool, mostly the user does not now about this and forgets to call Dispose to perform the necessary cleanup.
Sure, always immediately the answer comes: "use a analyzer", but is not the correct solution to prevent these errors if they could be solved more elegantly. Because analyzers need to be installed. If the developer does not install the analyzer there will be no message. Sure, you cannot prevent the developer from anything, buth with the introduction of nullability we can immediately see how many developers in our company really don't take care about nullable values. The same is true for dispose, because people think with the introduction of garbage collection no one needs to take care about memory.
Especially in situations where you have many unmanaged resources you can easily forget to Dispose() or place a using around the unmanaged resource.
Proposal
Especially for the case as shown above, I propose to enable the possibility to have a deterministic finalizer (if not destructor) that is called when the stack is cleaned up. In the end something like C++ CLI with the !. The proposal suggests this for
class
es andstruct
s. Sure, if objects are returned that might get cleaned up at the end of a method and special treatment needs to take place. And is maybe an issue that needs further clarification, and idea would be to have a implicit copy constructor as in C++, but a more C#-like style would also be sufficient.I know that there have been proposals in this direction but I couldn't find them. One reason for structs not to have a finalizer (deterministic or not) was that it's not clear how to keep track of finalization when the object is returned by a method, but I would say that this is a solvable problem. Either by reference counting or some other method (which have to be discussed).
One problem is that I'm not even able to solve the problem myself, because I don't have the necessary language support. Our code is cluttered with usings for scenarios where we have a lot of unmanaged resources to tidy up. In many situations we have 15 usings in a row (e.g. allocate GPU memory). Even here we had to introduce SafeHandles.
Furthermore for my current situation I have an already existing struct for performance and memory reason that are allocated at least 10000 times a second that I could fill with memory from the memory pool. The struct is returned by a method and the user could currently rely on the GC to do the cleanup. Introducing an IDisposable would be a breaking change now.
Just for clarification, event though it is a destructor it should be clear that only the destrcutor is calles deterministically. The real object finalization is still up to the GC. Also the destructor does not hook into the finalization queue. So to say it is a stack-type cleanup.
Open Questions
It has to be clarified how objects are returned and how to keep track of the destruction.
What about this Dispose? Does the destructor simply call Dispose() as it is done by the Finalizer?
Drawbacks
Currently none, since old structs and classes do not support destructors there is no impact.
Only newer structured will benefit from this behavior. So there is no breaking change.
Advantages
Deterministic object cleanup for high performance and memory intensive scenarios.
In my opinion also helps to improve readability.
Beta Was this translation helpful? Give feedback.
All reactions