I have been writing in C# 4.0 a lot lately and trying to write as lean as possible. As such, I have not been using the classic try/catch
blocks and using
statements as often.
I understand the general function of .Net's garbage collection and exception handling - I want to bulletproof my code and be efficient with objects, but I am trying to achieve this with the minimum amount of 'extra code' (since 'bloat' pissed people off).
And to be clear, I understand using()
is fundamental for relinquishing resources to handles and other code and its relationship to the IDisposable
interface.
But I am trying to get a better grip about how assertive a programmer should be in exception handling objects.
Are there key places in code you'd say try/catch
blocks and/or using
statements are inarguably necessary? Within the contexts of garbage collection and exception handling, what common-use objects/scenarios would you recommend explicit attention?
Should you simply make a catch block for every possible exception an object might through? How deep should they be nested? Are there steps I can take to proactively collect garbage (for example, to combat the example in comments of waiting for Dispose()
to be called)?
6 Answers 6
using
is not a "bloat", it's necessary to free resources wrapped into IDisposable
objects. using
is compiled into try...finally
, with a call to the Dispose
method in the finally
section. For example, when StreamReader and StreamWriter are used to read and write files, respectively - their Dispose
method automatically closes the file. Another example is SqlConnection
- its Dispose
method must be called to close the connection, and a using
statement is a clean way to do it.
Regarding try...catch
, their use in .NET does not differ from exception handling in C++ or Java. Exception handling decreases performance a bit, but that whether this is significant can only be determined by profiling the code. Exception handling must not be used for changing control flow, and it"s also not wise to use it to swallow exceptions. These and other important properties of exception handling can be found in OOP/C++/Java/... textbooks.
-
My understanding is that .Net's garbage collection will cleanup IDisposable stuff on its own, and that these statements are to help programmers be assertive and manually control when and how they want it done. If that's the case, I'm trying to figure out the places its most important for me to do the work, and the places its safe to let the .Net runtime be the whiz-kid it's supposed to be.one.beat.consumer– one.beat.consumer2011年12月13日 19:24:13 +00:00Commented Dec 13, 2011 at 19:24
-
5You have the wrong understanding. See my answer.
IDisposable
has nothing directly to do with garbage collection or memory. It is for cleaning up non-memory resources that also need to be cleaned up. Memory will take care of itself when an object goes out of scope, and trying to do it manually is not typically useful or necessary.mquander– mquander2011年12月13日 19:24:44 +00:00Commented Dec 13, 2011 at 19:24 -
2Please be careful: for example in case of
SqlConnection
, you must use ausing
statement, or callDispose
directly, to close the DB connection. The garbage collector won't do this for you: "If the SqlConnection goes out of scope, it won't be closed. Therefore, you must explicitly close the connection by calling Close or Dispose." --source: msdn.microsoft.com/en-us/library/…kol– kol2011年12月13日 19:27:35 +00:00Commented Dec 13, 2011 at 19:27 -
@Kol - thank you for your response. I know what the
using
statement means and how it ties into IDisposable. Your SqlConnection thought is more like what I'm getting at... where the garbage collector will not be enough. things that need manual attention. And in terms of try/catch, how assertive a programmer should be about catching possible exceptions simply because the object is capable of throwing them.one.beat.consumer– one.beat.consumer2011年12月13日 19:52:03 +00:00Commented Dec 13, 2011 at 19:52 -
1@one.beat You can easily tell which objects need manual disposing. They implement IDisposable. That means the API designer is telling you to dispose the object. Follow the instructions.MarkJ– MarkJ2011年12月13日 20:13:18 +00:00Commented Dec 13, 2011 at 20:13
try/finally
and using
are almost totally orthogonal concerns to memory leaks. You should use using
or try/finally
when you have non-memory resources, like a lock, a handle, or a connection, and you need to make sure those are cleaned up.
There are very few or no common cases in .NET where you need to pay explicit attention to avoiding memory leaks. The only way you'll wind up with a memory leak if if you're retaining a reference to something old and you don't realize it; there are a few ways this can happen, like if that thing has an event you didn't unsubscribe from. But there's nothing mysterious.
-
Thanks for the reply. I guess not "leaks" per se, but objects no longer needed lingering in memory? Isn't the
StreamWriter
scenario a common one where without a using statement it could potentially linger around in memory until the garbage collector decides its ready for the trash can? Perhaps I need to relearn what I thought I knew. :(one.beat.consumer– one.beat.consumer2011年12月13日 19:28:09 +00:00Commented Dec 13, 2011 at 19:28 -
2No, definitely not! The problem is not that the
StreamWriter
is lingering in memory. The problem is (e.g. if you're writing to a file) that theStreamWriter
, when opened, told Windows that it was taking control of a file, and never relinquished it when it was done, which means that other processes are probably locked out of writing to the same file. CallingDispose
in this case releases the file handle. Whether or not theStreamWriter
is in memory hardly matters.*mquander– mquander2011年12月13日 19:30:21 +00:00Commented Dec 13, 2011 at 19:30 -
* (Note: This is 95% of the truth. The other 5% is that when the garbage collector actually collects the object, the finalizer will run, and when the finalizer runs, it will call
Dispose
if you didn't already callDispose
yourself. However, since file handles and other similar things are important resources, it's bad to just wait and hope that the garbage collector will get around to it, so you should callDispose
yourself.)mquander– mquander2011年12月13日 19:32:30 +00:00Commented Dec 13, 2011 at 19:32 -
Thanks Kor for clarifying a bit... its the 5% I'm getting at... and items the garbage collection cannot automatically dispose (SqlConnection, etc.). Those are still items in memory, no? As well as locked in resources.one.beat.consumer– one.beat.consumer2011年12月13日 19:53:32 +00:00Commented Dec 13, 2011 at 19:53
-
1No. The garbage collector works exactly the same way with a SqlConnection as it does with any other heap-allocated object. It will remove it from memory whether or not you call
Dispose
. CallingDispose
has absolutely zero effect on whether or not the garbage collector will remove your object from memory or when. The only difference is that if you callDispose
early, the other important non-memory resources will be cleaned up early.mqp– mqp2011年12月13日 19:57:54 +00:00Commented Dec 13, 2011 at 19:57
using
is only allowed by the language for situations where you should almost certainly be doing it - that is, classes which implement IDisposable
. You're free to write code that implements IDisposable
needlessly if you really want to, but almost all libraries will only do it for a reason - because the object needs to be cleaned up. You can write the dispose logic yourself if you want to, but there is really no benefit because using
does exactly the same thing, it's just clearer.
Your definition of 'bloat' is an interesting one. While I understand that boring plumbing code is undesirable, that just makes it a candidate for walling up in a base class, not excluding it altogether. If boring code is proliferating, it can be an indication that you are violating DRY and should aim to refactor so that you specify the behaviour only once for everything that needs it.
-
I've not seen try/catch/finally and using statements in base classes very often. quite often im writing logic in an MVC controller action and I use db persistence objects, services, etc. and trying to find the cleanest way to handle exceptions and garbage without adding tons of catch blocks nested down the page... i'm not violating dry at all.one.beat.consumer– one.beat.consumer2011年12月13日 20:00:02 +00:00Commented Dec 13, 2011 at 20:00
Every exception gets handled. You just get to choose if it kills your program or not, and if not, what you want to do after that. I regularly write code that throws exceptions all up and down the stack, catches the ones that it wants to recover from, and has a simple main method:
public static int main(string args[])
{
try {
DoSomethingUseful(args);
return 0;
} catch (Exception e) {
Console.Write("Uncaught exception percolated to top level: " + e);
}
return 1;
}
Nested levels of try catch must be avoided if all they do is nothing but log the exceptions. If object supports IDisposable then it must be wrapped in using block.
Exceptions must be caught mostly on root entry points, instead catching and rethrowing at various levels. It just makes code little more complex to analyze stack traces.
Try-finally must be used only if resource object does not support IDisposable, or to turn some Boolean switches or to synchronize threads.
Don't ever use exceptions as part of logic, for example if I catch exception a then follow method b else follow method c, something like that. Other example include using exception in place of return type. Imagin you have ValidateUserPass method that returns void and throws exception instead of returning proper result. I know it is argueous but invalid logic is a user error and exception should mostly be used for system or network failure.
Do not throw generic exception Exception, but use proper encapsulated class for it.
At root level, catch all exceptions because it must be logged, and at intermediate level, do not catch exception unless you want to continue the logic in alternate way.
For example,
try{
connectToDevice()
return;
}catch(IOException ex){
}
connectToDatabase();
Here if I will catch all exception then I will eat up or catch wrong exception if I will put all in try block. Here in case my device connection fails I want to connect to database and work in offline mode. This is part of the logic only if device fails to connect.
But if user has entered a null as an address string, that should not be caught here because it is not an error to connecting device. My null pointer exception should be caught at root level, and not in this code.
For logging, catch all exceptions.
For alternative branch of execution, catch only one type of exception.
-
+1 Thank you. This is a little more what I was looking for. Some people have said to me 'catch everything an object might throw', others say 'catch
e
only at the top for clean code', etc... I'm trying to learn best practices... exception as logic is no concern here, but your first three comments are helpful. thank you.one.beat.consumer– one.beat.consumer2011年12月13日 19:57:34 +00:00Commented Dec 13, 2011 at 19:57 -
I have updated my answer.Akash Kava– Akash Kava2011年12月14日 04:35:49 +00:00Commented Dec 14, 2011 at 4:35
-
"Don't ever use exceptions as part of logic,", cancellationTokens are designed to throw when cancelled. So I think it is exaggerated to never use exceptions for logic. I think avoid whenever possible would be a better claim.JonasH– JonasH2022年07月29日 08:42:43 +00:00Commented Jul 29, 2022 at 8:42
To address the part of your question about what exceptions you should catch, have a look at this article:
How to Design Exception Hierarchies
Despite the title, it's very useful in understanding the different types of exceptions. There are three types, and they should be handled in three different ways:
- Usage error. This is due to an error in coding - fix your code.
- Logical error. An unavoidable condition, such as file not found - handle the exception.
- System failure. A system error such as out of memory - shut down the program.
(File not found is unavoidable because even if you call File.Exists(), it's possible someone could delete the file just before you access it. So to really be correct, you still need to handle it.)
The only case where you definitely need try/catch blocks is for logical errors. In those cases, you can't guarantee avoiding the exception, so you need to handle it.
Explore related questions
See similar questions with these tags.
try/catch
andusing
?