- Creating Classes
- Creating Objects
- Using Access Modifiers
- Creating Fields and Using Initializers
- Creating Methods
- Creating Properties
- Read-only Properties
- Creating Constructors
- Creating Structs
- Creating Static Members
- Creating Static Fields
- Creating Static Properties
- Creating Destructors and Handling Garbage Collection
- Overloading Methods
- Overloading Operators
- Creating Namespaces
- In Brief
Creating Destructors and Handling Garbage Collection
The flip side of constructors are destructors, which are called when it's time to get rid of an object and perform cleanup, such as disconnecting from the Internet or closing files. Getting rid of no-longer-needed objects involves the C# garbage collector, which calls your destructor. The C# garbage collector is far more sophisticated than the ones available in most C++ implementations, as we'll see soon. Let's start with destructors first.
Creating a Destructor
You place the code you want to use to clean up an object when it's being deleted, if any, in a destructor. Destructors cannot be static, cannot be inherited, do not take parameters, and do not use access modifiers. They're declared much like constructors, except that their name is prefaced with a tilda (~). You can see an example in ch03_10.cs, which appears in Listing 3.10, and includes a destructor for the Customer class. When there are no more references to objects of this class in your code, the garbage collector is notified and will call the object's destructor (sooner or lateryou can't predict when it will happen).
Listing 3.10 Creating a Destructor (ch03_10.cs)
class ch03_10 { static void Main() { Customer customer = new Customer(); } } public class Customer { public Customer() { System.Console.WriteLine("In Customer's constructor."); } ~Customer() { System.Console.WriteLine("In Customer's destructor."); } }
Here's what you see when you run ch03_10.cs:
C:\>ch03_10 In Customer's constructor. In Customer's destructor.
In C#, destructors are converted into a call to the System.Object class's Finalize method (which you cannot call directly in C#). In other words, this destructor:
~Customer() { // Your code }
is actually translated into this code by C# (the call to base.Finalize calls the Finalize method in the class the current class is based on; more on the base keyword in the next chapter):
protected override void Finalize() { try { // Your code } finally { base.Finalize(); } }
However, in C#, you must use the standard destructor syntax as we've done in Listing 3.10, and not call or use the Finalize method directly.
Understanding Garbage Collection
Destructors are actually called by the C# garbage collector, which manages memory for you in C# code. When you create new objects with the new keyword in C++, you should also later use the delete keyword to delete those objects and free up their allocated memory. That's no longer necessary in C#the garbage collector deletes objects no longer referenced in your code automatically (at a time of its choosing).
For C++ Programmers
In C#, you no longer have to use delete to explicitly delete objects after allocating them with new. The garbage collector will delete objects for you by itself. In fact, delete is not even a keyword in C#.
What that means in practical terms is that you don't have to worry about standard objects in your code as far as memory usage goes. On the other hand, when you use "unmanaged" resources like windows, files, or network connections, you should write a destructor that can be called by the garbage collector, and put the code you want to use to close or deallocate those resources in the destructor (because the garbage collector won't know how to do that itself).
Implementing a Dispose Method
On some occasions, you might want to do more than simply put code in a destructor to clean up resources. You might want to disconnect from the Internet or close a file as soon as you no longer need those resources. For such cases, C# recommends that you create a Dispose method. Because that method is public, your code can call it directly to dispose of the object that works with important resources you want to close.
For C++ Programmers
In C#, you can implement a Dispose method to explicitly deallocate resources.
In a Dispose method, you clean up after an object yourself, without C#'s help. Dispose can be called both explicitly, and/or by the code in your destructor. Note that if you call the Dispose method yourself, you should suppress garbage collection on the object once it's gone, as we'll do here.
Let's take a look at an example to see how this works. The Dispose method is formalized in C# as part of the IDisposable interface (we'll discuss interfaces in the next chapter). That has no real meaning for our code, except that we will indicate we're implementing the IDisposable interface. In our example, the Dispose method will clean up an object of a class we'll call Worker.
Note that if you call Dispose explicitly, you can access other objects from inside that method; however, you can't do so if Dispose is called from the destructor, because those objects might already have been destroyed. For that reason, we'll track whether Dispose is being called explicitly or by the destructor by passing Dispose a bool variable (set to true if we call Dispose, and false if the destructor calls Dispose). If that bool is true, we can access other objects in Disposeotherwise, we can't.
Also, if the object is disposed of by calling the Dispose method, we don't want the garbage collector to try to dispose of it as well, which means we'll call the System.GC.SuppressFinalize method to make sure the garbage collector suppresses garbage collection for the object. You can see what this code looks like in ch03.11.cs, Listing 3.11.
Listing 3.11 Implementing the Dispose Method (ch03_11.cs)
class ch03_11 { static void Main() { Worker worker = new Worker(); worker.Dispose(); } } public class Worker: System.IDisposable { private bool alreadyDisposed = false; public Worker() { System.Console.WriteLine("In the constructor."); } public void Dispose(bool explicitCall) { if(!this.alreadyDisposed) { if(explicitCall) { System.Console.WriteLine("Not in the destructor, " + "so cleaning up other objects."); // Not in the destructor, so we can reference other objects. //OtherObject1.Dispose(); //OtherObject2.Dispose(); } // Perform standard cleanup here... System.Console.WriteLine("Cleaning up."); } alreadyDisposed = true; } public void Dispose() { Dispose(true); System.GC.SuppressFinalize(this); } ~Worker() { System.Console.WriteLine("In the destructor now."); Dispose(false); } }
Here's what you see when you run ch03_11.cs. Note that the object was only disposed of once, when we called Dispose ourselves, and not by the destructor, because the object had already been disposed of:
C:\>ch03_11 In the constructor. Not in the destructor, so cleaning up other objects. Cleaning up.
Renaming Dispose
Sometimes, calling the Dispose method Dispose isn't appropriate. On some occasions, for example, Close or Deallocate might be a better name. C# still recommends you stick with the standard Dispose name, however, so if you do write a Close or Deallocate method, you can have it call Dispose.
Here's another thing to know if you're going to use Disposeyou don't have to call this method explicitly if you don't want to. You can let C# call it for you if you add the using keyword (this is not the same as the using directive, which imports a namespace). After control leaves the code in the curly braces following the using keyword, Dispose will be called automatically. Here's how this keyword works:
using (expression | type identifier = initializer){}
Here are the parts of this statement:
expressionAn expression you want to call Dispose on when control leaves the using statement.
typeThe type of identifier.
identifierThe name, or identifier, of the type type.
initializerAn expression that creates an object.
Here's an example, where Dispose will be called on the Worker object when control leaves the using statement:
class usingExample { public static void Main() { using (Worker worker = new Worker()) { // Use worker object in code here. } // C# will call Dispose on worker here. { }
Forcing a Garbage Collection
Here's one last thing to know about C# garbage collectionalthough the C# documentation (and many C# books) says you can't know when garbage collection will take place, that's not actually true. In fact, you can force garbage collection to occur simply by calling the System.GC.Collect method.
This method is safe to call, but if you do, you should know that execution speed of your code might be somewhat compromised.