[go: up one dir, main page]

100% found this document useful (1 vote)
753 views68 pages

Chapter 5-Exceptions and Object Lifetime

The document discusses exceptions in .NET programming. It describes the key elements of exception handling in .NET - throwing exceptions using the "throw" keyword, catching exceptions using try/catch blocks, and properties of the System.Exception class like TargetSite, StackTrace, and HelpLink. It also discusses the base Exception class and system-level exceptions that derive from SystemException.

Uploaded by

Rudresh CM
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
753 views68 pages

Chapter 5-Exceptions and Object Lifetime

The document discusses exceptions in .NET programming. It describes the key elements of exception handling in .NET - throwing exceptions using the "throw" keyword, catching exceptions using try/catch blocks, and properties of the System.Exception class like TargetSite, StackTrace, and HelpLink. It also discusses the base Exception class and system-level exceptions that derive from SystemException.

Uploaded by

Rudresh CM
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 68

Exceptions and Object

Lifetime
GRNICA 2
Ode to Errors, Bugs, and Exceptions
Bugs: An error on the part of the programmer. For example, assume
you are programming with unmanaged C++. If you make calls on a
NULL pointer, overflow the bounds of an array, or fail to delete
allocated memory (resulting in a memory leak), you have a bug.

Errors: Errors are typically caused by the end user of the
application, rather than by those who created application.
For example, an end user who enters a malformed string into a text
box that requires a Social Security number could very well generate
an error if you fail to trap this faulty input in your code base.

Exceptions: Exceptions are typically regarded as runtime
anomalies that are difficult, to prevent. Possible exceptions include
attempting to connect to a database that no longer exists,
opening a corrupted file, or contacting a machine that is currently
offline.
GRNICA 3
The Role of .NET Exception Handling
The .NET platform provides exactly one technique to send and trap
runtime errors: structured exception handling (SEH).

The beauty of this approach is that developers now have a well-
defined approach to error handling, which is common to all
languages targeting the .NET universe.

Therefore, the way in which a C# programmer handles errors is
conceptually similar to that of a VB .NET programmer, and a C++
programmer using managed extensions (MC++).

As an added bonus, the syntax used to throw and catch exceptions
across assemblies and machine boundaries is identical.
GRNICA 4
The Atoms of .NET Exception Handling
Programming with structured exception handling involves the use of
four key elements:
A type that represents the details of the exceptional circumstance
A method that throws the exception to the caller
A block of code that will invoke the exception-ready method
A block of code that will process (or catch) the exception (should it
occur)
The C# programming language offers four keywords ("try",
"catch", "throw", and "finally") that allow us to throw and handle
exceptions.
GRNICA 5
The System.Exception Base Class
All system-supplied and custom exceptions ultimately derive from
the System.Exception base class.
Core Members of the System.Exception Type
GRNICA 6
Throwing a Generic Exception
// Currently, SpeedUp() reports errors using console IO.
public void SpeedUp(int delta)
{
// If the car is dead, just say so...
if(carIsDead)
Console.WriteLine("{0} is out of order....", petName);
else // Not dead, speed up.
{
currSpeed += delta;
if(currSpeed >= maxSpeed)
{
Console.WriteLine("{0} has overheated...", petName);
carIsDead = true;
}
else
Console.WriteLine("=> CurrSpeed = {0}", currSpeed);
}
}
GRNICA 7
Let's retrofit SpeedUp() to throw an exception if the user attempts to
speed up the automobile after it has met its maker
(carIsDead = true).
// This time, throw an exception if the user speeds up a trashed automobile.
public void SpeedUp(int delta)
{
if(carIsDead)
throw new Exception("This car is already dead...");
else
{ ... }
}
A few points of interest. First of all, when we are building a
custom type, it is always up to us to decide exactly what constitutes
an exception, and when it should be thrown.
Here, we are making the assumption that if the program attempts to
increase the speed of a car that has expired, a System.Exception type
should be thrown to indicate the SpeedUp() method cannot continue.
GRNICA 8
Exceptions should be thrown only when a more terminal condition
has been met (for example, the inability to allocate a block of
unmanaged memory, not finding a necessary file, failing to connect
to a database).

Deciding exactly what constitutes throwing an exception is a design
issue you must always contend with
GRNICA 9
Catching Exceptions
When we are calling a method that may throw an exception, we
make use of a try/catch block.
Once we have caught the exception type, we are able to invoke of
the members of the System.Exception type.
After getting the message, we may wish to log this info to a given
file, write the error to the Windows event log, or display the message
to the end user or simply dump the contents to the console window
A try block is a section of code that is checked for any exception that
may be encountered during its scope.
If an exception is detected, the flow of program execution is sent to
the appropriate catch block.
If the code within a try block does not trigger an exception, the catch
block is skipped entirely,
GRNICA 10
// Speed up the car safely...
public static int Main(string[] args)
{
// Make a car.
Car buddha = new Car("Buddha", 100, 20);
// Speed up past the car's max speed to trigger the exception.
try
{
for(int i = 0; i < 10; i++)
buddha.SpeedUp(10);
}
catch(Exception e)
{
Console.WriteLine("\n*** Error! ***");
Console.WriteLine("Method: {0}", e.TargetSite);
Console.WriteLine("Message: {0}", e.Message);
Console.WriteLine("Source: {0}", e.Source);
}
// The error has been handled, continue on with the flow of this app...
Console.WriteLine("\n***** Out of exception logic *****");
return 0;
}
GRNICA 11
The TargetSite Property
The System.Exception.TargetSite property allows us to determine
the name of the method that threw the current exception.
TargetSite does not simply return a string, but a strongly typed
System.Reflection.MethodBase class.
catch(Exception e)
{
Console.WriteLine("\n*** Error! ***");
Console.WriteLine("Class defining member: {0}",
e.TargetSite.DeclaringType);
Console.WriteLine("Member type: {0}", e.TargetSite.MemberType);
Console.WriteLine("Member name: {0}", e.TargetSite);
Console.WriteLine("Message: {0}", e.Message);
Console.WriteLine("Source: {0}", e.Source);
}
GRNICA 12
We make use of the MethodBase.DeclaringType property to
determine the fully qualified name of the class that threw the error
(SimpleException.Car in this case)

MethodBase.MemberType property to identify the type of member
GRNICA 13
The Stack Trace Property
The System.Exception.StackTrace property allows you to identify
the series of calls that resulted in the exception.
catch(Exception e)
{
...
Console.WriteLine("Stack: {0}", e.StackTrace);
}
If you were to run the program, you would find the following line
printed to the console (assuming the application is located under the
C:\MyApps\Exception directory):
Stack: at SimpleException.Car.SpeedUp(Int32 delta)
in c:\myapps\exceptions\car.cs:line 65
at Exceptions.App.Main()
in c:\myapps\exceptions\app.cs:line 21
GRNICA 14
The HelpLink Property
The HelpLink property can be set to point the user to a given URL
or standard Win32 help file that contains more detailed information.
By default, the value managed by the HelpLink property is an empty
string. If you wish to fill this property with a relevant value, we
need to do so before throwing the System.Exception type.

Here is the relevant update to the SpeedUp() method:
if(carIsDead)
{
Exception ex = new Exception("Error information can be found at:");
ex.HelpLink = "http://www.CarsRUs.com";
throw ex;
}
catch(Exception e)
{
...
Console.WriteLine("Help Link: {0}", e.HelpLink);
}
GRNICA 15
class Program
{
static void Main(string[] args)
{
int a = 10, b = 5, c = 5, x, y;
try
{
x = a / (b - c);
}
catch (Exception e)
{
Console.WriteLine("Division by zero");
}
y = a / (b + c);
Console.WriteLine("y=" + y);

}
}
GRNICA 16
class Program
{
static void Main(string[] args)
{
int a = 10, b = 5, c = 5, x, y;
try
{

Exception ex = new Exception("Error info can be found at:");
ex.HelpLink = "Http://www.Cars.com";
throw ex;
x = a / (b - c);
}
catch (Exception e)
{
Console.WriteLine("Division by zero");
Console.WriteLine("class defining member : {0}", e.TargetSite.DeclaringType);
Console.WriteLine("Member type : {0}", e.TargetSite.MemberType);
Console.WriteLine("Member name : {0}", e.TargetSite);
Console.WriteLine("Message : {0}", e.Message);
Console.WriteLine("Source : {0}", e.Source);
Console.WriteLine("Stack : {0}", e.StackTrace);
Console.WriteLine("Help Link : {0}", e.HelpLink);
}
}
}
GRNICA 17
CLR System-Level Exceptions (System.SystemException)
The .NET base class libraries already define a number of exception
classes.
The System namespace defines numerous general exception types
such as ArgumentOutOfRangeException,
IndexOutOfRangeException, StackOverflowException, and so on.
Exceptions that are thrown by methods in the base class libraries are
called system exceptions.
System exceptions generally derive directly from a base class called
System.SystemException, which in turn derives from
System.Exception.

GRNICA 18
public class SystemException : Exception, ISerializable
{
public SystemException();
public SystemException(string message);
public SystemException(string message, Exception innerException);
public string HelpLink { virtual get; virtual set; }
public Exception InnerException { virtual get; }
public string Message { virtual get; virtual set; }
public string Source { virtual get; virtual set; }
public string StackTrace { virtual get; }
public MethodBase TargetSite { get; }
public virtual bool Equals(object obj);
public virtual Exception GetBaseException();
public virtual int GetHashCode();
public virtual void getObjectData(SerializationInfo info, StreamingContext
context);
public Type GetType();
public virtual string ToString();
}
GRNICA 19
Custom Application-Level Exceptions (System.ApplicationException)
The purpose of System.SystemException is to identify the CLR
exceptions.
The .NET base class library defines another System.Exception
derived type named System.ApplicationException:
GRNICA 20
public class ApplicationException : Exception,ISerializable
{
public ApplicationException();
public ApplicationException(string message);
public ApplicationException(string message, Exception innerException);
public string HelpLink { virtual get; virtual set; }
public Exception InnerException { get; }
public string Message { virtual get; }
public string Source { virtual get; virtual set; }
public string StackTrace { virtual get; }
public MethodBase TargetSite { get; }
public virtual bool Equals(object obj);
public virtual Exception GetBaseException();
public virtual int GetHashCode();
public virtual void
GetObjectData(SerializationInfo info, StreamingContext context);
public Type GetType();
public virtual string ToString();
}
GRNICA 21
Building Custom Exceptions,Take One
It is sometimes advantageous to build a strongly typed exception that
represents the unique details of our current problem.

The first step is to derive a new class from
System.ApplicationException
// This custom exception describes the details of the car-is-dead condition.
public class CarIsDeadException : System.Exception
{}
Like any class, we are free to include any custom properties,
methods, or fields that can be used from within the catch block of
the calling logic.

We are also free to override any virtual members defined by our
parent class:
GRNICA 22
// This custom exception describes the details of the car-is-dead condition.
public class CarIsDeadException : System.Exception
{
// This custom exception maintains the name of the doomed car.
private string carName;
public CarIsDeadException(){ }
public CarIsDeadException(string carName)
{
this.carName = carName;
}
// Override the Exception.Message property.
public override string Message
{
get
{
string msg = base.Message;
if(carName != null)
msg += carName + " has bought the farm...";
return msg;
}
}
}
GRNICA 23
// Throw the custom CarIsDeadException.
public void SpeedUp(int delta)
{
// If the car is dead, just say so...
if(carIsDead)
{
// Throw 'car is dead' exception.
throw new CarIsDeadException(this.petName);
}
else // Not dead, speed up.
{ ... }
}
static void Main(string[] args)
{
...
catch(CarIsDeadException e)
{
// Process incoming exception.
}
...
}
GRNICA 24
Building Custom Exceptions, Take Two
public class CarIsDeadException : System.Exception
{
public CarIsDeadException(){ }
public CarIsDeadException(string message) : base(message){ }
// Just in case the CarIsDeadException is generated by
// another exception, the previous exception can be passed in
// as a constructor parameter.
public CarIsDeadException(string message, Exception innerEx)
: base(message, innerEx){ }
}
GRNICA 25
public void SpeedUp(int delta)
{
...
if(carIsDead)
{
// Pass pet name and message as ctor argument.
throw new CarIsDeadException(this.petName + " has bought the
farm!");
}
else // Not dead, speed up.
{... }
}
Now have not provided a private string to hold the pet name, and
have not overridden the Message property.
When you wish to throw an exception of this type, you would send
in all necessary information as a constructor argument.
GRNICA 26
Building Custom Exceptions, Take Three
The exceptions can be categorized as system-level or application-
level types.
If we wish to clearly mark the fact that the CarIsDeadException is a
type thrown by the application itself, we are free to retrofit the type
definition as follows:
public class CarIsDeadException : ApplicationException
{
// Constructors for this custom exception.
public CarIsDeadException(){ }
public CarIsDeadException(string message) : base(message){ }
public CarIsDeadException(string message, Exception innerEx)
: base(message, innerEx){ }
}
GRNICA 27
Handling Multiple Exceptions
In its simplest form, a try block has a single corresponding catch
block.
In reality, we often run into a situation where the code within a try
block could trigger numerous possible exceptions.
// Test for bad parameter.
public void SpeedUp(int delta)
{
// Bad param? Throw system supplied exception!
if(delta < 0)
throw new ArgumentOutOfRangeException("Speed must be greater than zero!");
if(carIsDead)
{
// Throw 'Car is dead' application exception.
throw new CarIsDeadException(this.petName + " has bought the farm!");
}
...
}
GRNICA 28
The calling logic would look like this:
// Here, we are on the lookout for multiple exceptions.
try
{
for(int i = 0; i < 10; i++)
buddha.SpeedUp(10);
}
catch(CarIsDeadException e)
{
Console.WriteLine("Method: {0}", e.TargetSite);
Console.WriteLine("Message: {0}", e.Message);
}
catch(ArgumentOutOfRangeException e)
{
Console.WriteLine("Method: {0}", e.TargetSite);
Console.WriteLine("Message: {0}", e.Message);
}
GRNICA 29
When we are constructing multiple catch blocks for a single try
block, we must be aware that when an exception is thrown, it will
be processed by the "nearest available" catch.
// This code will not compile!
try
{
for(int i = 0; i < 10; i++)
buddha.SpeedUp(10);
}
catch(Exception e)
{...}
catch(CarIsDeadException e)
{...}
catch(ArgumentOutOfRangeException e)
{...}
This exception handling logic generates compile-time errors.
GRNICA 30
The problem is that the first catch block can handle anything
derived from System.Exception, including the CarIsDeadException
and ArgumentOutOfRangeException types.

Hence, the final two catch blocks are unreachable

The rule to keep in mind is that make sure our catch blocks are
structured such that the very first catch is the most specific
exception while the final catch is the most general
try
{
for(int i = 0; i < 10; i++)
buddha.SpeedUp(10);
}
catch(CarIsDeadException e)
{...}
catch(ArgumentOutOfRangeException e)
{...}
catch(Exception e) // This will handle any other exception.
{...}
GRNICA 31
Generic Catch Statements
C# also supports a generic catch block that does not explicitly define
the type of exception. Thus, we could implement a catch block as
follows:
// I handle any possible error thrown from a try block.
catch
{
Console.WriteLine("Something bad happened...");
}
This is not the most descriptive manner in which to handle runtime
exceptions, because we have no way to obtain meaningful
information about the error that occurred.
GRNICA 32
Rethrowing Exceptions
It is permissible to "rethrow" an error up the call stack to the
previous caller.

To do so, simply make use of the "throw" keyword within a catch
block. This passes the exception up the chain of calling logic:
try
{
// Speed up car logic...
}
catch(CarIsDeadException e)
{
// Do any partial processing of this error and pass the buck.
// Here, we are rethrowing the CarIsDeadException type.
// However, you are also free to throw a different exception if need be.
throw e;
}
GRNICA 33
The Finally Block
The idea behind a finally block is to ensure that a block of code will
always execute, even if an exception (of any type) interferes with the
normal flow of execution.
GRNICA 34
// Provide a manner to clean up.
public static int Main(string[] args)
{
...
// Try to rev the engine hard!
try
{
// Speed up car logic ...
}
catch(CarIsDeadException e)
{...}
catch(ArgumentOutOfRangeException e)
{...}
finally
{
// This will always occur. Exception or not.
buddha.CrankTunes(false);
}
return 0;
}
GRNICA 35
If we did not include a finally block, the radio would not be turned
off if an exception is encountered.

In a more real-world scenario, when we need to dispose of objects,
close a file, detach from a database , a finally block
ensures a location for proper cleanup.
GRNICA 36
The Last Chance Exception
.NET exceptions cannot be ignored.
One obvious question that may be on your mind is what would
happen if you do not handle an exception thrown your direction.

The result of ignoring the generated error would be highly
obstructive to the end user of the application.
Then what to do with the exception once you have trapped it ?

This is a design issue based on the current project.

We can simply dump the custom message and call stack to the
console.
A more realistic scenario may include freeing up acquired resources
or writing to a log file.
GRNICA 37
Dynamically Identifying Application- and System-Level Exceptions
Assume we wish to generalize our catch blocks in such a way that
all applicationlevel exceptions are handled apart from possible
system-level exceptions:
// This time, make things a bit more general.
try
{
for(int i = 0; i < 10; i++)
buddha.SpeedUp(10);
}
// Any type derived from System.ApplicationException handled here.
catch(ApplicationException e)
{
Console.WriteLine("Caught an app exception!");
Console.WriteLine("Method: {0}", e.TargetSite);
Console.WriteLine("Message: {0}", e.Message);
}
// Any type derived from System.SystemException handled here.
catch(SystemException e)
{
Console.WriteLine("Caught a system-level exception");
Console.WriteLine("Method: {0}", e.TargetSite);
Console.WriteLine("Message: {0}", e.Message);
}
GRNICA 38
Understanding Object Lifetime
Recall that unlike C++, C# programmers never directly deallocate
an object from memory.

Rather .NET objects are allocated onto a region of memory termed
the managed heap, where they will be automatically deallocated by
the runtime at "some time in the future.

A user has to just allocate an object onto to the managed heap using
the new keyword, and the forget the rest.
Once "new-ed," the CLR removes the object when it is no longer
needed.

Next question: How does the runtime determine when an object is
"no longer needed"?

The short answer is that the runtime removes an object from the
heap when it is unreachable by the current application.
GRNICA 39
// Create a local Car object.
public static int Main(string[] args)
{
// Place an object onto the managed heap.
Car c = new Car("Viper", 200, 100);
...
} // If c is the only reference to the Car object, it may be destroyed when
Main() exits.
Once the application shuts down, this reference is no longer valid,
and therefore is a candidate for garbage collection.
GRNICA 40
The CIL of "new"
when the C# compiler encounters the "new" keyword, it will emit a
CIL "newobj instruction to the code module.
First, understand that the managed heap is more than just a raw
chunk of memory accessed by the CLR.
The .NET garbage collector is quite a tidy housekeeper, given that it
will compact empty blocks of memory for purposes of optimization.
To help this, the managed heap maintains a pointer that identifies
exactly where the next object will be placed on the heap itself.
GRNICA 41
These things being said, the newobj instruction informs the CLR to
perform the following sequence of events:
Calculate the total amount of memory required for the object
to be allocated. If this object contains other internal objects, they
are also factored into the equation. As well, the memory
required for each base class is also taken into account.
The CLR then examines the managed heap to ensure that there
is indeed enough room to host the object to be allocated. If so,
the type's constructor is called, and the caller is returned a
reference to the type in memory, which just happens to be
identical to the last position of the new object pointer.
Finally, before returning the reference to the caller, the CLR
will advance the new object pointer to point to the next available
slot on the managed heap.
GRNICA 42
The details of allocating objects onto the managed heap
GRNICA 43
The Basics of Garbage Collection
If the managed heap does not have sufficient memory to allocate a
new object, a garbage collection will occur.
Assume that the CLR performs a garbage collection, & the
question is how the runtime is able to determine an object on the
heap is "no longer needed."

To understand the details, we need to be aware of the notion of
application roots.

A root can be understood as a variable in our application that
points to some area of memory on the managed heap.
GRNICA 44
A root can fall into any of the following categories:
References to global objects
References to static objects
References to local objects within a given method
References to object parameters passed into a method
Any CPU register that references a local object
When a garbage collection occurs, the runtime will investigate all
objects on the managed heap to determine if it is still in use in the
application.
To do so, the CLR will build an object graph, which represents
each object on the heap that is still reachable.
once the garbage collector determines that a given root is no longer
used by a given application, the object is marked for termination.
GRNICA 45
After the objects have been swept from memory, the memory on the
heap is compacted

This will cause the CLR to modify the set of application roots to
refer to the correct memory location.

Finally, the new object pointer is readjusted to point to the next
available slot.
GRNICA 46
Finalizing a Type
The .NET garbage collection scheme is nondeterministic in nature.

Although this approach to memory management can simplify the
coding efforts at some levels, but the objects possibly holds onto
unmanaged resources longer than necessary.

When we build .NET types that interact with unmanaged resources,
we wish to ensure that this resource is released in a timely manner
rather than by the .NET garbage collector.


To account for such situations, one choice we have is to override the
virtual System.Object.Finalize() method.

Assume we have a type that has acquired various unmanaged
resources and wish to support a custom version of
System.Object.Finalize() to ensure proper cleanup of the internal
unmanaged resources.
GRNICA 47
But the the C# language does not allow us to directly override the
Finalize() method using standard C# syntax:
public class FinalizedCar
{
// Compile time error!
protected override void Finalize(){ }
}
Rather, when we have to configure our custom C# class types to
override the Finalize() method, we make use of the following
destructor syntax to achieve the same effect:
// This Car overrides System.Object.Finalize().
class FinalizedCar
{
~FinalizedCar()
{Console.WriteLine("=> Finalizing car..."); }
}
GRNICA 48
The C# destructor-style syntax can be understood as a shorthand
notation for the following code:
protected override void Finalize()
{
try
{ Console.WriteLine("=> Finalizing car..."); }
finally
{ base.Finalize(); }
}
When c# classes make use of unmanaged resources that are
typically obtained by directly calling into the Win32 API, then the
custom implementation of Finalize() method can be used.
GRNICA 49
(Indirectly) Invoking System.Object.Finalize()
The .NET runtime will trigger a garbage collection when it requires
more memory than is currently available on the managed heap.
If we have created an application that is intended to run for lengthy
periods of time, garbage collections may occur over the course of
the application's lifetime.
It is important to note that finalization will automatically take place
when an application domain is unloaded by the CLR.
GRNICA 50
namespace SimpleFinalize
{
class FinalizedCar
{
~FinalizedCar()
{ Console.WriteLine("=> Finalizing car..."); }
}
class FinalizeApp
{
static void Main(string[] args)
{
Console.WriteLine("***** Making object *****");
FinalizedCar fc = new FinalizedCar();
Console.WriteLine("***** Exiting main *****");
}
}
}
GRNICA 51
The Finalization Process
The role of a finalizer is to ensure that a .NET object can clean up
unmanaged resources.
Thus, if we are building a type that does not make use of
unmanaged entities, finalization is of little use.
The practical reason to avoid supporting a finalize method:
Finalization takes time.
GRNICA 52
When we place an object onto the managed heap using the new
operator, the runtime automatically determines if our object
supports a custom Finalize() method.
If so, the object is marked as finalizable, and a pointer to this object
is stored on an internal queue, named as finalization queue.
When the garbage collector determines it is time to free an object
from memory, it examines each entry in the finalization queue, and
copies the object from the heap to another CLR- managed
structure termed the finalization reachable table.
At this point, a separate thread is spawned to invoke the Finalize()
method for each object on the finalization reachable table at the
next garbage collection.

GRNICA 53
Building an Ad Hoc Destruction Method
The process of finalizing an object is quite time consuming. Ideally,
we should design your objects in such a way that, they do not need
to be marked as finalizable in the first place.
When a type manipulates unmanaged resources, we need not
ensure that they are released in a timely and predictable manner. To
support a C# destructor there are better ways.
One alternative is to define a custom ad hoc method. This is called
method Dispose().
The assumption is that when the object user is finished using
the type, it manually calls Dispose() before allowing the object
reference to drop out of scope.
In this way, our objects can perform any amount of cleanup of
unmanaged resources without waiting for the garbage collector to
trigger the class' finalization logic:
GRNICA 54
// Equipping our class with an ad hoc destruction method.
public car
{

// This is a custom method we except the object user to call manually.
public void Dispose()
{ /* clean up your internal unmanaged resources.*/}
}
The name of this method is completely up to the user. While
Dispose() is a very common name, if we are building a class that
manipulates a physical file, we may opt to call Close().
GRNICA 55
The IDisposable Interface
In order to provide symmetry among all objects that support an
explicit destruction routine, the .NET class libraries define an
interface named IDisposable.
public interface IDisposable
{
public void Dispose();
}
// Implementing IDisposable.
public Car : IDisposable
{
...
// This is still a custom method we expect the object user to call manually.
public void Dispose()
{
// Clean up your internal unmanaged resources.
}
}
GRNICA 56
Using this approach, we provide the object user with a way to
manually dispose of acquired resources as soon as possible, and
avoid the overhead of being placed on the finalization queue.
The calling logic is straightforward:
namespace DisposeMe
{
public class App
{
public static int Main(string[] args)
{
Car c1 = new Car("Car one", 40, 10);
c1.Dispose();
return 0;
} // C1 is still on the heap and may be collected at this point.
}
}
Always call Dispose() for any object, where have we manually
allocated to the heap.
GRNICA 57
Reusing the C# "using" Keyword
When we are handling a managed object that implements
IDisposable, it will be quite common to make use of structured
exception handling.
public void SomeMethod()
{
Car c = new Car();
try
{// Use the car. }
catch
{ // Catch any exceptions here. }
finally
{
// Always call Dispose(), error or not.
c.Dispose();
}
}
GRNICA 58
To achieve the same result in a much less obtrusive manner, C#
supports a special bit of syntax that looks like this:
public void SomeMethod()
{
using(Car c = new Car())
{
// Use the car.
// Dispose() is called automatically when the
// using block exits.
}
}
GRNICA 59
Garbage Collection Optimizations
When the CLR is attempting to locate unreachable objects, is does
not literally walk over each and every object placed on the managed
heap looking for orphaned roots.

To help optimize the collection process, each object on the heap is
assigned to a given "generation.

The idea behind generations is simple: The longer an object has
existed on the heap, the more likely it is to stay there.

Conversely, objects that have been recently placed on the heap are
more likely to be dereferenced by the application quickly.

GRNICA 60
Given these assumptions, each object belongs to one of the
following generations:
Generation 0: Identifies a newly allocated object that has never
been marked for collection.

Generation 1: Identifies an object that has survived a garbage
collection sweep

Generation 2: Identifies an object that has survived more than one
sweep of the garbage collector.

Now, when a collection occurs, the GC marks and sweeps all
generation 0 objects first. If this results in the required amount of
memory, the remaining objects are promoted to the next available
generation.

If all generation 0 objects have been removed from the heap, but
more memory is still necessary, generation 1 objects are marked and
swept, followed (if necessary) by generation 2 objects.
GRNICA 61
The System.GC Type
We are able to interact with the garbage collector using a base class
library type, which in this case is System.GC
GRNICA 62
Building Finalizable and Disposable Types
// Memory clean up.
public class Car : IDisposable
{
...
~Car()
{
// Clean up any internal unmanaged resouces.
}
public void Dispose()
{
// Clean up any internal resources.
...
// No need to finalize if user called Dispose(),
// so suppress finalization.
GC.SuppressFinalize(this);
}
}
GRNICA 63
This iteration of the Car class supports both a C#-style destructor as
well as the IDisposable interface.

Here, the Dispose() method has been altered to call
GC.SuppressFinalize(), which informs the system that it should no
longer call the destructor for the specified object, as the end user has
called Dispose().
GRNICA 64
// Interacting with the GC.
public class GCApp
{
public static int Main(string[] args)
{
// Add these cars to the managed heap.
Car c1,c2, c3, c4;
c1 = new Car("Car one", 40, 10);
c2 = new Car("Car two", 70, 5);
c3 = new Car("Car three", 200, 100);
c4 = new Car("Car four", 140, 80);
// Manually dispose some objects.
c1.Dispose();
c3.Dispose();
return 0;
}
}
GRNICA 65
Two of the Car types have been manually disposed by the object
user, these types do not have their destructor logic triggered due to
the call to GC.SuppressFinalize().
GRNICA 66
// A sophisticated resource wrapper.
public class MyResourceWrapper : IDisposable
{
// The garbage collector will call this method if the
// object user forgets to call Dispose().
~ MyResourceWrapper()
{
// Clean up any internal unmanaged resources.
// Do **not** call Dispose() on any managed objects.
}
// The object user will call this method to clean up
// resources ASAP.
public void Dispose()
{
// Clean up unmanaged resources here.
// Call Dispose() on other contained disposable objects.
// No need to finalize if user called Dispose(),
// so suppress finalization.
GC.SuppressFinalize(this);
}
}
GRNICA 67
Notice that this Dispose() method has been updated to call
GC.SuppressFinalize(), which informs the CLR that it is no longer
necessary to call the destructor when this object is garbage
collected, given that the unmanaged resources have already been
freed via the Dispose() logic.
GRNICA 68
Forcing a Garbage Collection
We are able to programmatically force the runtime to perform a
garbage collection using the static GC.Collect() method.
Assume we have an object-hungry application that is designed to run
for a lengthy amount of time. To trigger a garbage collection, we
could write the following:
// Force a garbage collection, and wait for
// each object to be finalized.
GC.Collect();
GC.WaitForPendingFinalizers();
Note that we call GC.WaitForPendingFinalizers() after forcing a
garbage collection cycle.
In this way, it is assured that all finalizable objects have had a chance
to perform any necessary cleanup before continuing.

You might also like