Replacing the Stack and Queue with Their Generic Counterparts | 151 Console.WriteLinenumericQueue.Dequeue ; Console.WriteLinenumericQueue.Dequeue .ToString ; } Here is that same code u
Trang 1Replacing the Stack and Queue with Their Generic Counterparts | 151
Console.WriteLine(numericQueue.Dequeue( ));
Console.WriteLine(numericQueue.Dequeue( ).ToString( ));
}
Here is that same code using aSystem.Collections.Generic.Queue object:
public static void UseGenericQueue( )
{
// Create a generic Queue object.
Queue<int> numericQueue = new Queue<int>( );
Here is a simple example of using aSystem.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).
Here is that same code using aSystem.Collections.Generic.Stack object:
public static void UseGenericStack( )
{
// Create a generic Stack object.
Stack<int> numericStack = new Stack<int>( );
Trang 2a type argument in order to create the type The type argument in thisexample isan
int Thistype argument indicatesthat thisQueueorStackobject will be able to tain only integer types, aswell asany type that implicitly convertsto an integer, such
con-as ashort:
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 adouble,will cause a compile-time error:
When choosing between a generic and nongenericQueueorStack, you need to decidewhether you wish to use a generic Queue or Stack object or a nongeneric Queue orStackobject Choosing the genericQueueorStackclass over its nongeneric form givesyou many benefits, including:
Type-safety
Each element contained in the data structure is typed to one specific type Thismeansno more casting of objectswhen they are added to or removed from thedata structure You cannot store multiple disparate types within a single datastructure; you always know what type is stored within the data structure Typechecking isdone at compile time rather than runtime Thisboilsdown 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 class the System.Collections.Queue or System.Collections.Stack class in order
sub-to create your own This is time-consuming and error-prone
Performance
The genericQueueorStackdoesnot require a cast that could fail to occur whenadding and removing elementsfrom it In addition, no boxing operation occurs
Trang 3Replacing the Stack and Queue with Their Generic Counterparts | 153
when adding a value type to theQueueorStack Likewise, in almost all cases, nounboxing operation occurs when removing a value type from theQueue or Stack.
Easier-to-read code
Your code base will be much smaller because you will not have to subclass thenongenericQueueorStackclass to create your own strongly typed class In addi-tion, the type-safety features of generic code will allow you to better understandwhat the purpose of theQueue or Stack class is in your code.
The following class members are implemented in the nongeneric Queue and Stackclasses but not in their generic counterparts:
One way around the missingClonemethod in the genericQueueandStackclasses is
to use the constructor that accepts anIEnumerable<T> type Since thisisone of theinterfacesthat the Queue and Stack classes implement, it is easy to write For theQueue 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)
Trang 4For theStack 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)
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
Trang 5Using a Linked List | 155
public static void UseLinkedList( )
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)
Trang 6156 | 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
ThisistheTodoItemclass, which is a simple container of two string propertiesNameandComment The propertiesuse the new Automatically Implemented Propertiesfea-ture in C# 3.0 that allowsyou to declare properties, and the definition of the back-ing fields is generated automatically:
Notice that each node (i.e., the square boxes) contains a reference to the next node(i.e., the arrowspointing to the right) and a pointer to the previousnode (i.e., thearrows pointing to the left) in the linked list In contrast, a singly linked list containsonly 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
Trang 7Using a Linked List | 157
In the LinkedList<T> class, the previous node is always accessed through thePreviousproperty, and the next node is always accessed through theNextproperty.The first node’sPreviousproperty in the linked list always returns anullvalue Like-wise, the last node’sNext 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 agenericLinkedListNode<T>object So aLinkedList<T>object isactually a collection ofLinkedListNode<T>objects Each of theseLinkedListNode<T>objectscontainsproper-tiesto accessthe next and previousLinkedListNode<T>objects, aswell asthe objectcontained within it The object contained in theLinkedListNode<T>object is accessedthrough the Value property In addition to these properties, a LinkedListNode<T>object also contains a property called List, which allowsaccessto the containing LinkedList<T> object.
Items to be aware of withList<T> and LinkedList<T>:
• Adding and removing nodeswithin aList<T>is, in general, faster than the sameoperation using aLinkedList<T> class.
• A List<T> stores its data essentially in one big array on the managed heap,whereastheLinkedList<T>can potentially store its nodes all over the managedheap Thisforcesthe garbage collector to work that much harder to manageLinkedList<T> node objects on the managed heap.
• Note that theList<T>.Insert* methodscan be slower than adding a node where within a LinkedList<T>using one of its Add* methods However, this isdependent on where the object isinserted into theList<T> An Insert methodmust shift all the elements within theList<T>object at the point where the newelement is inserted up by one position If the new element is inserted at or nearthe end of theList<T>, the overhead of shifting the existing elements is negligi-ble compared to the garbage collector overhead of managing theLinkedList<T>nodesobjects Another area where the List<T> can outperform theLinkedList<T>is when you’re doing an indexed access With the List<T>, youcan use the indexer to do an indexed lookup of the element at the specified posi-tion However, with aLinkedList<T>class, you do not have that luxury With aLinkedList<T>class, you must navigate theLinkedListNode<T>objectsusing thePrevious and Next propertieson eachLinkedListNode<T>, running through thelist until you find the one at the specified position
any-• AList<T>class also has performance benefits over a LinkedList<T>class whensearching for an element or node TheList<T>.BinarySearchmethod isfaster atfinding elementswithin aList<T>object than itscomparable methodswithin theLinkedList<T> class, namely the Contains, Find, and FindLast methods.
Table 4-2 shows the comparison betweenList<T> and LinkedList<T>.
Trang 8158 | Chapter 4: Generics
See Also
The “LinkedList<T> Class” topic in the MSDN documentation
Null
Problem
You have a variable that isa numeric type, which will hold a numeric value obtainedfrom a database The database may return this value as anull You need a simple,clean way to store this numeric value, even if it is returned as anull.
Solution
Use a nullable value type There are two waysof creating a nullable value type The
first way is to use the ? type modifier:
int? myDBInt = null;
The second way is to use theNullable<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 implementstheINullableValue interface, which hastwo read-onlyproperty members,HasValue and Value The HasValueproperty returns false if thenullable value isset tonull; otherwise, it returns true If HasValuereturnstrue, youcan access theValueproperty, which containsthe currently stored value IfHasValuereturns false and you attempt to read the Value property, you will get anInvalidOperationExceptionthrown Thisisbecause the Valueproperty isundefined
at this point Below is an example of a test of nullable value using theHasValueerty value:
prop-Table 4-2 Performance comparison between List<T> and LinkedList<T>
Adding/Removing Nodes List<T>
Inserting nodes LinkedList<T>*
Indexed access List<T>
Node searching List<T>
Trang 9Creating 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 tonull, 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 wouldnormally, except when the nullable type isset to null In thiscase, anInvalidOperationException isthrown When casting a non-nullable value to a nul-lable value, the cast operates as it would normally No InvalidOperationExceptionwill be thrown, as the non-nullable value can never benull.
The tricky thing to watch out for with nullable typesiswhen comparisonsare 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
ofmyTempDBIntisnull To fix thiscode, you have to check if myTempDBIntisnull If it
is not, you can execute theif statement in the previous code block:
int? Result = DBInt + Value; // Result == 12
The result of using a nullable value in most operators is anullif any nullable value isnull.
Trang 10Use LINQ to Objects to query theSortedList<T>and apply adescendingorder to theinformation in the list After instantiating a SortedList<TKey, TValue>, the key ofwhich isan intand the value of which isastring, a series of unordered numbersand their text representations are inserted into the list Those items are thendisplayed:
SortedList<int, string> data = new SortedList<int, string>( );
Trang 11Reversing the Contents of a Sorted List | 161
Now the sort order is reversed by creating a query using LINQ to Objects and ting theorderbyclause todescending The results are then displayed from the queryresult set:
// query ordering by descending
var query = from d in data
orderby d.Key descending
// requery ordering by descending
query = from d in data
orderby d.Key descending
// Just go against the original list for ascending
foreach (KeyValuePair<int, string> kvp in data)
Trang 12162 | Chapter 4: Generics
Discussion
ASortedListblends array and list syntax to allow for accessing the data in either mat, which can be a handy thing to do The data is accessible as key/value pairs ordirectly by index and will not allow duplicate keysto be added In addition, valuesthat are reference or nullable typescan benull, but keyscannot The itemscan beiterated using a foreach loop, with KeyValuePair being the type returned Whileaccessing elements of theSortedList<T>, they may only be read from The usual iter-ator syntax prohibits updating or deleting elements of the list while reading, as it willinvalidate the iterator
for-Theorderbyclause in the query causes the result set of the query to be ordered either
inascending(the default) ordescendingorder This sorting is accomplished throughuse of the default comparer for the element type, so it can be affected by overridingtheEqualsmethod for elements that are custom classes Multiple keys can be speci-fied for theorderbyclause, which has the effect of nesting the sort order such as sort-ing by “last name” and then “first name.”
can-public class Lottery
// pick the winning numbers
_numbers = new List<int>(5) { 17, 21, 32, 44, 58 };
}
Trang 13Making Read-Only Collections the Generic Way | 163
public ReadOnlyCollection<int> Results
{
// return a wrapped copy of the results
get { return new ReadOnlyCollection<int>(_numbers); }
}
}
Lottery hasa List<int> of winning numbersthat it fillsin the constructor Theinteresting part is that it also exposes a property called Results, which returnsa ReadOnlyCollectiontyped as<int>for seeing the winning numbers Internally, a newReadOnlyCollectionwrapper iscreated to hold theList<int>that hasthe numbersin
it, and then this instance is returned for use by the user
If users then attempt to set a value on the collection, they get a compile error:
Lottery tryYourLuck = new Lottery( );
// Print out the results.
for (int i = 0; i < tryYourLuck.Results.Count; i++)
eas-See Also
The “ReadOnlyCollection” topic in the MSDN documentation
Trang 14Here is a simple example of using aSystem.Collections.Hashtable object:
public static void UseNonGenericHashtable( )
{
Console.WriteLine("\r\nUseNonGenericHashtable");
// Create and populate a Hashtable
Hashtable numbers = new Hashtable( )
{ {1, "one"},"one"}, // Causes a boxing operation to occur for the key {2, "two"} }; // Causes a boxing operation to occur for the key
// Display all key/value pairs in the Hashtable
// Causes an unboxing operation to occur on each iteration for the key
foreach (DictionaryEntry de in numbers)
// Create and populate a Dictionary
Dictionary<int, string> numbers = new Dictionary<int, string>( )
{ { 1, "one" }, { 2, "two" } };
// Display all key/value pairs in the Dictionary
foreach (KeyValuePair<int, string> kvp in numbers)
{
Trang 15Replacing the Hashtable with Its Generic Counterpart | 165
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
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
Count property Count property
IsFixedSize property ((IDictionary)myDict).IsFixedSize
IsReadOnly property ((IDictionary)myDict).IsReadOnly
IsSynchronized property ((IDictionary)myDict).IsSynchronized
SyncRoot property ((IDictionary)myDict).SyncRoot
Values property Values property
Clear method Clear method
Clone method Use overloaded constructor, which accepts an IDictionary<T,U> type
Contains method ContainsKey method
ContainsKey method ContainsKey method
ContainsValue method ContainsValue method
CopyTo method ((ICollection)myDict).CopyTo(arr,0)
Remove method Remove method
Synchronized static method lock(myDictionary.SyncRoot) { }
Trang 16166 | Chapter 4: Generics
In several cases within Table 4-3, there is not a one-to-one correlation between themembersof a Hashtableand the membersof the generic Dictionary class Startingwith the properties, notice that only theCount, Keys, Values, and Itempropertiesarepresent in both classes To make up for the missing properties in the Dictionaryclass, you can perform a cast to anIDictionary The following code shows how touse these casts to get at the missing properties:
Dictionary<int, string> numbers = new Dictionary<int, string>( );
Since theClonemethod is also missing from the genericDictionaryclass (due to thefact that thisclassdoesnot implement theICloneableinterface), you can instead usethe overloaded constructor, which accepts anIDictionary<T,U> type:
// Create and populate a Dictionary
Dictionary<int, string> numbers = new Dictionary<int, string>( )
{ { 1, "one" }, { 2, "two" } };
// Display all key/value pairs in the original Dictionary.
foreach (KeyValuePair<int, string> kvp in numbers)
{
Console.WriteLine("Original Key: " + kvp.Key + "\tValue: " + kvp.Value); }
// Clone the Dictionary object.
Dictionary<int, string> clonedNumbers = new Dictionary<int, string>(numbers);
// Display all key/value pairs in the cloned Dictionary.
foreach (KeyValuePair<int, string> kvp in numbers)
{
Console.WriteLine("Cloned Key: " + kvp.Key + "\tValue: " + kvp.Value); }
There are two more methods that are missing from theDictionaryclass, theContains
class In theHashtableclass, theContainsmethod and theContainsKeymethod bothexhibit the same behavior; therefore, you can simply use theContainsKeymethod oftheDictionary class to simulate the Contains method of the Hashtable class:
Trang 17Replacing the Hashtable with Its Generic Counterpart | 167
// Create and populate a Dictionary
Dictionary<int, string> numbers =
new Dictionary<int, string>( )
// Create and populate a Dictionary
Dictionary<int, string> numbers =
new Dictionary<int, string>( )
{ { 1, "one" }, { 2, "two" } };
// Display all key/value pairs in the Dictionary.
foreach (KeyValuePair<int, string> kvp in numbers)
{
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
}
// Create object array to hold copied information from Dictionary object.
KeyValuePair<int, string>[] objs = new KeyValuePair<int, string>[numbers.Count];
// Calling CopyTo on a Dictionary
// Copies all KeyValuePair objects in Dictionary object to objs[]
((IDictionary)numbers).CopyTo(objs, 0);
// Display all key/value pairs in the objs[].
foreach (KeyValuePair<int, string> kvp in objs)
See Also
The “System.Collections.Hashtable Class” and nary Class” topics in the MSDN documentation
Trang 18// Create a Dictionary object and populate it
Dictionary<int, string> myStringDict = new Dictionary<int, string>( )
{ { 1, "Foo" }, { 2, "Bar" }, { 3, "Baz" } };
// Enumerate and display all key and value pairs.
foreach (KeyValuePair<int, string> kvp in myStringDict)
The nongeneric System.Collections.Hashtable (the counterpart to the System.
System.Collections.SortedList classes support foreach using the DictionaryEntrytype, as shown here:
Hashtable myHashtable = new Hashtable( )
{ { 1, "Foo" }, { 2, "Bar" }, { 3, "Baz" } };
foreach (DictionaryEntry de in myHashtable)
However, the Dictionaryobject supports the KeyValuePair<T,U>type when using a
IEnumerator, which in turn returns KeyValuePair<T,U> types, not DictionaryEntrytypes
TheKeyValuePair<T,U>type is well suited to be used when enumerating the genericDictionaryclass with aforeach loop TheDictionaryEntry object containskey andvalue pairsasobjects, whereasthe KeyValuePair<T,U> type containskey and valuepairsastheir original types, defined when creating theDictionaryobject Thisboosts
Trang 19Constraining Type Arguments | 169
performance and can reduce the amount of code you have to write, asyou do nothave to cast the key and value pairs to their original types
public class DisposableList<T> : IList<T>
where T : class, IDisposable
{
private List<T> _items = new List<T>( );
// Private method that will dispose of items in the list
private void Delete(T item)
get {return (_items[index]);}
set {_items[index] = value;}
}
public void RemoveAt(int index)
{
Trang 21Constraining Type Arguments | 171
on that object Thisallowsyou to transparently handle the management of anyobject stored within thisDisposableList object.
The following code exercises aDisposableList object:
public static void TestDisposableListCls( )
{
DisposableList<StreamReader> dl = new DisposableList<StreamReader>( );
// Create a few test objects.
StreamReader tr1 = new StreamReader("c:\\boot.ini");
StreamReader tr2 = new StreamReader("c:\\autoexec.bat");
StreamReader tr3 = new StreamReader("c:\\config.sys");
// Add the test object to the DisposableList.
// Call Dispose before any of the disposable objects are
// removed from the DisposableList.
dl.RemoveAt(0);
dl.Remove(tr1);
dl.Clear( );
}
Trang 22172 | Chapter 4: Generics
Discussion
Thewhere keyword is used to constrain a type parameter to accept only argumentsthat satisfy the given constraint For example, theDisposableListhasthe constraintthat any type argumentT must implement the IDisposable interface:
public class DisposableList<T> : IList<T>
where T : IDisposable
This means that the following code will compile successfully:
DisposableList<StreamReader> dl = new DisposableList<StreamReader>( );
but the following code will not:
DisposableList<string> dl = new DisposableList<string>( );
Thisisbecause thestring type doesnot implement the IDisposableinterface, andtheStreamReader type does.
Other constraints on the type argument are allowed, in addition to requiring one ormore specific interfaces to be implemented You can force a type argument to beinherited from a specific base class, such as theTextReader class:
public class DisposableList<T> : IList<T>
where T : System.IO.TextReader, IDisposable
You can also determine if the type argument is narrowed down to only value types oronly reference types The following class declaration is constrained to using onlyvalue types:
public class DisposableList<T> : IList<T>
where T : struct
This class declaration is constrained to only reference types:
public class DisposableList<T> : IList<T>
where T : class
In addition, you can also require any type argument to implement a public defaultconstructor:
public class DisposableList<T> : IList<T>
where T : IDisposable, new( )
Using constraints allows you to write generic types that accept a narrower set ofavailable type arguments If theIDisposableconstraint is omitted in the Solution forthisrecipe, a compile-time error will occur Thisisbecause not all of the typesthatcan be used as the type argument for the DisposableList class will implement theIDisposableinterface If you skip this compile-time check, a DisposableList objectmay contain objectsthat do not have a public no-argumentDisposemethod In thiscase, a runtime exception will occur Generics and constraints in particular forcestrict type checking of the class-type arguments and allow you to catch these prob-lems at compile time rather than at runtime
Trang 23Initializing Generic Variables to Their Default Values | 173
See Also
The “where Keyword” topic in the MSDN documentation
4.12 Initializing Generic Variables to Their Default
Values
Problem
You have a generic class that contains a variable of the same type as the type ter defined by the class itself Upon construction of your generic object, you wantthat variable to be initialized to its default value
parame-Solution
Simply use thedefault keyword to initialize that variable to its default value:
public class DefaultValueExample<T>
The code to use this class is shown here:
public static void ShowSettingFieldsToDefaults( )
{
DefaultValueExample<int> dv = new DefaultValueExample<int>( );
// Check if the data is set to its default value; true is returned.
bool isDefault = dv.IsDefaultData( );
Console.WriteLine("Initial data: " + isDefault);
Trang 24Initial data: True
Set data: False
Thedefaultkeyword allowsyou to tell the compiler that at compile time the defaultvalue of this variable should be used If the type argument supplied is a numericvalue (e.g.,int, long, decimal), then the default value iszero If the type argumentsupplied is a reference type, then the default value isnull If the type argument sup-plied isa struct, then the default value of the struct isdetermined by initializingeach member field to its default value
See Also
Recipe 4.6, and the “default Keyword in Generic Code” topic in the MSDNdocumentation
Trang 25pairs The following collection types consist of a straightforward list of elements: System.Collections.ArrayList
These collection classes are organized under theSystem.Collectionsand theSystem Collections.Generic namespaces In addition to these namespaces, another name-space calledSystem.Collections.Specializedcontainsa few more useful collectionclasses These classes might not be as well known as the previous classes, so here is ashort explanation of the collection classes under theSystem.Collections.Specializednamespace:
Trang 26This class contains two static methods: one to create a case-insensitiveHashtableand another to create a case-insensitiveSortedList When you directly create a HashtableandSortedListobject, you always create a case-sensitiveHashtableorSortedList, unless you use one of the constructors that take an IComparerandpassCaseInsensitiveComparer.Default to it.
NameValueCollection
This collection consists of key and value pairs, which are both of typeString.The interesting thing about this collection is that it can store multiple stringvalues with a single key The multiple string values are comma-delimited TheString.Split method is useful when breaking up multiple strings in a value StringCollection
This collection is a simple list containing string elements This list acceptsnullelements as well as duplicate strings This list is case-sensitive
StringDictionary
ThisisaHashtablethat stores both the key and value as strings Keys are verted to all-lowercase letters before being added to theHashtable, allowing forcase-insensitive comparisons Keys cannot benull, but values may be set to null.The C# compiler also supports a fixed-size array Arrays of any type may be createdusing the following syntax:
int[] foo = new int[2];
Trang 27Swapping Two Elements in an Array | 177
int[,] foo = new int[2,3]; // A 2-dimensional array
Jagged arrays are arrays of arrays If you picture a jagged array as a one-dimensionalarray with each element in that array containing another one-dimensional array, itcould have a different number of elementsin each row A jagged array isdefined asfollows:
int[][] baz = new int[2][] {new int[2], new int[3]};
Thebazarray consists of a one-dimensional array containing two elements Each ofthese elements consists of another array, the first array having two elements and thesecond array having three
The rest of this chapter contains recipes dealing with arrays and the various tion types
Trang 28This code produces the following output:
Element 0 = 1 ← The original array
You can use the staticReverse method, as in this snippet of code:
int[] someArray = new int[5] {1,2,3,4,5};
Trang 29Reversing an Array Quickly | 179
theArray[counter] = theArray[theArray.Length - counter - 1];
theArray[theArray.Length - counter - 1] = tempHolder;
Discussion
The followingTestArrayReversalmethod creates a test array of five integers and playsthe elementsin their initial order Next, theDoReversal<T>method iscalled toreverse the elements in the array After this method returns, the array is then dis-played a second time as a reversed array:
public static void TestArrayReversal( )
{
int[] someArray = new int[5] {1,2,3,4,5};
for (int counter = 0; counter < someArray.Length; counter++)
Trang 30180 | Chapter 5: Collections
This code displays the following:
Element 0 = 1 ← The original array
Note that thisisinteger division, so if the array length is an odd number, the
remain-der is discarded Since your array length is five, thefor loop counts from zero to one.Inside of the loop are three lines of code:
tempHolder = theArray[counter];
theArray[counter] = theArray[theArray.Length - counter - 1];
theArray[theArray.Length - counter - 1] = tempHolder;
These three lines swap the first half of the array with the second half As theforloopcounts from zero, these three lines swap the first and last elements in the array Theloop incrementsthe counter by one, allowing the second element and the next-to-last element to be swapped This continues until all elements in the array have beenswapped
There isone element in the array that cannot be swapped; thisisthe middle element
of an array with an odd number for the length For example, in thiscode, there arefive elements in the array The third element should not be swapped Put anotherway, all of the other elementspivot on thisthird element when they are swapped.This does not occur when the length of the array is an even number
By dividing the array length by two, you can compensate for even or odd array ments Since you get back an integer number from this division, you can easily skipover the middle element in an array with an odd length
ele-See Also
Recipe 5.1, and the “Array.Reverse Method” topic in the MSDN documentation
Trang 31Writing a More Flexible StackTrace Class | 181
be much better if theStackTrace object operated like a collection.
The StackTrace object can now be used as if it were a collection of StackFrameobjects To obtain aStackTraceobject for the current point in code, use the follow-ing code:
StackTrace sTrace = new StackTrace( );
IList<StackFrame> frames = sTrace.ToList( );
To display a portion or all of the stack trace, use the following code:
// Display the first stack frame.
Console.WriteLine(frames[0].ToString( ));
Example 5-1 Writing a More Flexible StackTrace Class
public static ReadOnlyCollection<StackFrame> ToList(this StackTrace stackTrace)
var frames = new StackFrame[stackTrace.FrameCount];
for (int counter = 0; counter < stackTrace.FrameCount; counter++)
Trang 32182 | Chapter 5: Collections
// Display all stack frames.
foreach (StackFrame SF in frames)
To copy theStackFrame objects to a new array, use the following code:
StackFrame[] myNewArray = new StackFrame[frames.Count];
isusu-to be discarded:
StackTraceList arrStackTrace = new StackTraceList(1);
See Also
The “StackTraceClass” and “ReadOnlyCollection class” topics in the MSDN
docu-mentation Also see the “Adapter Design Pattern” chapter in Design Patterns by
Gamma et al (Addison-Wesley)
Appears in a List<T>
Problem
You need the number of occurrencesof one type of object contained in a List<T>.TheList<T> contains methods, such asContains andBinarySearch, to find a single
Trang 33Determining the Number of Times an Item Appears in a List<T> | 183
item Unfortunately, these methods cannot find all duplicated items at one time—
essentially, there is no count all functionality If you want to find multiple items, you
need to implement your own routine
Solution
Use the two methods defined in Example 5-2—CountAllandBinarySearchCountAll.These methods extend theList<T>class to return the number of times a particularobject appears in a sorted and an unsortedList<T>.
Discussion
The CountAll method acceptsa search value (searchValue) of generic type T Thismethod then proceedsto count the number of timesthe search value appearsin theList<T>class This method may be used when the List<T>is sorted or unsorted Ifthe List<T> issorted (a List<T> issorted by calling the Sort method), the
Example 5-2 Determining the number of times an item appears in a List <T>
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
static class CollectionExtMethods{
// Count the number of times an item appears in this
// unsorted or sorted List<T>
public static int CountAll<T>(this List<T> myList, T searchValue)
{
return ((from t in myList where t.Equals(searchValue) select t).Count( ));
}
// Count the number of times an item appears in this sorted List<T>.
public static int BinarySearchCountAll<T>(this List<T> myList, T searchValue)
{
// Search for first item.
int center = myList.BinarySearch(searchValue);
int left = center;
while (left < 0 && myList[left-1].Equals(searchValue))
{
left -= 1;
}
int right = center;
while (right < (myList.Count – 1) && myList[right+1].Equals(searchValue))
Trang 34184 | Chapter 5: Collections
BinarySearchCountAllmethod can be used to increase the efficiency of the searching.Thisisdone by making use of theBinarySearchmethod on theList<T>class, whichismuch faster than iterating through the entireList<T> Thisisespecially true asthe List<T> grows in size.
The following code exercises these two new methods of theList<T> class:
Trang 35Retrieving All Instances of a Specific Item in a List<T> | 185
(foundCounter) isincremented by one Thiscounter isreturned by thismethod toindicate the number of items matching the search criteria in theList<T>.
matching the search criteria (searchValue) in the List<T> If one isfound, a whileloop is used to find the very first matching item in the sortedList<T>, and the posi-tion of that element isrecorded in theleftvariable A secondwhile loop isused tofind the very last matching item, and the position of this element is recorded in theright variable The value in the left variable issubtracted from the value in therightvariable, and then one isadded to thisresult in order to get the total number ofmatches
Recipe 5.5 containsa variation of thisrecipe that returnsthe actual itemsfoundrather than a count
See Also
Recipe 5.5, and the “List<T> Class” topic in the MSDN documentation
List<T>
Problem
You need to retrieve every object contained in aList<T>that matchesa search rion The List<T> containstheBinarySearch method to find a single item—essen-
crite-tially, there isno find all functionality If you want to find all itemsduplicated in a
List<T>, you must write your own routine.
Solution
Use the GetAll and BinarySearchGetAll methodsshown in Example 5-3, whichextend theList<T>class These methods return an array of all the matching objectsfound in a sorted or unsortedList<T>.
Example 5-3 Retrieving all instances of a specific item in a List<T>
// The method to retrieve all matching objects in a
// sorted or unsorted ListEx<T>
public T[] GetAll(T searchValue)
{
Trang 36186 | Chapter 5: Collections
Discussion
TheGetAll and BinarySearchGetAllmethods used in this recipe are very similar tothose used in Recipe 5.4 The main difference is that these methods return the actualitemsfound in aList<T> object instead of a count of the number of times an itemwas found The main thing to keep in mind when choosing between these methods
List<T> foundItem = new List<T>( );
for (int index = 0; index < this.Count; index++)
// The method to retrieve all matching objects in a sorted ListEx<T>
public static T[] BinarySearchGetAll<T>(this List<T> myList, T searchValue)
{
List<T> RetObjs = new List<T>( );
// Search for first item.
int center = myList.BinarySearch(searchValue);
if (center > 0)
{
RetObjs.Add(myList[center]);
int left = center;
while (left > 0 && myList[left - 1].Equals(searchValue))
{
left -= 1;
RetObjs.Add(myList[left]);
}
int right = center;
while (right < (myList.Count - 1) &&
Trang 37Retrieving All Instances of a Specific Item in a List<T> | 187
iswhether you are going to be searching aList<T>that is sorted or unsorted Choosethe GetAllmethod to obtain an array of all found itemsfrom an unsorted List<T>and choose theBinarySearchGetAll method to get all items in a sorted List<T>.The following code exercises these two new extension methods of theList<T> class: class Test
IEnumerable<int> objects = arrayExt.GetAll(2);
foreach (object o in objects)
int[] objs = arrayExt.BinarySearchGetAll(-2);
foreach (object o in objs)
Trang 38See Also
Recipe 5.4, and the “List<T> Class” topic in the MSDN documentation
Problem
You need the ability to insert and remove items from a standardSystem.Arraytype.When an item is inserted, it should not overwrite the item where it is being inserted;instead, it should be inserted between the element at that index and the previousindex When an item is removed, the void left by the element should be closed byshifting the other elements in the array However, the Array type hasno usablemethod to perform these operations
Solution
If possible, switch to aList<T>instead If this is not possible (for example, if you’renot in control of the code that createstheArrayorArrayListin the first place), usethe approach shown in the following class Two methods insert and remove itemsfrom the array TheInsertIntoArray method that extendstheArray type will insert
an item into the array without overwriting any data that already exists in the array
Trang 39Inserting and Removing Items from an Array | 189
from the array:
using System;
public static class ArrayUtilities
{
public static void InsertIntoArray(this Array target,
object value, int index)
{
if (index < target.GetLowerBound(0) ||
index > target.GetUpperBound(0))
{
throw (new ArgumentOutOfRangeException("index", index,
"Array index out of bounds."));
throw (new ArgumentOutOfRangeException("index", index,
"Array index out of bounds."));
static Both methods make use of theArray.Copystatic method to perform their ations Initially, both methods test to see whether an item is being added or removedwithin the boundsof the array target If the item passes this test, the Array.Copymethod is used to shift items around to either make room for an element to beinserted or to overwrite an element being removed from the array
Trang 40oper-190 | Chapter 5: Collections
TheRemoveFromArraymethod accepts two parameters The first array,target, isthearray from which an element isto be removed; the second parameter,index, isthezero-based position of the element to be removed in the array Elements at and abovethe inserted element are shifted down by one The last element in the array is set tothe default value for the array type
TheInsertIntoArraymethod accepts three parameters The first parameter,target,isthe array that isto have an element added;valueisthe element to be added; and
indexis the zero-based position at whichvalueisto be added Elementsat and abovethe inserted element are shifted up by one The last element in the array is discarded.The following code illustrates the use of the InsertIntoArray and RemoveFromArraymethods: