Sie sind auf Seite 1von 40

Effective .

NET Framework based


Exception
Development:
Handing and Memory
Management
Brad Abrams
Lead Program Manager
Common Language Runtime Team
Microsoft Corporation

brada@microsoft.com http://blogs.msdn.com/brada
Agenda
 Exception
“Cleaner, more elegant, and wrong.”
Raymond Chen (http://blogs.msdn.com/oldnewthing/)

 Memory Management
“I'm addicted to deterministic destruction”
Chris Sells (http://www.sellsbrothers.com/)
Exception Handling
Questions
 Throwing an exception
 What exception to throw?
 What data to include in the exception?
 Managing resources
 What resources need to be cleaned up?
 When should they be cleaned up?
 Catching an Exception
 When to catch an exception?
 How to report the error?
 What to do with the control follow: terminate
the app, abort the transaction, ignore the
exception, disable some functionality, etc.?
When to Throw?
 Exceptions rather than error codes
 Robust: failures get noticed
 Your method is defined to do
something…
 If it succeeds in performing its purpose,
return
 If it fails to do what it was written to do,

throw an exception
What to throw?
 Use or subclass existing exceptions
if at all possible
 Only create separate classes if you
think developers will handle the
exception differently
try
try {{
//some
//some operation
operation
}}
catch
catch (FileNotFoundException
(FileNotFoundException fe) fe) {{
//do
//do some
some set
set of
of work
work
}}
catch
catch (DriveNotFoundException
(DriveNotFoundException be) be) {{
//do
//do some
some other
other set
set of
of work
work
}}
Throwing an Exception
 Do not just map error codes on to a
single exception with an error code
property (e.g., the WMIException)
 Use separate exception types
 Error Messages
 Consider localization
 Use a complete sentence (end in a
period)
 Don’t expose privacy related information
(such as file paths)
Performance
 Minimize the number of exceptions you
throw in your API’s success code-paths
 You don’t pay for exceptions until you throw in
managed code
 Throwing exceptions degrades performance
 Perf counters tell you exactly how many
exceptions your application is throwing
 Only an issue when using exceptions as control
flow
 Consider providing a way to avoid an
exception being thrown
Performance (continued)

int
int i;i;
try
try {{
ii == Int32.Parse(“123”);
Int32.Parse(“123”);
}}
catch
catch (FormatException
(FormatException )) {{
Console.WriteLine
Console.WriteLine (“Invalid”);
(“Invalid”);
}}
int
int i;
i;
if
if (!Int32.TryParse
(!Int32.TryParse (“123”,
(“123”, out
out i))
i))
{{
Console.Writeline(“Invalid”);
Console.Writeline(“Invalid”);
}}
Managing Resources
 You should use try..finally 10 times
as often as try..catch
 Catches eat exceptions making it hard
to debug
 Finally allows you to clean up, but let
the exception continue
Managing Resources
try
try {{
.. .. ..
}}
catch
catch (DivisionByZeroException
(DivisionByZeroException e) e) {{
//
// do
do clean
clean up
up work
work
throw
throw new
new BetterException
BetterException (message,
(message, e);
e);
}}

 You may catch exceptions to re-throw them with a clearer


name
 Typical at an “API” boundary
 Always nest the underlying exception
 Catch-and-rethrow has many of the benefits as try..finally
 But, be aware of debugging issues with catch..throw new() and
catch..throw;
 Generally, cleanup code should go in finalizer
Catching Exceptions
 Do not catch and eat exceptions
 Exceptions should be handled only where there
is enough context to do the right thing
 That generally means exceptions should be
caught as high in the application as possible
 Mistake – catch the exception, report the
error and rethrow it.
 Only catch where you can handle it
 Mistake – catch the exception, turn it into
a bool pass/fail and return the bool 
Catching Exceptions
 Consider including a try/catch at the top of a
thread’s stack if the error can be handled properly
 Unhandled exceptions at the top of the main thread will
terminate the app
 In 2.0, unhandled exceptions at the top of the stack on
any thread will terminate the app
 But avoid catch blocks in finalizers
 Be aware: In many cases it is “appropriate” to let the app
terminate
 Be aware of (but ignore) exceptions that don’t
inherit from System.Exception
 Allowed in V1.0\V1.1, likely addressed in V2.0
catch (Exception e) is
your friend
 Myth: Catching Exception is evil
 This is motivated by a desire to avoid catching low level
exceptions such as OutOfMemoryException, and
StackOverflowException
 Do catch every exception you should handle
 Don’t attempt to catch every exception a method
could throw
 Its ugly, version brittle, and difficult to test
 Catch what you need to handle, let the rest pass
Agenda
 Exception
“Cleaner, more elegant, and wrong.”
Raymond Chen (http://blogs.msdn.com/oldnewthing/)

 Memory Management
“I'm addicted to deterministic destruction”
Chris Sells (http://www.sellsbrothers.com/)
Memory Management
 The GC does an excellent job managing
“managed” memory
 GC doesn’t manage external resources (DB
connections, HWnds, etc.)
 Generational Mark-and-sweep garbage
collection means non-deterministic finalization
 Exact time of finalization is unspecified
 Order of finalization is unspecified
 Thread is unspecified
Resource Management
 If you are encapsulating external
resources:
 Add a finalizer (C# destructor) to guarantee
the resource will eventually be freed
 Provide developers an explicit way to free
external resources
 Formalized in the IDisposable interface
 Signals to users they need to explicitly Dispose of
instances
 Enables C# and VB (2005) using support
Finalizers
Object.Finalize() is not accessible
in C#
 VERY
public
public class different than C++’s
class Resource
Resource
{{
destructors
~Resource()
~Resource() {{ public
public class
class Resource
Resource
...
... {{
}} protected
protected override
override void
void Finalize()
Finalize() {{
}} try
try {{
...
...
}}
finally
finally {{
base.Finalize();
base.Finalize();
}}
}}
}}
Finalizers (2)
 Only implement Finalize on objects that
need finalization
 Finalization is only appropriate for cleanup of
unmanaged resources
 Keeps objects alive an order of magnitude
longer
 Free any external resources you own in
your Finalize method
 Do not throw exceptions in finalizers
 The rest of your finalizer will not run
 Check out Critical Finalizers in 2.0
Finalizers (3)
 Do not block or wait in finalizers
 All finalization for that process could be
stopped
 Only release resources that are held
onto by this instance
 Do not reference other instances
 Will be called on one or more
different threads
Dispose Pattern
 Implement the dispose pattern
whenever you have a finalizer
 Gives developers explicit control
 Free any disposable resources your
type owns in the Dispose() method
 Not just the external resources
 Propagate calls to Dispose() through
containment hierarchies
Dispose Pattern (2)
 Suppress finalization once Dispose() has
been called
 Dispose() should be callable multiple
times without throwing an exception
 The method will do nothing after the first call
 After Dispose() is called other methods on
the class can throw ObjectDisposedException
 Do not assume that Dispose() will be called
 For unmanaged cleanup have a finalizer as well
 Do call your base class’s Dispose(bool)
method if it implements IDisposable
Implementing
public class IDisposable
public class Resource: IDisposable
Resource: IDisposable {{
private
private bool
bool disposed
disposed == false;
false;
pubic
pubic int
int GetValue
GetValue ()
() {{
if
if (disposed)
(disposed) throw
throw new
new ObjectDisposedException();
ObjectDisposedException();
//
// do
do work
work
}}
public
public void
void Dispose()
Dispose() {{
if
if (disposed)
(disposed) return;
return;
Dispose(true);
Dispose(true);
GC.SuppressFinalize(this);
GC.SuppressFinalize(this);
}}
protected
protected virtual
virtual void
void Dispose(bool
Dispose(bool disposing)
disposing) {{
if
if (disposing)
(disposing) {{
//
// Dispose
Dispose dependent
dependent objects
objects
disposed
disposed == true;
true;
}}
//
// Free
Free unmanaged
unmanaged resources
resources
}}
~Resource()
~Resource() {{
Dispose(false);
Dispose(false);
}}
}
Using Statement
 Acquire, Execute, Release pattern
 Works with any IDisposable object
Data access classes, streams, text

readers and writers, network classes,
using etc.
using (Resource
(Resource res
res == new
new Resource())
Resource()) {{
res.DoWork();
res.DoWork();
}} Resource
Resource res
res == new
new Resource(...);
Resource(...);
try
try {{
res.DoWork();
res.DoWork();
}}
finally
finally {{
if
if (res
(res !=
!= null)
null)
((IDisposable)res).Dispose();
((IDisposable)res).Dispose();
}}
Using Statement
 Acquire, Execute, Release pattern
 Works with any IDisposable object
 Data access classes, streams, text
readers and writers, network classes,
Using etc.As Resource
Using res
res As Resource == New
New Resource
Resource ()
()
res.DoWork()
res.DoWork()
End Dim
Dim res
res As
As New
New Resource()
End Using
Using Resource()
Try
Try
res.DoWork()
res.DoWork()
Finally
Finally
If
If res
res IsNot
IsNot Nothing
Nothing Then
Then
CType(res,
CType(res, IDisposable).Dispose()
IDisposable).Dispose()
End
End If
If
End
End Try
Try
Using Statement (2)
 Can you find the “bug” in this code?
 Will input and output always be
closed?
static
static void
void Copy(string
Copy(string sourceName,
sourceName, string
string destName)
destName) {{
Stream
Stream input
input == File.OpenRead(sourceName);
File.OpenRead(sourceName);
Stream
Stream output
output == File.Create(destName);
File.Create(destName);
byte[]
byte[] bb == new
new byte[65536];
byte[65536];
int
int n;
n;
while
while ((n
((n == input.Read(b,
input.Read(b, 0,
0, b.Length))
b.Length)) !=
!= 0)
0) {{
output.Write(b,
output.Write(b, 0, 0, n);
n);
}}
output.Close();
output.Close();
input.Close();
input.Close();
}}
Using Statement (3)
static
static void
void Copy(string
Copy(string sourceName,
sourceName, string
string destName)
destName) {{
Stream
Stream input
input == File.OpenRead(sourceName);
File.OpenRead(sourceName);
Stream
Stream output
output == File.Create(destName);
File.Create(destName);
try
try {{
byte[]
byte[] bb == new
new byte[65536];
byte[65536];
int
int n;
n;
while
while ((n
((n == input.Read(b,
input.Read(b, 0,0, b.Length))
b.Length)) !=
!= 0)
0) {{
output.Write(b,
output.Write(b, 0, 0, n);
n);
}}
}}
finally
finally {{
output.Close();
output.Close();
input.Close();
input.Close();
}}
}}
Using Statement (4)
static
static void
void Copy(string
Copy(string sourceName,
sourceName, string
string destName)
destName) {{
Stream
Stream input
input == File.OpenRead(sourceName);
File.OpenRead(sourceName);
try
try {{
Stream
Stream output
output == File.Create(destName);
File.Create(destName);
try
try {{
byte[]
byte[] bb == new
new byte[65536];
byte[65536];
int
int n;
n;
while
while ((n
((n == input.Read(b,
input.Read(b, 0,0, b.Length))
b.Length)) !=
!= 0)
0) {{
output.Write(b,
output.Write(b, 0, 0, n);
n);
}}
}}
finally
finally {{
output.Close();
output.Close();
}}
}}
finally
finally {{
input.Close();
input.Close();
}}
}}
Using Statement (4)
 Code is correct and much more
readable with using statements
 Types should implement IDisposable to
take advantage of this support

static
static void
void Copy(string
Copy(string sourceName,
sourceName, string
string destName)
destName) {{
using
using (Stream
(Stream input
input == File.OpenRead(sourceName))
File.OpenRead(sourceName))
using
using (Stream
(Stream output
output == File.Create(destName))
File.Create(destName)) {{
byte[]
byte[] bb == new
new byte[65536];
byte[65536];
int
int n;
n;
while
while ((n
((n == input.Read(b,
input.Read(b, 0,0, b.Length))
b.Length)) !=
!= 0)
0) {{
output.Write(b,
output.Write(b, 0, 0, n);
n);
}}
}}
}}
Resource Management
2.0 Feature:
MemoryPressure
 GC.AddMemoryPressure ( int pressure
)
 Useful when you have a disproportionate
ratio of managed, to unmanaged
resources
 GC alters it’s strategy, to increase the
number of collections performed
 GC.RemoveMemoryPressure when your
object is freed, to allow the GC to return
to its standard strategy
Resource Management
2.0 Feature:
class
MemoryPressure
Bitmap {
class Bitmap {
private
private long
long _size;
_size;
Bitmap
Bitmap (string
(string path
path )) {{
_size
_size == new
new FileInfo(path).Length;
FileInfo(path).Length;
GC.AddMemoryPressure(_size);
GC.AddMemoryPressure(_size);
//
// other
other work
work
}}
~Bitmap()
~Bitmap() {{
GC.RemoveMemoryPressure(_size);
GC.RemoveMemoryPressure(_size);
//
// other
other work
work
}}
}}
Resource Management
2.0 Feature:

HandleCollector
HandleCollector keeps track of a limited number of handles
 typically, unmanaged resource handles: HDCs, HWnds, etc
 When you allocate a new handle, call Add.
 When you freeing, call Remove
 As you add to the collector, it may perform a GC.Collect(), to
free existing handles, based on the current count, and the
number of resources available

HandleCollector(string
HandleCollector(string name,
name, int
int initialThreshold,
initialThreshold,
int
int maximumThreshold);
maximumThreshold);
name: allows you to track each handle type separately, if needed
initialThreshold: the point at which collections should begin being performed
maximumThreshold: the point at which collections MUST be performed. This
should be set to the maximum number of available handles
Resource Management
2.0 Feature:
HandleCollector
static readonly HandleCollector GdiHandleType =
static readonly HandleCollector GdiHandleType =
new
new HandleCollector(
HandleCollector( “GdiHandles”,
“GdiHandles”, 10,
10, 50);
50);

static
static IntPtr
IntPtr CreateSolidBrush()
CreateSolidBrush() {{
IntPtr
IntPtr temp
temp == CreateSolidBrushImpl(…);
CreateSolidBrushImpl(…);
GdiHandleType.Add();
GdiHandleType.Add();
return
return temp;
temp;
}}

internal
internal static
static void
void DeleteObject(IntPtr
DeleteObject(IntPtr handle)
handle) {{
DeleteObjectImpl(handle);
DeleteObjectImpl(handle);
GdiHandleType.Remove();
GdiHandleType.Remove();
}}
More Information
 My Blog:
http://blogs.msdn.com/brada

Applied Microsoft .NET Framewor


The SLAR

Designing .NET Class Libraries:


http://msdn.microsoft.com/netframework/programming/classlibraries/

FxCop is your ally in the fight: http://www.gotdotnet.com/team/fxcop/


.NET Framework Resource Management whitepaper
Resource Management with the CLR: The IDisposable Pattern
Special thanks to Brian Harry for significant input in the exceptions section
Back up
Intro to Exception Handling
in VB
 VB .NET has structured Exception Handling
Try
Obj.DoSomeWork
Catch e As Exception
LogError(e)
Finally
Obj.Dispose
End Try
 Can “Throw” an exception
Throw New System.Exception(“Error Message”)
 Enhanced Exception object
 Tracks nested exceptions

 Provides StackTrace to help pinpoint error


On Error vs.
Try/Catch/Finally
Try
fReRaise = False
fReRaise
Try = False
'<code that may fail>
'<code that may fail>
OnError GoTo ErrHandler
OnError
Catch
GoTo
ex Asthat
'<code Exception
ErrHandler
may fail>
Catch ex
'<code
'<error
Asthat
handling
Exception
code> may fail>
'<error
GoTo CleanUp handling code>
ErrHandler:
Finally
Finally
'<clean up>
ErrHandler:
If '<condition
If'<clean
End Try
'<conditionup> we can
we can handle> Then handle> Then
'<error
'<error handling
handling code> code>
Else
Else
fReRaise = True
fReRaise = True
End If
End If
CleanUp:
CleanUp:
If fReRaise Then Err.Raise errNum
If fReRaise Then Err.Raise errNum
VB 2005 Exception
 Exception AssistantSupport
 A dialog pops up when an exception occurs in your application that
describes the exception type and the common ways reasons for it being
thrown. You can set any exception type you like to be a “first chance
exception” which would bring up the dialog in the first place it
encountered this exception via the DebugàExceptions menus.  
 Overlapping Exception Warnings
 VB warns when you’ve caught a supertype exception before its subtype
(thus indicating dead code.)
 IDispose spit
 Visual Basic will now spit the IDisposable pattern when you type
Implements IDispose and commit.  
 Unhandled Exception event
 VB now has application events which you can access by opening the
app designer, selecting the “view application events” button, and using
the drop downs to navigate to the UnhandledException event.
 Using statement
 In 2005, VB will support it too.
 Application tracing
 VB now supports an extremely easy way to do application logging. Just
type My.Application.Log.WriteException() and go from there…
Using Exceptions:
Creating new Exceptions
(continued)
 Every exception should have at
least the top three constructors
public
public class
class XxxException
XxxException :: YyyException
YyyException {{
public
public XxxException
XxxException ()
() {}
{}
public
public XxxException
XxxException (string
(string message)
message) {}
{}
public
public XxxException
XxxException (string
(string message,
message,
Exception
Exception inner)
inner) {}
{}
protected
protected XxxException
XxxException ((
SerializationInfo
SerializationInfo info,
info,
StreamingContext
StreamingContext context)
context) {}
{}
}}
 Note: making an exception fully serializable implies a
little more work…
Using Exceptions: Bad
Practice
 ArgumentNullException does not follow
this pattern
public
public class
class ArgumentNullException
ArgumentNullException ::
ArgumentException
ArgumentException {{
public
public ArgumentNullException
ArgumentNullException ()
() {}
{}
public
public ArgumentNullException
ArgumentNullException (string
(string paramName)
paramName) {}{}
public
public ArgumentNullException
ArgumentNullException (string
(string paramName,
paramName,
string
string message)
message) {}
{}
}}

 Justification: Argument names are much


more common than message
Using Exceptions: Bad
Practice
 Result: Habit wins out and people
Result: Habit wins out and people
commonly
throw
throw new type:
new ArgumentNullException
ArgumentNullException ("the
("the value
value must
must
pass
pass an
an employee
employee name");
name");

• Rather than:
throw
throw new
new ArgumentNullException
ArgumentNullException ("Name",
("Name", "the
"the value
value
must
must pass
pass an
an employee
employee name");
name");

• We end up with odd error messages such as:


Unhandled
Unhandled Exception:
Exception: System.ArgumentNullException:
System.ArgumentNullException:
Value
Value cannot
cannot be
be null.
null.
Parameter
Parameter name:
name: the
the value
value must
must pass
pass an
an employee
employee name
name

• Lesson: Just follow the pattern!

Das könnte Ihnen auch gefallen