IDisposable.Dispose

Dispose methods complement finalizers. Contrary to finalizers, Dispose methods are deterministic and can access managed types. Dispose methods are sometimes referred to as explicit garbage collection. You can call Dispose for immediate cleanup of resources associated with an object, such as closing a file handle. Remember that accessing a managed object in a finalizer is inadvisable. This requirement greatly limits the flexibility and functionality of a finalizer. The Dispose method does not have this limitation because garbage collection is not occurring simultaneously.

The Dispose method is defined in the IDisposable interface, which is found in the System namespace. Disposable objects should inherit and implement the IDisposable interface, where the Dispose method is the only member. You then call the Dispose method as a normal method to start deterministic garbage collection. Although possible, you should not implement the Dispose method apart from the IDisposable interface. The IDisposable interface is an important marker that confirms the presence of a disposable object. There are statements and behaviors, such as the using statement, that rely on this marker.

The following code demonstrates a simple implementation of the Dispose method:

public class Starter {
    private static void Main() {
        ZClass disposableobject = new ZClass();
        disposableobject.Dispose();
        disposableobject = null;
    }
}

public class ZClass : IDisposable {
    public ZClass() {
        // Allocate resources
    }

    public void Dispose() {
        // Release resources
    }
}

In the preceding code, the Dispose method is not guaranteed to run. Raising an exception prior to the Dispose method call could cause the cleanup to be skipped and result in resource leakage. To protect against this possibility, place the Dispose method call in a finally block. This ensures that the Dispose method is called whether or not an exception is raised in the try block. In this updated version of the code, the Dispose method is placed in a finally block:

public static void Main() {
    ZClass disposableobject = null;
    try {
        disposableobject = new ZClass();
    }
    finally {
        disposableobject.Dispose();
        disposableobject = null;
    }
}

The using block is the short form of the preceding code. The using block is an abbreviated try and finally block. The Dispose method of the referenced object is called automatically in an implicit finally block. The referenced object is defined in the using statement. Here is the equivalent code written with a using statement:

public static void Main() {
    using(ZClass disposableobject = new ZClass()) {
        // use object
    }
}

The C# compiler substitutes a try and a finally block for the using block. This is some of the MSIL code emitted for a using block from the C# compiler:

IL_0006:  stloc.0
.try
{
    IL_0007:  nop
    IL_0008:  nop
    IL_0009:  leave.s      IL_001b
}  // end .try
finally
{
   // partial listing...

   IL_0014:  callvirt     instance void[mscorlib]System.IDisposable::Dispose()
   IL_0019:  nop
   IL_001a:  endfinally
}  // end handler

Multiple objects of the same type can be declared in one using statement. Delimit the objects with commas. All objects declared in the statement are accessible within the using block. When the using block exits, the Dispose method is called on each of the objects declared in the using statement. In the following code, two objects of the same type are declared in the using statement:

using System;

namespace Donis.CSharpBook {

    public class Starter {
        public static void Main() {
            using(ZClass obj1 = new ZClass(),
                         obj2 = new ZClass()) {
            }
        }
    }

    public class ZClass : IDisposable {
        public void Dispose() {
            Console.WriteLine("ZClass.Dispose");
        }
    }
}

You also can declare objects of different types by providing multiple using statements. In the following code, three objects are declared for the using block. There are two ZClass objects and an XClass object. All three are disposed at the end of the using block:

using System;

namespace Donis.CSharpBook {
    public class Starter {
        private static void Main() {
            using(XClass obj3 = new XClass())
            using(ZClass obj1 = new ZClass(),
                         obj2 = new ZClass()) {
            }
        }
    }

    public class ZClass: IDisposable {
        public void Dispose() {
            Console.WriteLine("ZClass.Dispose");
        }
    }

    public class XClass: IDisposable {
        public void Dispose() {
            Console.WriteLine("XClass.Dispose");
        }
    }
}

Classes can have both a Dispose method and a destructor. You can relinquish both managed and unmanaged resources in the Dispose method, whereas the destructor can clean up only unmanaged resources. Because finalizers are called, they are effective safety nets. However, finalization should not be performed on an already-disposed object. A second iteration of cleanup could have unexpected results. For this reason, developers typically suppress the finalizer in the Dispose method. The GC.SuppressFinalize method is called in the Dispose method to suppress the finalizer. Performance is improved because future finalization of the object is eliminated.

In the following code, the ZClass has both a Dispose and finalizer method. Note that GC.SuppressFinalize is invoked in the Dispose method to suppress future finalization:

using System;

namespace Donis.CSharpBook {
    public class Starter {
        private static void Main() {
            using(ZClass obj1 = new ZClass()) {
            }
        }
    }

    public class ZClass: IDisposable {
        public void Dispose() {
            Console.WriteLine("Disposing resources");
            GC.SuppressFinalize(this);
        }

        ~ZClass() {
            Console.WriteLine("ZClass.constructor");
            // Cleanup unmanaged resources
        }
    }
}

This section reviewed the simple implementation of the Dispose method, which is sufficient for sealed classes. However, inheritable classes require the more complex Disposable pattern, which is discussed in the section titled "Disposable Pattern" later in this chapter.

TLS Example

In the section titled "Multithreading" earlier in this chapter, an improper version of a TLS application with the TMonitor class was presented. The following code shows the corrected version that uses the Dispose method. The Dispose method runs on the same thread as the WriteFile method of the object. For the TMonitor class, the Dispose method (instead of a destructor) is called from each thread that is using the class, which correctly releases the relevant resources of each thread:

class TMonitor: IDisposable {
    public void WriteFile() {
        StreamWriter sw = Thread.GetData(Form1.Slot) as StreamWriter;
        if (sw == null) {
            sw = new StreamWriter(string.Format(
                @"C:{0}File.txt",
                Thread.CurrentThread.Name),
                true);
            Thread.SetData(Form1.Slot, sw);
        }
        sw.WriteLine(DateTime.Now.ToLongTimeString());
    }

    public void Dispose() {
        StreamWriter sw = Thread.GetData(Form1.Slot) as StreamWriter;
        Thread.SetData(Form1.Slot, null);
        if (sw != null) {
            sw.Close();
            MessageBox.Show("sw closed");
        }
    }
}

Disposable Pattern

The Disposable pattern provides a template for implementing the Dispose method and the destructor in a base class and a derived class. The Disposable pattern, shown in the following code, should be implemented where there is a base class and a derived class that require some form of cleanup:

using System;
using System.Threading;

namespace Donis.CSharpBook {

    public class Base: IDisposable {
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing) {
            if (disposing) {
                // Release managed resources
            }
            // Release unmanaged resources
        }

        ~Base() {
            Dispose (false);
        }
    }

    public class Derived: Base {
        protected override void Dispose(bool disposing) {
            if (disposing) {
                // Release managed resources.
            }
            // Release unmanaged resources
            base.Dispose(disposing);
        }
    }
}

Let us focus first on the base class, which implements the IDisposable interface and contains two Dispose methods.

The one-argument Dispose method has a Boolean disposing argument. This argument indicates whether the method is being called during deterministic or nondeterministic garbage collection. If called during nondeterministic garbage collection, the disposing argument is false. Otherwise, the argument is true. When the argument is false, only unmanaged resources should be released in the method. When true, both managed and unmanaged resources can be released.

There is also a no-argument Dispose method. Both the no-argument Dispose method and the destructor delegate to the one-argument Dispose method. The no-argument Dispose method is public and is called explicitly for deterministic garbage collection. This Dispose method delegates to the one-argument destructor with the disposing flag set to true, which indicates deterministic garbage collection. It then calls GC.SuppressFinalize to suppress finalization and avoid further garbage collection.

The destructor delegates to the one-argument Dispose method with the disposing flag set to false, which indicates nondeterministic garbage collection.

In the base class, the no-argument Dispose method is not a virtual method and should not be overridden in the derived class. This method should always delegate to the most derived one-argument Dispose method to access the correct behavior. Any other behavior would seriously break the Disposable pattern.

In the derived class, override the one-argument Dispose method to clean up managed and unmanaged resources of the derived class. The one-argument Dispose method in the derived class should call the same method of the base class, affording the base class the opportunity to release its resources.

In the derived class, you should not implement a destructor. The base class implementation of the destructor method will correctly call the most derived Dispose method. Disposal then propagates from the most derived class to all ascendants. Therefore, resource cleanup is performed in the correct order.

Disposable Pattern Considerations

There are several factors to consider when implementing a simple Dispose or the more complex Disposable pattern. This section describes many of the factors that should be considered when implementing a Dispose method. Here are the factors:

Redundant Dispose Method

In the following code, the Dispose method is called twice (once explicitly and once implicitly, as a result of the using statement). You should be able to call the Dispose method multiple times safely. Set a flag the first time Dispose is called, and check the flag to confirm whether the object is disposed already. If the object is disposed, do not dispose it again. Alternatively, you might be able to confirm the disposability of an object from the state of the object. It is a good practice to confirm the disposed status at the beginning of other member methods. If the object is disposed, you have two options. Either revive the object and execute the method, or throw the ObjectDisposedException:

private static void Main() {
    using(ZClass disposableobject =
            new ZClass()) {
        disposableobject.Dispose();
    }
}

The following code demonstrates a resilient Dispose method, which can be called multiple times. The ReverseReader type is a thin wrapper for a StreamReader. It inverts information read from a file source. ReverseReader contains a StreamReader field. It is initialized in the class constructor and closed in the Dispose method. If the StreamReader is null, the object is presumed disposed. This is checked in the Dispose method. If the object is already disposed, the Dispose method simply returns. In addition, the ReverseReader.ReadLine method throws the ObjectDisposedException exception if the object has been disposed. You cannot call this method successfully if the object has been disposed. To test this implementation, there is a using block in Main. The Dispose method is called explicitly within the using block. After the using block, the Dispose method is called implicitly again, which proves to be harmless. A second call to the ReadLine method is commented out, because the object now has been disposed. If the second call to ReadLine were uncommented, an exception would be raised, as expected:

using System;
using System.IO;

namespace Donis.CSharpBook {
    public class Starter {

        private static void Main() {
            using(ReverseReader input = new ReverseReader("text.txt")) {
                string result = input.ReadLine();
                while (result != null) {
                    Console.WriteLine(result);
                    result = input.ReadLine();
                }
                input.Dispose();
                // input.ReadLine();
            }
        }
    }

    public class ReverseReader:IDisposable {

        public ReverseReader(string filename) {
            file = new StreamReader(filename);
        }

        public string ReadLine() {
            if (file == null) {
                throw new ObjectDisposedException(
                    "ReadLine object");
            }
            if (file.Peek() < 0) {
                return null;
            }
            string temp = file.ReadLine();
            char[] tempArray = temp.ToCharArray();
            Array.Reverse(tempArray);
            return new string(tempArray);
        }

        public void Dispose() {
            if (file == null) {
                return;
            }
            else {
                file.Close();
                file = null;
            }
        }

        private StreamReader file = null;
    }
}

Close Method

Instead of the Dispose method, some classes expose another method for deterministic cleanup. Although this should not be done as a general practice, the exception is when a differently named method would be more intuitive than the Dispose method. For example, the FileStream class exposes the Close method. (Close is the traditional term for releasing a file.) The alternate method should delegate to the proper Dispose method. Do not implement the disposable routine more than once. Both the Dispose and the alternative methods are available to the clients for deterministic garbage collection. The correct implementation is demonstrated with the StreamWriter.Close method, as shown in the following code. StreamWriter.Close delegates to TextWriter.Dispose for deterministic garbage collection. TextWriter.Dispose is inherited by the StreamWriter class. The Close method suppresses the finalizer, which is standard behavior of a deterministic method. Both the Close and Dispose methods are available on the StreamWriter class. You should clearly document any alternate method for deterministic garbage collection:

.method public hidebysig virtual instance void Close() cil managed
{
    // Code size       14 (0xe)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.1
    IL_0002:  callvirt   instance void System.IO.TextWriter::Dispose(bool)
    IL_0007:  ldarg.0
    IL_0008:  call       void System.GC::SuppressFinalize(object)
    IL_000d:  ret
} // end of method StreamWriter::Close

Thread-Safe Dispose Method

The Dispose method is not implicitly thread-safe. As a public method, Dispose is callable from multiple threads simultaneously. Thread synchronization is required for thread-safety. The lock statement, as demonstrated in the following code, is a convenient means of providing thread-safe access to the Dispose method:

public class ZClass : IDisposable {

    public void Dispose() {
        lock(this) {
        }
    }
}

Reusable Objects

The Disposable pattern accommodates reusable objects. Unless the object is nondisposed, it can be recycled. You should not recycle an object implicitly where recycling can occur inadvertently or without detection. Expose a method that explicitly recycles the object. There is no convention for naming this method. However, an Open method is usually a good choice. If disposed, this method should recycle the object using the object constructor. Recyclable objects should also expose a property that confirms the status of the object as alive or disposed.

The following code is a revision of the ReverseReader class, which was presented earlier in this chapter. This version is recyclable. It has both a default and a one-argument constructor. The one-argument constructor delegates to the Open method. You can call the Open method directly to recycle a ReverseReader instance. The Active property returns the status of the object. If true, the object is active and is not disposed:

public class ReverseReader :IDisposable {

    public ReverseReader() {
    }

    public ReverseReader(string filename) {
        Open(filename);
    }

    public bool Open(string filename) {
        if (file != null) {
            return false;
        }
        file = new StreamReader(filename);
        return true;
    }

    public string ReadLine() {
        if (file == null) {
            throw new ObjectDisposedException(
                "ReadLine object");
        }
        if (file.Peek() < 0) {
            return null;
        }
        string temp = file.ReadLine();
        char[] tempArray = temp.ToCharArray();
        Array.Reverse(tempArray);
        return new string(tempArray);
    }

    public void Dispose() {
        if (file == null) {
            return;
        }
        else {
            file.Close();
            file = null;
        }
    }

    public void Close() {
        Dispose();
    }

    private StreamReader file = null;

    public bool Active {
        get {
            return !(file == null);
        }
    }
}

Disposing Inner Objects

When disposing, a class should call the Dispose method on disposable member fields. Call the Dispose method of those inner objects in the Dispose method of the containing class. After disposing, set the disposable fields to null. Of course, the inner objects dispose the disposable objects they contain, and so on. In this way, the Dispose method could be considered transitive.

The proper disposal of inner objects is shown in the following code:

public class ZClass : IDisposable{

    public ZClass() {
        inner = new YClass();
    }

    public void Dispose() {
        Console.WriteLine("ZClass.Dispose");
        inner.Dispose();
        inner = null;
    }
    private YClass inner = null;
}

public class YClass : IDisposable{
    public void Dispose() {
        Console.WriteLine("YClass.Dispose");
    }
}

An object should not dispose any object not within its full control, as doing so can cause unwanted side effects. In the following code, ZClass has a property called inner, which is backed by the _inner field. The _inner field is of the YClass type, which is disposable. In Main, two instances of ZClass are created. Through the inner property, the _inner fields of both are set to the same object. Therefore, neither object has full control of their respective _inner object. When the using block is exited, the _inner field of the second object is disposed. However, the other object, which shares the _inner field, remains active. When the remaining active object attempts to access the inner property, and subsequently the shared _inner field, an exception is raised:

using System;

namespace Donis.CSharpBook {
    public class Starter {
        private static void Main() {
            ZClass obj1 = new ZClass();
            obj1.Inner = new YClass();
            using(ZClass obj2 = new ZClass()) {
                obj2.Inner = obj1.Inner;
            }
            obj1.MethodA();  // exception
            obj1.Dispose();
            obj1 = null;
        }
    }

    public class ZClass: IDisposable{

        public ZClass() {
        }

        public void Dispose() {
            Console.WriteLine("ZClass.Dispose");
            _inner.Dispose();
        }

        public void MethodA() {
            Console.WriteLine("ZClass.MethodA");
            _inner.MethodA();
        }
        public YClass Inner {
            set {
               _inner = value;
            }
            get {
                return _inner;
            }
        }
        private YClass _inner = null;
    }

    public class YClass: IDisposable{
        public void Dispose() {
            Console.WriteLine("YClass.Dispose");
            disposed = true;
        }

        public void MethodA() {
            if (disposed) {
                throw new ObjectDisposedException(
                    "YClass disposed");
            }
            Console.WriteLine("YClass.MethodA");
        }

        private bool disposed = false;
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.12.34