1. Trang chủ
  2. » Công Nghệ Thông Tin

Pro C# 2008 and the .NET 3.5 Platform, Fourth Edition phần 3 ppsx

140 461 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 140
Dung lượng 3,87 MB

Nội dung

If all generation 0 objects have been evaluated, but additional memory is still required, genera- tion 1 objects are then investigated for their “reachability” and collected accordingly. Surviving generation 1 objects are then promoted to generation 2. If the garbage collector still requires addi- tional memory, generation 2 objects are then evaluated for their reachability. At this point, if a generation 2 object survives a garbage collection, it remains a generation 2 object given the prede- fined upper limit of object generations. The bottom line is that by assigning a generational value to objects on the heap, newer objects (such as local variables) will be removed quickly, while older objects (such as a program’s applica- tion object) are not “bothered” as often. The System.GC Type The base class libraries provide a class type named System.GC that allows you to programmatically interact with the garbage collector using a set of static members. Now, do be very aware that you will seldom (if ever) need to make use of this type directly in your code. Typically speaking, the only time you will make use of the members of System.GC is when you are creating types that make use of unmanaged resources. Table 8-1 provides a rundown of some of the more interesting members (consult the .NET Framework 3.5 SDK documentation for complete details). Table 8-1. Select Members of the System.GC Type System.GC Member Meaning in Life AddMemoryPressure() Allow you to specify a numerical value that represents the RemoveMemoryPressure() calling object’s “urgency level” regarding the garbage collection process. Be aware that these methods should alter pressure in tandem and thus never remove more pressure than the total amount you have added. Collect() Forces the GC to perform a garbage collection. This method has been overloaded to specify a generation to collect, as well as the mode of collection (via the GCCollectionMode enumeration). CollectionCount() Returns a numerical value representing how many times a given generation has been swept. GetGeneration() Returns the generation to which an object currently belongs. GetTotalMemory() Returns the estimated amount of memory (in bytes) currently allocated on the managed heap. The Boolean parameter specifies whether the call should wait for garbage collection to occur before returning. MaxGeneration Returns the maximum of generations supported on the target system. Under Microsoft’s .NET 3.5, there are three possible generations (0, 1, and 2). SuppressFinalize() Sets a flag indicating that the specified object should not have its Finalize() method called. WaitForPendingFinalizers() Suspends the current thread until all finalizable objects have been finalized. This method is typically called directly after invoking GC.Collect(). To illustrate how the System.GC type can be used to obtain various garbage collection–centric details, consider the following Main() method, which makes use of several members of GC: CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME252 8849CH08.qxd 10/22/07 1:27 PM Page 252 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com static void Main(string[] args) { Console.WriteLine("***** Fun with System.GC *****"); // Print out estimated number of bytes on heap. Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false)); // MaxGeneration is zero based, so add 1 for display purposes. Console.WriteLine("This OS has {0} object generations.\n", (GC.MaxGeneration + 1)); Car refToMyCar = new Car("Zippy", 100); Console.WriteLine(refToMyCar.ToString()); // Print out generation of refToMyCar object. Console.WriteLine("Generation of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); Console.ReadLine(); } Forcing a Garbage Collection Again, the whole purpose of the .NET garbage collector is to manage memory on our behalf. How- ever, under some very rare circumstances, it may be beneficial to programmatically force a garbage collection using GC.Collect(). Specifically: • Your application is about to enter into a block of code that you do not wish to be interrupted by a possible garbage collection. • Your application has just finished allocating an extremely large number of objects and you wish to remove as much of the acquired memory as possible. If you determine it may be beneficial to have the garbage collector check for unreachable objects, you could explicitly trigger a garbage collection, as follows: static void Main(string[] args) { // Force a garbage collection and wait for // each object to be finalized. GC.Collect(); GC.WaitForPendingFinalizers(); } When you manually force a garbage collection, you should always make a call to GC. WaitForPendingFinalizers() . With this approach, you can rest assured that all finalizable objects have had a chance to perform any necessary cleanup before your program continues forward. Under the hood, GC.WaitForPendingFinalizers() will suspend the calling “thread” during the col- lection process. This is a good thing, as it ensures your code does not invoke methods on an object currently being destroyed! The GC.Collect() method can also be supplied a numerical value that identifies the oldest generation on which a garbage collection will be performed. For example, if you wished to instruct the CLR to only investigate generation 0 objects, you would write the following: CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME 253 8849CH08.qxd 10/22/07 1:27 PM Page 253 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com static void Main(string[] args) { // Only investigate generation 0 objects. GC.Collect(0); GC.WaitForPendingFinalizers(); } As well, as of .NET 3.5, the Collect() method can also be passed in a value of the GCCollectionMode enumeration as a second parameter, to fine-tune exactly how the runtime should force the garbage collection. This enum defines the following values: public enum GCCollectionMode { Default, // Forced is the current default. Forced, // Tells the runtime to collect immediately! Optimized // Allows the runtime to determine // whether the current time is optimal to reclaim objects. } Like any garbage collection, calling GC.Collect() will promote surviving generations. To illus- trate, assume that our Main() method has been updated as follows: static void Main(string[] args) { Console.WriteLine("***** Fun with System.GC *****"); // Print out estimated number of bytes on heap. Console.WriteLine("Estimated bytes on heap: {0}", GC.GetTotalMemory(false)); // MaxGeneration is zero based. Console.WriteLine("This OS has {0} object generations.\n", (GC.MaxGeneration + 1)); Car refToMyCar = new Car("Zippy", 100); Console.WriteLine(refToMyCar.ToString()); // Print out generation of refToMyCar. Console.WriteLine("\nGeneration of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); // Make a ton of objects for testing purposes. object[] tonsOfObjects = new object[50000]; for (int i = 0; i < 50000; i++) tonsOfObjects[i] = new object(); // Collect only gen 0 objects. GC.Collect(0, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); // Print out generation of refToMyCar. Console.WriteLine("Generation of refToMyCar is: {0}", GC.GetGeneration(refToMyCar)); CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME254 8849CH08.qxd 10/22/07 1:27 PM Page 254 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com // See if tonsOfObjects[9000] is still alive. if (tonsOfObjects[9000] != null) { Console.WriteLine("Generation of tonsOfObjects[9000] is: {0}", GC.GetGeneration(tonsOfObjects[9000])); } else Console.WriteLine("tonsOfObjects[9000] is no longer alive."); // Print out how many times a generation has been swept. Console.WriteLine("\nGen 0 has been swept {0} times", GC.CollectionCount(0)); Console.WriteLine("Gen 1 has been swept {0} times", GC.CollectionCount(1)); Console.WriteLine("Gen 2 has been swept {0} times", GC.CollectionCount(2)); Console.ReadLine(); } Here, we have purposely created a very large array of object types (50,000 to be exact) for test- ing purposes. As you can see from the output shown in Figure 8-6, even though this Main() method only made one explicit request for a garbage collection (via the GC.Collect() method), the CLR per- formed a number of them in the background. Figure 8-6. Interacting with the CLR garbage collector via System.GC At this point in the chapter, I hope you feel more comfortable regarding the details of object lifetime. The remainder of this chapter examines the garbage collection process a bit further by addressing how you can build finalizable objects as well as disposable objects. Be very aware that the following techniques will only be useful if you are building managed classes that maintain internal unmanaged resources. ■Source Code The SimpleGC project is included under the Chapter 8 subdirectory. CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME 255 8849CH08.qxd 10/22/07 1:27 PM Page 255 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Building Finalizable Objects In Chapter 6, you learned that the supreme base class of .NET, System.Object, defines a virtual method named Finalize(). The default implementation of this method does nothing whatsoever: // System.Object public class Object { protected virtual void Finalize() {} } When you override Finalize() for your custom classes, you establish a specific location to per- form any necessary cleanup logic for your type. Given that this member is defined as protected, it is not possible to directly call an object’s Finalize() method from a class instance via the dot opera- tor. Rather, the garbage collector will call an object’s Finalize() method (if supported) before removing the object from memory. ■Note It is illegal to override Finalize() on structure types. This makes perfect sense given that structures are value types, which are never allocated on the heap to begin with, and therefore are not garbage collected! Of course, a call to Finalize() will (eventually) occur during a “natural” garbage collection or when you programmatically force a collection via GC.Collect(). In addition, a type’s finalizer method will automatically be called when the application domain hosting your application is unloaded from memory. Based on your current background in .NET, you may know that application domains (or simply AppDomains) are used to host an executable assembly and any necessary external code libraries. If you are not familiar with this .NET concept, you will be by the time you’ve finished Chapter 17. The short answer is that when your AppDomain is unloaded from memory, the CLR automatically invokes finalizers for every finalizable object created during its lifetime. Now, despite what your developer instincts may tell you, a vast majority of your C# classes will not require any explicit cleanup logic and will not need a custom finalizer. The reason is simple: if your types are simply making use of other managed objects, everything will eventually be garbage collected. The only time you would need to design a class that can clean up after itself is when you are making use of unmanaged resources (such as raw OS file handles, raw unmanaged database connections, chunks of unmanaged memory, or other unmanaged resources). Under the .NET plat- form, unmanaged resources are obtained by directly calling into the API of the operating system using Platform Invocation Services (PInvoke) or due to some very elaborate COM interoperability scenarios. Given this, consider the next rule of garbage collection: ■Rule The only reason to override Finalize() is if your C# class is making use of unmanaged resources via PInvoke or complex COM interoperability tasks (typically via various members defined by the System.Runtime. InteropServices.Marshal type). Overriding System.Object.Finalize() In the rare case that you do build a C# class that makes use of unmanaged resources, you will obvi- ously wish to ensure that the underlying memory is released in a predictable manner. Assume you have created a new C# Console Application named SimpleFinalize and inserted a class named MyResourceWrapper that makes use of an unmanaged resource (whatever that may be) and you wish CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME256 8849CH08.qxd 10/22/07 1:27 PM Page 256 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com to override Finalize(). The odd thing about doing so in C# is that you cannot do so using the expected override keyword: public class MyResourceWrapper { // Compile-time error! protected override void Finalize(){ } } Rather, when you wish to configure your custom C# class types to override the Finalize() method, you make use of a (C++-like) destructor syntax to achieve the same effect. The reason for this alternative form of overriding a virtual method is that when the C# compiler processes the finalizer syntax, it will automatically add a good deal of required infrastructure within the implicitly overridden Finalize() method (shown in just a moment). C# finalizers look very similar to a constructor in that they are named identically to the class they are defined within. In addition, finalizers are prefixed with a tilde symbol ( ~). Unlike a con- structor, however, finalizers never take an access modifier (they are implicitly protected), never take parameters and cannot be overloaded (only one finalizer per class). Here is a custom finalizer for MyResourceWrapper that will issue a system beep when invoked. Obviously this is only for instructional purposes. A real-world finalizer would do nothing more than free any unmanaged resources and would not interact with other managed objects, even those ref- erenced by the current object, as you cannot assume they are still alive at the point the garbage collector invokes your Finalize() method: // Override System.Object.Finalize() via finalizer syntax. class MyResourceWrapper { ~MyResourceWrapper() { // Clean up unmanaged resources here. // Beep when destroyed (testing purposes only!) Console.Beep(); } } If you were to examine this C# destructor using ildasm.exe, you will see that the compiler inserts some necessary error checking code. First, the code statements within the scope of your Finalize() method are placed within a try block (see Chapter 7). The related finally block ensures that your base classes’ Finalize() method will always execute, regardless of any exceptions encountered within the try scope: .method family hidebysig virtual instance void Finalize() cil managed { // Code size 13 (0xd) .maxstack 1 .try { IL_0000: ldc.i4 0x4e20 IL_0005: ldc.i4 0x3e8 IL_000a: call void [mscorlib]System.Console::Beep(int32, int32) IL_000f: nop IL_0010: nop IL_0011: leave.s IL_001b } // end .try CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME 257 8849CH08.qxd 10/22/07 1:27 PM Page 257 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com finally { IL_0013: ldarg.0 IL_0014: call instance void [mscorlib]System.Object::Finalize() IL_0019: nop IL_001a: endfinally } // end handler IL_001b: nop IL_001c: ret } // end of method MyResourceWrapper::Finalize If you were to now test the MyResourceWrapper type, you would find that a system beep occurs when the application terminates, given that the CLR will automatically invoke finalizers upon AppDomain shutdown: static void Main(string[] args) { Console.WriteLine("***** Fun with Finalizers *****\n"); Console.WriteLine("Hit the return key to shut down this app"); Console.WriteLine("and force the GC to invoke Finalize()"); Console.WriteLine("for finalizable objects created in this AppDomain."); Console.ReadLine(); MyResourceWrapper rw = new MyResourceWrapper(); } ■Source Code The SimpleFinalize project is included under the Chapter 8 subdirectory. Detailing the Finalization Process Not to beat a dead horse, but always remember that the role of the Finalize() method is to ensure that a .NET object can clean up unmanaged resources when garbage collected. Thus, if you are building a type that does not make use of unmanaged entities (by far the most common case), final- ization is of little use. In fact, if at all possible, you should design your types to avoid supporting a Finalize() method for the very simple reason that finalization takes time. When you allocate an object onto the managed heap, the runtime automatically determines whether your 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 the finalization queue. The finalization queue is a table maintained by the garbage collector that points to each and every object that must be finalized before it is removed from the heap. When the garbage collector determines it is time to free an object from memory, it examines each entry on the finalization queue and copies the object off the heap to yet another managed structure termed the finalization reachable table (often abbreviated as freachable, and pronounced “eff-reachable”). At this point, a separate thread is spawned to invoke the Finalize() method for each object on the freachable table at the next garbage collection. Given this, it will take at the very least two garbage collections to truly finalize an object. The bottom line is that while finalization of an object does ensure an object can clean up unmanaged resources, it is still nondeterministic in nature, and due to the extra behind-the- curtains processing, considerably slower. CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME258 8849CH08.qxd 10/22/07 1:27 PM Page 258 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Building Disposable Objects As you have seen, finalizers can be used to release unmanaged resources when the garbage collec- tor kicks in. However, given that many unmanaged objects are “precious items” (such as database or file handles), it may be valuable to release them as soon as possible instead of relying on a garbage collection to occur. As an alternative to overriding Finalize(), your class could implement the IDisposable interface, which defines a single method named Dispose(): public interface IDisposable { void Dispose(); } If you are new to interface-based programming, Chapter 9 will take you through the details. In a nutshell, an interface as a collection of abstract members a class or structure may support. When you do support the IDisposable interface, the assumption is that when the object user is finished using the object, it manually calls Dispose() before allowing the object reference to drop out of scope. In this way, an object can perform any necessary cleanup of unmanaged resources without incurring the hit of being placed on the finalization queue and without waiting for the garbage col- lector to trigger the class’s finalization logic. ■Note Structures and class types can both implement IDisposable (unlike overriding Finalize(), which is reserved for class types), as the object user (not the garbage collector) invokes the Dispose() method. To illustrate the use of this interface, create a new C# Console Application named Simple- Dispose. Here is an updated MyResourceWrapper class that now implements IDisposable, rather than overriding System.Object.Finalize(): // Implementing IDisposable. public class MyResourceWrapper : IDisposable { // The object user should call this method // when they finish with the object. public void Dispose() { // Clean up unmanaged resources // Dispose other contained disposable objects // Just for a test. Console.WriteLine("***** In Dispose! *****"); } } Notice that a Dispose() method is not only responsible for releasing the type’s unmanaged resources, but should also call Dispose() on any other contained disposable methods. Unlike Finalize(), it is perfectly safe to communicate with other managed objects within a Dispose() method. The reason is simple: the garbage collector has no clue about the IDisposable interface and will never call Dispose(). Therefore, when the object user calls this method, the object is still living a productive life on the managed heap and has access to all other heap-allocated objects. The calling logic is straightforward: CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME 259 8849CH08.qxd 10/22/07 1:27 PM Page 259 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com public class Program { static void Main() { Console.WriteLine("***** Fun with Dispose *****\n"); // Create a disposable object and call Dispose() // to free any internal resources. MyResourceWrapper rw = new MyResourceWrapper(); rw.Dispose(); Console.ReadLine(); } } Of course, before you attempt to call Dispose() on an object, you will want to ensure the type supports the IDisposable interface. While you will typically know which base class library types implement IDisposable by consulting the .NET Framework 3.5 SDK documentation, a program- matic check can be accomplished using the is or as keywords discussed in Chapter 6: public class Program { static void Main() { Console.WriteLine("***** Fun with Dispose *****\n"); MyResourceWrapper rw = new MyResourceWrapper(); if (rw is IDisposable) rw.Dispose(); Console.ReadLine(); } } This example exposes yet another rule of working with garbage-collected types. ■Rule Always call Dispose() on any object you directly create if the object supports IDisposable. The assumption you should make is that if the class designer chose to support the Dispose() method, the type has some cleanup to perform. There is one caveat to the previous rule. A number of types in the base class libraries that do implement the IDisposable interface provide a (somewhat confusing) alias to the Dispose() method, in an attempt to make the disposal-centric method sound more natural for the defining type. By way of an example, while the System.IO.FileStream class implements IDisposable (and therefore supports a Dispose() method), it also defines a Close() method that is used for the same purpose: // Assume you have imported // the System.IO namespace static void DisposeFileStream() { FileStream fs = new FileStream("myFile.txt", FileMode.OpenOrCreate); // Confusing, to say the least! // These method calls do the same thing! fs.Close(); fs.Dispose(); } CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME260 8849CH08.qxd 10/22/07 1:27 PM Page 260 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com While it does feel more natural to “close” a file rather than “dispose” of one, you may agree that this doubling up of disposal-centric methods is confusing. For the few types that do provide an alias, just remember that if a type implements IDisposable, calling Dispose() is always a correct course of action. Reusing the C# using Keyword When you are handling a managed object that implements IDisposable, it will be quite common to make use of structured exception handling to ensure the type’s Dispose() method is called in the event of a runtime exception: static void Main(string[] args) { Console.WriteLine("***** Fun with Dispose *****\n"); MyResourceWrapper rw = new MyResourceWrapper (); try { // Use the members of rw. } finally { // Always call Dispose(), error or not. rw.Dispose(); } } While this is a fine example of defensive programming, the truth of the matter is that few devel- opers are thrilled by the prospects of wrapping each and every disposable type within a try/finally block just to ensure the Dispose() method is called. To achieve the same result in a much less obtru- sive manner, C# supports a special bit of syntax that looks like this: static void Main(string[] args) { Console.WriteLine("***** Fun with Dispose *****\n"); // Dispose() is called automatically when the // using scope exits. using(MyResourceWrapper rw = new MyResourceWrapper()) { // Use rw object. } } If you were to look at the CIL code of the Main() method using ildasm.exe, you will find the using syntax does indeed expand to try/final logic, with the expected call to Dispose(): .method private hidebysig static void Main(string[] args) cil managed { .try { } // end .try finally { IL_0012: callvirt instance void SimpleFinalize.MyResourceWrapper::Dispose() } // end handler CHAPTER 8 ■ UNDERSTANDING OBJECT LIFETIME 261 8849CH08.qxd 10/22/07 1:27 PM Page 261 www.free-ebooks-download.org Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... type: // I'll draw anyone supporting IDraw3D static void DrawIn3D(IDraw3D itf3d) { Console.WriteLine("-> Drawing IDraw3D compatible type"); itf3d.Draw3D(); } We could now test whether an item in the Shape array supports this new interface, and if so, pass it into the DrawIn3D() method for processing: 279 8849CH09.qxd 10/1/07 10:41 AM Page 280 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com... inform the garbage collector to bypass the finalization process by calling GC.SuppressFinalize() If the object user forgets to call Dispose(), the object will eventually be finalized and have a chance to free up the internal resources The good news is that the object’s internal unmanaged resources will be freed one way or another Here is the next iteration of MyResourceWrapper, which is now finalizable and. .. current understanding of object-oriented development by examining the topic of interface-based programming Here you learn how to define and implement interfaces, and come to understand the benefits of building types that support “multiple behaviors.” Along the way, a number of related topics are also discussed, such as obtaining interface references, explicit interface implementation, and the construction... do the same ee -e bo o ks - do w nl oa d o Don’t concern yourself with the details of what these members actually do at this point Simply understand that the IDbConnection interface defines a set of members that are common to all ADO.NET connection objects Given this, you are guaranteed that each and every connection object supports members such as Open(), Close(), CreateCommand(), and so forth Furthermore,... must contend with the set of abstract members and provide an implementation To see this problem, recall the shapes hierarchy we defined in Chapter 6 Assume we defined a new abstract method in the Shape base class named GetNumberOfPoints(), which allows derived types to return the number of points required to render the shape: w fr ee -e bo o Clearly, the only type that has any points in the first place... three shapes (Circle and Hexagon) have been configured to support this new behavior: o oa d // Hexagon supports IPointy and IDraw3D public class Hexagon : Shape, IPointy, IDraw3D { public void Draw3D() { Console.WriteLine("Drawing Hexagon in 3D!"); } } rg // Circle supports IDraw3D public class Circle : Shape, IDraw3D { public void Draw3D() { Console.WriteLine("Drawing Circle in 3D!"); } } w w w fr... the rw2 object, and therefore when the application terminates, we hear a single beep If you were to comment out the call to Dispose() on the rw object, you would hear two beeps s Source Code The FinalizableDisposableClass project is included under the Chapter 8 subdirectory nl oa d o rg That wraps up our investigation of how the CLR is managing your objects via garbage collection While there are additional... Hexagon, and ThreeDCircle) must now provide a concrete implementation of this function even if it makes no sense to do so Again, the interface type provides a solution If we were to define an interface that represents the behavior of “having points,” we could simply plug it into the Hexagon type, leaving Circle and ThreeDCircle untouched w Defining Custom Interfaces w 272 Now that you better understand the. .. level, an interface is defined using the C# interface keyword Unlike other NET types, interfaces never specify a base class (not even System.Object) and their members never specify an access modifier (as all interface members are implicitly public and abstract) To get the ball rolling, here is a custom interface defined in C#: w w w fr // This interface defines the behavior of "having points." public... create the IPointy interface to use a read-only property rather than a traditional accessor method: o Interface types can also contain event (see Chapter 11) and indexer (see Chapter 12) definitions oa d s Note rg // The pointy behavior as a read-only property public interface IPointy { // A read-write property in an interface would look like // retVal PropName { get; set; } // while a write-only property . Returns the maximum of generations supported on the target system. Under Microsoft’s .NET 3. 5, there are three possible generations (0, 1, and 2). SuppressFinalize() Sets a flag indicating that the. well, as of .NET 3. 5, the Collect() method can also be passed in a value of the GCCollectionMode enumeration as a second parameter, to fine-tune exactly how the runtime should force the garbage. MyResourceWrapper(); } ■Source Code The SimpleFinalize project is included under the Chapter 8 subdirectory. Detailing the Finalization Process Not to beat a dead horse, but always remember that the role of the Finalize()

Ngày đăng: 12/08/2014, 23:20

TỪ KHÓA LIÊN QUAN