Why does the Dispose pattern in C# not work more like RAII in C++
Asked Answered
S

4

10

So I was just reading about the RAII pattern for non garbage collected languages, and this section caught my eye:

This limitation is typically encountered whenever developing custom classes. Custom classes in C# and Java have to explicitly implement the dispose method in order to be dispose-compatible for the client code. The dispose method has to contain explicit closing of all child resources belonging to the class. This limitation does not exist in C++ with RAII, where the destructor of custom classes automatically destructs all child resources recursively without requiring any explicit code.

Why is it that C++ can correctly track these resources that are allocated in the RAII pattern, but we don't get this lovely Stack Unwinding with the C# using construct?

Shipe answered 29/8, 2013 at 14:38 Comment(9)
Related read: blogs.msdn.com/b/brada/archive/2005/02/11/371015.aspxNakasuji
You know how many years of auto_ptr, shared_ptr and similar things it took for C++ to be able to correctly use the RAII idiom with collections? :-)Gerena
@Gerena you mean, how many years it took the C++ community to embrace that? Because those things have been in the language all along.Secretion
@R.MartinhoFernandes They most certainly were not. I started using C++ in the late 1980s, and the STL was far from existing.Benedicite
The STL doesn't provide functionality needed for RAII. RAII is part of the core language, not the library.Secretion
@R.MartinhoFernandes If the C++ Standard Library doesn't give the instruments necessary to use RAII in an easy way, how should/could programmers use it? By rewriting it entirely? Until C++11 and unique_ptr, the automatic ptr of C++ weren't compatible with the collections of C++ (I always thought this was the stupidest thing in the world... You have the two main instruments of a C++ programmer, RAII pointers and collections... and they can't speak between themselves!)Gerena
@R.MartinhoFernandes I know, still that isn't C++, it's C++ + something. C++ by itself had incomplete and not-completely-compatible libraries. We can play semantics all the day with "but you should have implemented RAII by yourself", and it's true that C++ as syntax was ok, but the supporting "standard" libraries weren't on par, and fortunately C# didn't need 10 years to have coherent libraries at least for containers and references.Gerena
@Gerena Does C#? How do you handle a vector of pointers in C++ without the right tools? You delete them all manually? How do you handle a List<Stream> in C#? You close (or using) them all manually? Seems quite similar to me. How do you handle a class that has a List<Stream> member?Secretion
What a good question! There is a Dispose pattern in C#. Makes things ugly and makes me wonder about the advantage of GC languages. So better related reads: msdn.microsoft.com/en-us/library/fs2xkftw.aspx, #899328Agog
N
13

Suppose an object O is composed of two resource-owning objects R and S. What happens if O is destroyed?

In C++ with RAII, objects can own other objects such that one object's destruction is necessarily coupled to the other's. If O owns R and S -- by storing them by value, or by owning something which in turn owns R and S (unique_ptr, container that stores R and S by value), then destruction of O necessarily destroys R and S. As long as the destructors of R and S properly clean up after themselves, O doesn't need to do anything manually.

In contrast, C# objects do not have an owner that decides when its lifetime ends. Even if O would be destroyed deterministically (which it generally isn't), R and S might be reachable by another reference. What's more, the way O refers to R and S is the same way any other local variable, object field, array element, etc. refer to R and S. In other words, there's no way to indicate ownership, so the computer can't decide when the object is supposed to be destroyed and when it was only a non-owning/borrowed reference. Surely you would not expect this code to close the file?

File f = GetAFile();
return f; // end of method, the reference f disappears

But as far as the CLR is concerned, the reference from the local f here is exactly the same as the reference from O to R/S.

TL;DR Ownership.

Nakasuji answered 29/8, 2013 at 14:58 Comment(2)
Assuming that the language you wrote the code snippet in supports move semantics, the return f statement shouldn't dispose of the file. In fact, in C++/CLI, this will work fine and do what's expected. If you won't return f or pass it out of the function, it will get disposed properly. It basically does what you'd expect it should do, given semantics of C++.Denature
@KubaOber But C# doesn't, and it lacks several prerequisites to adding a sane ownership model. That's exactly the point of my answer.Nakasuji
P
3

Because to implement this correctly in C#, you'd need to somehow mark which objects the class owns and which objects are shared. Perhaps a syntax similar to this one:

// NOT VALID CODE
public class Manager: IDisposable
{
     // Tell the runtime to call resource.Dispose when disposing Manager
     using private UnmanagedResource resource;
}

My guess is they decided to not go down that road because if you have to mark the objects you own, you have to write code about it. And if you have to write code about it, you can just write it in the Dispose method, calling Dispose for the objects you own :)

In C++, object ownership is usually very clear - if you hold the instance itself, you own it. In C# you never hold the instance itself, you always hold a reference, and a reference can be something you own or something you use - there is no way to tell which is true for a specific instance.

Periodontal answered 29/8, 2013 at 14:52 Comment(1)
In a properly-designed language/framework, marking objects that a class owns would avoid the need to write code to clean up those objects. Field annotations wouldn't eliminate the need to write disposal-cleanup code in all cases, but would take care of a lot of them. Unfortunately, making things work properly would require a means of requesting that a particular class method should be thrown if a constructor throws an exception. Without that ability, there's no way for a base class to avoid resource leaks if a derived-class constructor throws.Isodynamic
T
2

In C++, objects have definite lifetimes. Automatic variables' lifetimes end when they go out of scope, and dynamically-allocated objects' lifetimes end when they get deleted.

In C#, most objects are dynamically allocated, and there is no delete. Therefore, objects don't have a defined point in time when they are "deleted". The closest thing you have, then, is using.

Tophet answered 29/8, 2013 at 14:43 Comment(2)
That's not what the question is about. The question is about the composition of objects that manage resources in a single object. In C++, the composing object frees the resources automatically with no code, in C# you have to free them explicitly in Dispose.Periodontal
@Periodontal Fair enough, that is a huge bonus C++ has. :-DTophet
H
1

The short answer: When was the last time you cleaned up your own memory allocations/resource allocations in a Java/C# destructor? In fact, when was the last time you remember writing a Java/C# destructor?

Since you are responsible for cleaning up after yourself in C++, you HAVE to do the cleanup. Thus, when you stop using a resource (if you have written good quality code), it will immediately be cleaned up.

In managed languages, the garbage collector is responsible for doing the cleanup. A resource your allocate could still be there long after you stopped using it (if the garbage collector is implemented poorly). This is a problem when the managed objects are creating unmanaged resources (e.g. database connections). Which is why the Dispose methods exist - to tell those unmanaged resources to go away. Since the destructor doesn't get called until the garbage collector cleans up the memory, doing the cleanup there would still leave the (finite) resource open for longer than it needed to be.

Hedger answered 29/8, 2013 at 14:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.