Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 88 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
88
Dung lượng
369,52 KB
Nội dung
Replacing the Stack and Queue with Their Generic Counterparts | 151 Console.WriteLine(numericQueue.Dequeue( )); Console.WriteLine(numericQueue.Dequeue( ).ToString( )); } Here is that same code using a System.Collections.Generic.Queue object: public static void UseGenericQueue( ) { // Create a generic Queue object. Queue<int> numericQueue = new Queue<int>( ); // Populate Queue. numericQueue.Enqueue(1); numericQueue.Enqueue(2); numericQueue.Enqueue(3); // De-populate Queue and display items. Console.WriteLine(numericQueue.Dequeue( )); Console.WriteLine(numericQueue.Dequeue( )); Console.WriteLine(numericQueue.Dequeue( )); } Here is a simple example of using a System.Collections.Stack object: public static void UseNonGenericStack( ) { // Create a non-generic Stack object. Stack numericStack = new Stack( ); // Populate Stack (causing a boxing operation to occur). numericStack.Push(1); numericStack.Push(2); numericStack.Push(3); // De-populate Stack and display items (causing an unboxing operation to occur). Console.WriteLine(numericStack.Pop( ).ToString( )); Console.WriteLine(numericStack.Pop( ).ToString( )); Console.WriteLine(numericStack.Pop( ).ToString( )); } Here is that same code using a System.Collections.Generic.Stack object: public static void UseGenericStack( ) { // Create a generic Stack object. Stack<int> numericStack = new Stack<int>( ); // Populate Stack. numericStack.Push(1); numericStack.Push(2); numericStack.Push(3); // De-populate Stack and display items. Console.WriteLine(numericStack.Pop( ).ToString( )); Console.WriteLine(numericStack.Pop( ).ToString( )); 152 | Chapter 4: Generics Console.WriteLine(numericStack.Pop( ).ToString( )); } Discussion On the surface, the generic and nongeneric Queue and Stack classes seem similar enough. However, it is a very different story underneath the surface. The basic use of the generic Queue and Stack objects are the same as with their nongeneric counter- parts, except for the syntax used to instantiate the objects. The generic form requires a type argument in order to create the type. The type argument in this example is an int. This type argument indicates that this Queue or Stack object will be able to con- tain only integer types, as well as any type that implicitly converts to an integer, such as a short: short s = 300; numericQueue.Enqueue(s); // OK, because of the implicit conversion However, a type that cannot be implicitly converted to an integer, such as a double, will cause a compile-time error: double d = 300; numericQueue.Enqueue(d); // Error, no implicit conversion available numericQueue.Enqueue((int)d); // OK, because of the explicit cast The nongeneric form does not require this type argument, because the nongeneric Queue and Stack objects are allowed to contain any item as an element because all items are convertible to type Object. When choosing between a generic and nongeneric Queue or Stack, you need to decide whether you wish to use a generic Queue or Stack object or a nongeneric Queue or Stack object. Choosing the generic Queue or Stack class over its nongeneric form gives you many benefits, including: Type-safety Each element contained in the data structure is typed to one specific type. This means no more casting of objects when they are added to or removed from the data structure. You cannot store multiple disparate types within a single data structure; you always know what type is stored within the data structure. Type checking is done at compile time rather than runtime. This boils down to writ- ing less code, achieving better performance, and making fewer errors. Shortened development time To make a type-safe data structure without using generics means having to sub- class the System.Collections.Queue or System.Collections.Stack class in order to create your own. This is time-consuming and error-prone. Performance The generic Queue or Stack does not require a cast that could fail to occur when adding and removing elements from it. In addition, no boxing operation occurs Replacing the Stack and Queue with Their Generic Counterparts | 153 when adding a value type to the Queue or Stack. Likewise, in almost all cases, no unboxing operation occurs when removing a value type from the Queue or Stack. Easier-to-read code Your code base will be much smaller because you will not have to subclass the nongeneric Queue or Stack class to create your own strongly typed class. In addi- tion, the type-safety features of generic code will allow you to better understand what the purpose of the Queue or Stack class is in your code. The following class members are implemented in the nongeneric Queue and Stack classes but not in their generic counterparts: Clone method IsSynchronized property SyncRoot property Synchronized method The addition of the Clone method on the nongeneric Queue and Stack classes is due to the ICloneable interface being implemented only on the nongeneric Queue and Stack classes. However, all other interfaces implemented by the generic and nongeneric Queue and Stack classes are identical. One way around the missing Clone method in the generic Queue and Stack classes is to use the constructor that accepts an IEnumerable<T> type. Since this is one of the interfaces that the Queue and Stack classes implement, it is easy to write. For the Queue object, the code is as follows: public static void CloneQueue( ) { // Create a generic Queue object. Queue<int> numericQueue = new Queue<int>( ); // Populate Queue. numericQueue.Enqueue(1); numericQueue.Enqueue(2); numericQueue.Enqueue(3); // Create a clone of the numericQueue. Queue<int> clonedNumericQueue = new Queue<int>(numericQueue); // This does a simple peek at the values, not a dequeue. foreach (int i in clonedNumericQueue) { Console.WriteLine("foreach: " + i.ToString( )); } // De-populate Queue and display items. Console.WriteLine(clonedNumericQueue.Dequeue( ).ToString( )); Console.WriteLine(clonedNumericQueue.Dequeue( ).ToString( )); Console.WriteLine(clonedNumericQueue.Dequeue( ).ToString( )); } 154 | Chapter 4: Generics The output for this method is shown here: foreach: 1 foreach: 2 foreach: 3 1 2 3 For the Stack object, the code is as follows: public static void CloneStack( ) { // Create a generic Stack object. Stack<int> numericStack = new Stack<int>( ); // Populate Stack. numericStack.Push(1); numericStack.Push(2); numericStack.Push(3); // Clone the numericStack object. Stack<int> clonedNumericStack = new Stack<int>(numericStack); // This does a simple peek at the values, not a pop. foreach (int i in clonedNumericStack) { Console.WriteLine("foreach: " + i.ToString( )); } // De-populate Stack and display items. Console.WriteLine(clonedNumericStack.Pop( ).ToString( )); Console.WriteLine(clonedNumericStack.Pop( ).ToString( )); Console.WriteLine(clonedNumericStack.Pop( ).ToString( )); } The output for this method is shown here: foreach: 1 foreach: 2 foreach: 3 1 2 3 This constructor creates a new instance of the Queue or Stack class containing the ele- ments copied from the IEnumerable<T> type. See Also The “System.Collections.Stack Class,” “System.Collections.Generic.Stack Class,” “System.Collections.Queue Class,” and “System.Collections.Generic.Queue Class” topics in the MSDN documentation. Using a Linked List | 155 4.5 Using a Linked List Problem You need a linked data structure that allows you to easily add and remove elements. Solution Use the generic LinkedList<T> class. The following method creates a LinkedList<T> class, adds nodes to this linked list object, and then uses several methods to obtain information from nodes within the linked list: public static void UseLinkedList( ) { Console.WriteLine("\r\n\r\n"); // Create TodoItem objects to add to the linked list TodoItem i1 = new TodoItem( ) { Name = "paint door", Comment = "Should be done third" }; TodoItem i2 = new TodoItem( ) { Name = "buy door", Comment = "Should be done first" }; TodoItem i3 = new TodoItem( ) { Name = "assemble door", Comment = "Should be done second" }; TodoItem i4 = new TodoItem( ) { Name = "hang door", Comment = "Should be done last" }; // Create a new LinkedList object LinkedList<TodoItem> todoList = new LinkedList<TodoItem>( ); // Add the items todoList.AddFirst(i1); todoList.AddFirst(i2); todoList.AddBefore(todoList.Find(i1), i3); todoList.AddAfter(todoList.Find(i1), i4); // Display all items foreach (TodoItem tdi in todoList) { Console.WriteLine(tdi.Name + " : " + tdi.Comment); } // Display information from the first node in the linked list Console.WriteLine("todoList.First.Value.Name == " + todoList.First.Value.Name); // Display information from the second node in the linked list Console.WriteLine("todoList.First.Next.Value.Name == " + todoList.First.Next.Value.Name); // Display information from the next to last node in the linked list Console.WriteLine("todoList.Last.Previous.Value.Name == " + todoList.Last.Previous.Value.Name); } 156 | Chapter 4: Generics The output for this method is shown here: buy door : Should be done first assemble door : Should be done second paint door : Should be done third hang door : Should be done last todoList.First.Value.Name == buy door todoList.First.Next.Value.Name == assemble door todoList.Last.Previous.Value.Name == paint door This is the TodoItem class, which is a simple container of two string properties Name and Comment. The properties use the new Automatically Implemented Properties fea- ture in C# 3.0 that allows you to declare properties, and the definition of the back- ing fields is generated automatically: /// <summary> /// Todo list item /// </summary> public class TodoItem { /// <summary> /// Name of the item /// </summary> public string Name { get; set; } /// <summary> /// Comment for the item /// </summary> public string Comment { get; set; } } Discussion The LinkedList<T> class in the .NET Framework is a doubly linked list. This is because each node in the linked list contains a pointer to both the previous node and the next node in the linked list. Figure 4-1 shows what a doubly linked list looks like diagrammed on paper. Each node in this diagram represents a single LinkedListNode<T> object. Notice that each node (i.e., the square boxes) contains a reference to the next node (i.e., the arrows pointing to the right) and a pointer to the previous node (i.e., the arrows pointing to the left) in the linked list. In contrast, a singly linked list contains only pointers to the next node in the list. There is no pointer to the previous node. Figure 4-1. Graphical representation of a doubly linked list with three nodes NULL NODE NODE NULL NODE Using a Linked List | 157 In the LinkedList<T> class, the previous node is always accessed through the Previous property, and the next node is always accessed through the Next property. The first node’s Previous property in the linked list always returns a null value. Like- wise, the last node’s Next property in the linked list always returns a null value. Each node (represented by the boxes in Figure 4-1) in the linked list is actually a generic LinkedListNode<T> object. So a LinkedList<T> object is actually a collection of LinkedListNode<T> objects. Each of these LinkedListNode<T> objects contains proper- ties to access the next and previous LinkedListNode<T> objects, as well as the object contained within it. The object contained in the LinkedListNode<T> object is accessed through the Value property. In addition to these properties, a LinkedListNode<T> object also contains a property called List, which allows access to the containing LinkedList<T> object. Items to be aware of with List<T> and LinkedList<T>: • Adding and removing nodes within a List<T> is, in general, faster than the same operation using a LinkedList<T> class. •A List<T> stores its data essentially in one big array on the managed heap, whereas the LinkedList<T> can potentially store its nodes all over the managed heap. This forces the garbage collector to work that much harder to manage LinkedList<T> node objects on the managed heap. • Note that the List<T>.Insert* methods can be slower than adding a node any- where within a LinkedList<T> using one of its Add* methods. However, this is dependent on where the object is inserted into the List<T>.AnInsert method must shift all the elements within the List<T> object at the point where the new element is inserted up by one position. If the new element is inserted at or near the end of the List<T>, the overhead of shifting the existing elements is negligi- ble compared to the garbage collector overhead of managing the LinkedList<T> nodes objects. Another area where the List<T> can outperform the LinkedList<T> is when you’re doing an indexed access. With the List<T>, you can use the indexer to do an indexed lookup of the element at the specified posi- tion. However, with a LinkedList<T> class, you do not have that luxury. With a LinkedList<T> class, you must navigate the LinkedListNode<T> objects using the Previous and Next properties on each LinkedListNode<T>, running through the list until you find the one at the specified position. •A List<T> class also has performance benefits over a LinkedList<T> class when searching for an element or node. The List<T>.BinarySearch method is faster at finding elements within a List<T> object than its comparable methods within the LinkedList<T> class, namely the Contains, Find, and FindLast methods. Table 4-2 shows the comparison between List<T> and LinkedList<T>. 158 | Chapter 4: Generics See Also The “LinkedList<T> Class” topic in the MSDN documentation. 4.6 Creating a Value Type That Can Be Initialized to Null Problem You have a variable that is a numeric type, which will hold a numeric value obtained from a database. The database may return this value as a null. You need a simple, clean way to store this numeric value, even if it is returned as a null. Solution Use a nullable value type. There are two ways of creating a nullable value type. The first way is to use the ? type modifier: int? myDBInt = null; The second way is to use the Nullable<T> generic type: Nullable<int> myDBInt = new Nullable<int>( ); Discussion Both of the following statements are equivalent: int? myDBInt = null; Nullable<int> myDBInt = new Nullable<int>( ); In both cases, myDBInt is a nullable type and is initialized to null. A nullable type implements the INullableValue interface, which has two read-only property members, HasValue and Value. The HasValue property returns false if the nullable value is set to null; otherwise, it returns true.IfHasValue returns true, you can access the Value property, which contains the currently stored value. If HasValue returns false and you attempt to read the Value property, you will get an InvalidOperationException thrown. This is because the Value property is undefined at this point. Below is an example of a test of nullable value using the HasValue prop- erty value: Table 4-2. Performance comparison between List<T> and LinkedList<T> Action Who Wins Adding/Removing Nodes List<T> Inserting nodes LinkedList<T>* Indexed access List<T> Node searching List<T> Creating a Value Type That Can Be Initialized to Null | 159 if (myDBInt.HasValue) Console.WriteLine("Has a value: " + myDBInt.Value); else Console.WriteLine("Does not have a value (NULL)"); In addition, one can simply compare the value to null, as shown below: if (myDBInt != null) Console.WriteLine("Has a value: " + myDBInt.Value); else Console.WriteLine("Does not have a value (NULL)"); Either method is acceptable. When casting a nullable value to a non-nullable value, the cast operates as it would normally, except when the nullable type is set to null. In this case, an InvalidOperationException is thrown. When casting a non-nullable value to a nul- lable value, the cast operates as it would normally. No InvalidOperationException will be thrown, as the non-nullable value can never be null. The tricky thing to watch out for with nullable types is when comparisons are per- formed. For example, if the following code is executed: if (myTempDBInt < 100) Console.WriteLine("myTempDBInt < 100"); else Console.WriteLine("myTempDBInt >= 100"); The text “myTempDBInt >= 100” is displayed, which is obviously incorrect if the value of myTempDBInt is null. To fix this code, you have to check if myTempDBInt is null.Ifit is not, you can execute the if statement in the previous code block: if (myTempDBInt != null) { if (myTempDBInt < 100) Console.WriteLine("myTempDBInt < 100"); else Console.WriteLine("myTempDBInt >= 100"); } else { // Handle the null here. } Another interesting thing about nullable types is that you can use them in expres- sions similar to normal numeric types, for example: int? DBInt = 10; int Value = 2; int? Result = DBInt + Value; // Result == 12 The result of using a nullable value in most operators is a null if any nullable value is null. 160 | Chapter 4: Generics Neither the comparison operators nor the null coalescing operator lift to nullable. However, if none of the nullable values is null, the operation is evaluated as it nor- mally would be. If DBInt, for example, were set to null, the value placed in Result would also be null. See Also The “Nullable<T> Generic Class” and “Using Nullable Types” topics in the MSDN documentation. 4.7 Reversing the Contents of a Sorted List Problem You want to be able to reverse the contents of a sorted list of items while also main- taining the ability to access them in both array and list styles like SortedList and the generic SortedList<T> classes provide. Neither SortedList nor SortedList<T> pro- vides a direct way to accomplish this without reloading the list. Solution Use LINQ to Objects to query the SortedList<T> and apply a descending order to the information in the list. After instantiating a SortedList<TKey, TValue>, the key of which is an int and the value of which is a string, a series of unordered numbers and their text representations are inserted into the list. Those items are then displayed: SortedList<int, string> data = new SortedList<int, string>( ); data.Add(2, "two"); data.Add(5, "five"); data.Add(3, "three"); data.Add(1, "one"); foreach (KeyValuePair<int, string> kvp in data) { Debug.WriteLine("\t" + kvp.Key + "\t" + kvp.Value); } The output for the list is shown sorted in ascending order (the default): 1 one 2 two 3 three 5 five [...]... = 1 ← The original array 2 3 4 5 Element Element Element Element Element 5.2 0 1 2 3 4 0 1 2 3 4 = = = = = 5 ← The array with elements swapped 2 3 4 1 Reversing an Array Quickly Problem You want an efficient method to reverse the order of elements within an array Solution You can use the static Reverse method, as in this snippet of code: int[] someArray = new int[5] {1,2 ,3, 4,5}; Array.Reverse(someArray);... someArray[counter]); } } Reversing an Array Quickly | 179 This code displays the following: Element Element Element Element Element 0 1 2 3 4 = = = = = 1 ← The original array 2 3 4 5 Element Element Element Element Element 0 1 2 3 4 = = = = = 5 ← The reversed array 4 3 2 1 Reversing the elements in an array is a fairly common routine The algorithm here swaps elements in the array until it is fully reversed... Console.WriteLine("Count2: " + count); count = arrayExt.BinarySearchCountAll (3) ; Console.WriteLine("Count3: " + count); count = arrayExt.BinarySearchCountAll(1); Console.WriteLine("Count1: " + count); } } This code outputs the following: CONTAINS TOTAL-Count2: 4 Count3: 1 Count1: 1 BINARY SEARCH COUNT ALL-Count2: 4 Count3: 1 Count1: 1 The CountAll method uses a sequential search that is performed... List class: class Test { static void Main( ) { List arrayExt = new List( ) {-2,-2,-1,-1,1,2,2,2,2 ,3, 100,4,5}; Console.WriteLine(" CONTAINS TOTAL "); int count = arrayExt.CountAll(2); Console.WriteLine("Count2: " + count); count = arrayExt.CountAll (3) ; Console.WriteLine("Count3: " + count); count = arrayExt.CountAll(1); Console.WriteLine("Count1: " + count); Console.WriteLine("\r\n BINARY... well: single-dimensional, jagged, and even jagged multidimensional Multidimensional arrays are defined here: 176 | Chapter 5: Collections int[,] foo = new int[2 ,3] ; // A 2-dimensional array // containing 6 elements int[,,] bar = new int[2 ,3, 4]; // A 3- dimensional array // containing 24 elements A two-dimensional array is usually described as a table with rows and columns The foo array would be described... there are some things to watch out for For example, the generic Dictionary class does not implement the ICloneable interface, while the Hashtable class does Table 4 -3 shows the equivalent members that are implemented in both classes Table 4 -3 Equivalent members in the Hashtable and the generic Dictionary classes Members in the Hashtable class Equivalent members in the generic Dictionary class N/A Comparer... numbers = new Dictionary( ) { { 1, "one" }, { 2, "two" } }; Console.WriteLine("numbers.ContainsKey(1) == " + numbers.ContainsKey(1)); Console.WriteLine("numbers.ContainsKey (3) == " + numbers.ContainsKey (3) ); The CopyTo method is also easy to simulate in the Dictionary class, but it involves a little more work: // Create and populate a Dictionary Dictionary numbers = new Dictionary . if (myTempDBInt < 100 ) Console.WriteLine("myTempDBInt < 100 "); else Console.WriteLine("myTempDBInt >= 100 "); The text “myTempDBInt >= 100 ” is displayed, which. (myTempDBInt != null) { if (myTempDBInt < 100 ) Console.WriteLine("myTempDBInt < 100 "); else Console.WriteLine("myTempDBInt >= 100 "); } else { // Handle the null. Name and Comment. The properties use the new Automatically Implemented Properties fea- ture in C# 3. 0 that allows you to declare properties, and the definition of the back- ing fields is generated