2

I originally come from a C++ background where going out of scope means calling of a destructor and in that destructor is where you clean up everything. The destructor isn't a thing called later at some indeterminate time (like in C#).

New to C# I've been reading a lot about the disposable pattern (because I've made many many mistakes over the past year that resulted in huge amounts of unmanaged resources not being cleaned up resulting in spectacular crashing of programs) and frustratingly it seems like the language made it easier to accidentlly implement incorrectly than it did easy to accidentlly implement correctly.

Take for example:

public void DoSomething() {
    using ClassThatImplementsIDisposable obj = new ClassThatImplementsIDisposable();
    // do stuff
}

In the above (I think) I've implemented usage of a disposable class correctly. Namely that when I go out of scope the Dispose method will correctly be called because I added the using keyword.

But I find while developing that most of the time I remember to add the using keyword, but occasionally I might forget to add it resulting in unmanaged resource issues, which made me wonder:

Are there ever cases where I'd want to create an object that implements IDisposable AND that variable is going to go out of scope (and by going out of scope I also mean when the reference count goes to zero for objects that are passed around) AND I don't want it to dispose? For example:

public void DoSomething() {
    // is there ever a reason to purposefully leave off 'using'
    // here on a variable that will go out of scope
    ClassThatImplementsIDisposable obj = new ClassThatImplementsIDisposable();
    // do stuff
}

if there are cases for the above (which I can't think of any):

  • Seem's to me the C# language would've been safer for programmers if they didn't give us the using keyword (which would mean all IDisposable objects would be implicitly disposed when reference count goes to zero) and instead maybe they give a notusing keyword for that weird case where you don't want to dispose (again asking if there are ever cases for this)
Stanton
  • 904
  • 10
  • 25
  • Indeed there are. [Here's a conversation](https://stackoverflow.com/questions/50912160/should-httpclient-instances-created-by-httpclientfactory-be-disposed) I initiated a few years ago regarding `HttpClient` and `HttpClientFactory` because I smelled some inconsistency. – spender Mar 17 '23 at 15:23
  • 1
    Sure, lots of C# programs out there that complete in a few seconds or less. Calling Dispose() or using the using keyword in them is not going to make a difference that anybody will ever notice. But if your program stays active for much longer then you can't ignore the fact that you're running on an unmanaged operating system that needs help knowing when its objects are no longer used. There is no reference counting in .NET, too inefficient and unreliable due to cycles, you have to add it yourself. – Hans Passant Mar 17 '23 at 15:32
  • Side-note: please don't use code font for emphasis. That's what italics and bold are for. `using` and `Dispose` - fine - "to accidentlly implement incorrectly" is not code. – Jon Skeet Mar 17 '23 at 15:38
  • Common example: returning a `MemoryStream` from a method. (Or other streams, in fact - `File.OpenRead` for example.) – Jon Skeet Mar 17 '23 at 15:40
  • There are some classes for which there is no point disposing as it does nothing eg `MemoryStream` and `DataTable` – Charlieface Mar 17 '23 at 16:28

4 Answers4

2

Of course there are, for example when you construct an IDisposable in a function, return it out and then the caller adds it to an array for safe keeping. As a really degenerate example of that, that's what constructors do.

Seem's to me the C# language would've been safer for programmers if they didn't give us the using keyword (which would mean all IDisposable objects would be implicitly disposed when reference count goes to zero)

Then you need to read up a lot more on how managed memory works. For one thing there is no reference counting, and for the other what you describe already happens today. The point of Dispose is to force it to clean up after itself in a deterministic fashion rather than when the garbage collector decides it's time. This is very important for OS handles like file handles, sockets, Sql Server handles, GDI+ drawing stuff, windows, etc.

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • the case where an object is returned from a method and stored in a list for safe keeping isn't what I'm talking about (because there's still some sort of *handle* to that returned object). I'm trying to find out if you'd ever "not dispose an object that you were not going to have a handle on". – Stanton Mar 17 '23 at 16:55
  • Ah, in that case the correct term is "unrooted", and the answer is still almost always yes. In fact, the number of objects you dispose is a drop in the water compared to the amount of objects you create during normal program execution. Even more, you don't even dispose most of the disposable objects that you create (looking at you, `Task`!) – Blindy Mar 17 '23 at 17:32
1

In VS you could enable various rules, and specifically you could enable CA2000: Dispose objects before losing scope with severity Error to make your build fail in case of IDisposable object not disposed.

To enable it I created a .editorconfig file from Tools > Options > Text Editor > C# > Code Style > Generate .editorconfig, saving it in solution folder.

Then enabled CA2000 adding:

dotnet_diagnostic.CA2000.severity = error

Or simply open the .editorconfig file in VS and use the Analyzers tab.

0

Well you can't prove that something does not exist, so I can't say with certainty that there is no use case.

However, if you don't call Dispose, then at some point (that you don't control) the garbage collector will destroy the object for you. So there's no practical reason NOT to call dispose - you're just handing control of the disposition to the runtime.

There are definite practical reasons FOR calling dispose (proper release of unmanaged resources being a big one).

which would mean all IDisposable objects would be implicitly disposed when reference count goes to zero

They are, just not immediately since it can be done in the background when there is either memory pressure or enough spare resources to do it without bottlenecking the runtime.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • You seem to imply that garbage collection will always happen eventually. This is not true: if there is never memory pressure (at least in Generation 2) then collection may never happen. – Charlieface Mar 17 '23 at 16:29
0

Are there ever cases where I'd want to create an object that implements IDisposable AND that variable is going to go out of scope ... AND I don't want it to dispose?

That would be a misuse of the Disposable pattern.

I won't say no one has ever misused the pattern this way (DbDataReader and friends come to mind as an area where it's tempting), but it's definitely not something you find in every day code.

But something else in the question worries me:

I've made many many mistakes over the past year that resulted in huge memory leaks

IDisposable has NOTHING to do with memory. It is intended for use with unmanaged resources, and in .Net we have a garbage collector that is already there to manage memory.

In fact, most C# classes should have neither a Dispose() method nor a finalizer.

About the only exception to this is event handlers, where a subscription to an event may unintentionally hold a reference to an object, preventing the garbage collector from holding it. In this case, you might use IDisposable to clean this up.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Poor wording on my part. I don't mean memory leak I guess I mean unmanaged resources not being cleaned up. Specifically I do a lot of calls to databases (e.g. `CosmosClient` and `SqlConnection` etc...) – Stanton Mar 17 '23 at 15:33
  • The SqlConnection type is misunderstood. ADO.Net does connection pooling, and when you create an SqlConnection object you aren't creating the actual database connection. You are creating a _wrapper_ that can receive a connection from the pool. For this reason, you should not try to cache or re-use SqlConnection objects in your application, but rather create them new for each time you talk to the database, and then _ensure they are disposed as quickly as possible_, so the connection can be returned to the pool. – Joel Coehoorn Mar 17 '23 at 15:42
  • "That would be a misuse of the Disposable pattern." Not sure how to interpret this. Are you suggesting that the `File.OpenRead` method shouldn't exist, for example? It seems entirely feasible to me that the implementation might have a local variable whose value is returned... that variable is going to be out of scope, but you really don't want the method to dispose of the stream it's about to return. – Jon Skeet Mar 17 '23 at 15:42
  • "IDisposable has NOTHING to do with memory. It is intended for use with unmanaged resources" but commonly unmanaged resources hold memory, and that is why you want to dispose, so comes to the same thing. – Charlieface Mar 17 '23 at 16:27