Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
170,69 KB
Nội dung
182 | Chapter 9: Arrays, Indexers, and Collections strings[index] = value; if (ctr < index+1) ctr = index+1; } } This code is kept simple and thus is not robust. There are any number of other checks you’ll want to make on the value passed in (e.g., checking that you were not passed a negative index, and that it doesn’t exceed the size of the underlying strings[] array). This allows you to create a “sparse” array in which you can assign to offset 10 with- out ever having assigned to offset 9. Thus, if you now write: lbt[10] = "wow!"; the output will be: lbt[0]: Hello lbt[1]: Universe lbt[2]: Who lbt[3]: Is lbt[4]: Douglas lbt[5]: Adams lbt[6]: lbt[7]: lbt[8]: lbt[9]: lbt[10]: wow! In Main( ), you create an instance of the ListBoxTest class named lbt and pass in two strings as parameters: ListBoxTest lbt = new ListBoxTest("Hello", "World"); Then, call Add( ) to add four more strings: // add a few strings lbt.Add("Who"); lbt.Add("Is"); lbt.Add("Douglas"); lbt.Add("Adams"); Before examining the values, modify the second value (at index 1): string subst = "Universe"; lbt[1] = subst; Finally, display each value in a loop: for (int i = 0;i<lbt.GetNumEntries( );i++) { Console.WriteLine("lbt[{0}]: {1}",i,lbt[i]); } Indexers | 183 Indexing on Other Values C# doesn’t require that you always use an integer value as the index to a collection. When you create a custom collection class and create your indexer, you are free to create indexers that index on strings and other types. In fact, the index value can be overloaded so that a given collection can be indexed, for example, by an integer value or by a string value, depending on the needs of the client. In the case of your listbox, you might want to be able to index into the listbox based on a string. Example 9-10 illustrates a string index. The indexer calls findString( ), which is a helper method that returns a record based on the value of the string pro- vided. Notice that the overloaded indexer and the indexer from Example 9-9 are able to coexist. Example 9-10. Overloading an index using System; using System.Collections.Generic; using System.Text; namespace OverloadedIndexer { // a simplified ListBox control public class ListBoxTest { private string[] strings; private int ctr = 0; // initialize the listbox with strings public ListBoxTest(params string[] initialStrings) { // allocate space for the strings strings = new String[256]; // copy the strings passed in to the constructor foreach (string s in initialStrings) { strings[ctr++] = s; } } // add a single string to the end of the listbox public void Add(string theString) { strings[ctr] = theString; ctr++; } // allow array-like access public string this[int index] { 184 | Chapter 9: Arrays, Indexers, and Collections get { if (index < 0 || index >= strings.Length) { // handle bad index } return strings[index]; } set { strings[index] = value; } } private int findString(string searchString) { for (int i = 0; i < strings.Length; i++) { if (strings[i].StartsWith(searchString)) { return i; } } return -1; } // index on string public string this[string index] { get { if (index.Length == 0) { // handle bad index } return this[findString(index)]; } set { strings[findString(index)] = value; } } // publish how many strings you hold public int GetNumEntries( ) { return ctr; } } Example 9-10. Overloading an index (continued) Indexers | 185 Example 9-10 is identical to Example 9-9 except for the addition of an overloaded indexer, which can match a string, and the method findString, created to support that index. The findString method simply iterates through the strings held in myStrings until it finds a string that starts with the target string you use in the index. If found, it returns the index of that string; otherwise, it returns the value -1. We see in Main( ) that the user passes in a string segment to the index, just as with an integer: lbt["Hel"] = "GoodBye"; public class Tester { static void Main( ) { // create a new listbox and initialize ListBoxTest lbt = new ListBoxTest("Hello", "World"); // add a few strings lbt.Add("Who"); lbt.Add("Is"); lbt.Add("Douglas"); lbt.Add("Adams"); // test the access string subst = "Universe"; lbt[1] = subst; lbt["Hel"] = "GoodBye"; // lbt["xyz"] = "oops"; // access all the strings for (int i = 0; i < lbt.GetNumEntries( ); i++) { Console.WriteLine("lbt[{0}]: {1}", i, lbt[i]); } // end for } // end main } // end tester } Output: lbt[0]: GoodBye lbt[1]: Universe lbt[2]: Who lbt[3]: Is lbt[4]: Douglas lbt[5]: Adams Example 9-10. Overloading an index (continued) 186 | Chapter 9: Arrays, Indexers, and Collections This calls the overloaded index, which does some rudimentary error-checking (in this case, making sure the string passed in has at least one letter), and then passes the value ( Hel)tofindString. It gets back an index and uses that index to index into myStrings: return this[findString(index)]; The set value works in the same way: myStrings[findString(index)] = value; The careful reader will note that if the string doesn’t match, a value of -1 is returned, which is then used as an index into myStrings. This action then generates an exception ( System.NullReferenceException), as you can see by uncommenting the following line in Main( ): lbt["xyz"] = "oops"; The proper handling of not finding a string is, as they say, left as an exercise for the reader. You might consider displaying an error mes- sage or otherwise allowing the user to recover from the error. Collection Interfaces The .NET Framework provides two sets of standard interfaces for enumerating and comparing collections: the traditional (nontype-safe) and the new generic type-safe collections. This book focuses only on the new type-safe collection interfaces, as these are far preferable. You can declare an ICollection of any specific type by substituting the actual type (e.g., int or string) for the generic type in the interface declaration (<T>). C++ programmers take note: C# Generics are similar in syntax and usage to C++ templates. However, because the generic types are expanded to their specific type at runtime, the JIT compiler is able to share code among different instances, dramatically reducing the code bloat that you may see when using templates in C++. Table 9-2 lists the key generic collection interfaces. * * For backward compatibility, C# also provides nongeneric interfaces (e.g., ICollection, IEnumerator), but they aren’t considered here because they are obsolete. Table 9-2. Collection interfaces Interface Purpose ICollection<T> Base interface for generic collections IEnumerator<T> IEnumerable<T> Enumerate through a collection using a foreach statement Collection Interfaces | 187 The IEnumerable<T> Interface You can support the foreach statement in ListBoxTest by implementing the IEnumerable<T> interface (see Example 9-11). IEnumerable<T> has only one method, GetEnumerator( ), whose job is to return an implementation of IEnumerator<T>. The C# language provides special help in creating the enumerator, using the new key- word yield. ICollection<T> Implemented by all collections to provide the CopyTo( ) method as well as the Count, IsSynchronized, and SyncRoot properties IComparer<T> IComparable<T> Compare two objects held in a collection so that the collection can be sorted IList<T> Used by array-indexable collections IDictionary<K,V> Used for key-/value-based collections such as Dictionary Example 9-11. Making a ListBox an enumerable class using System; using System.Collections; using System.Collections.Generic; using System.Text; namespace Enumerable { public class ListBoxTest : IEnumerable<string> { private string[] strings; private int ctr = 0; // Enumerable classes can return an enumerator public IEnumerator<string> GetEnumerator( ) { foreach (string s in strings) { yield return s; } } // Explicit interface implementation. IEnumerator IEnumerable.GetEnumerator( ) { return GetEnumerator( ); } // initialize the listbox with strings public ListBoxTest(params string[] initialStrings) { // allocate space for the strings strings = new String[8]; Table 9-2. Collection interfaces (continued) Interface Purpose 188 | Chapter 9: Arrays, Indexers, and Collections // copy the strings passed in to the constructor foreach (string s in initialStrings) { strings[ctr++] = s; } } // add a single string to the end of the listbox public void Add(string theString) { strings[ctr] = theString; ctr++; } // allow array-like access public string this[int index] { get { if (index < 0 || index >= strings.Length) { // handle bad index } return strings[index]; } set { strings[index] = value; } } // publish how many strings you hold public int GetNumEntries( ) { return ctr; } } public class Tester { static void Main( ) { // create a new listbox and initialize ListBoxTest lbt = new ListBoxTest("Hello", "World"); // add a few strings lbt.Add("Who"); lbt.Add("Is"); Example 9-11. Making a ListBox an enumerable class (continued) Collection Interfaces | 189 The program begins in Main( ), creating a new ListBoxTest object and passing two strings to the constructor. When the object is created, an array of Strings is created with enough room for eight strings. Four more strings are added using the Add method, and the second string is updated, just as in the previous example. The big change in this version of the program is that a foreach loop is called, retriev- ing each string in the listbox. The foreach loop automatically uses the IEnumerable<T> interface, invoking GetEnumerator( ). The GetEnumerator method is declared to return an IEnumerator of string: public IEnumerator<string> GetEnumerator( ) The implementation iterates through the array of strings, yielding each in turn: foreach ( string s in strings ) { yield return s; } All the bookkeeping for keeping track of which element is next, resetting the itera- tor, and so forth is provided for you by the Framework. lbt.Add("Douglas"); lbt.Add("Adams"); // test the access string subst = "Universe"; lbt[1] = subst; // access all the strings foreach (string s in lbt) { Console.WriteLine("Value: {0}", s); } } } } Output: Value: Hello Value: Universe Value: Who Value: Is Value: Douglas Value: Adams Value: Value: Example 9-11. Making a ListBox an enumerable class (continued) 190 | Chapter 9: Arrays, Indexers, and Collections Constraints There are times when you must ensure that the elements you add to a generic list meet certain constraints (e.g., they derive from a given base class, or they implement a specific interface). In the next example, you implement a simplified, singly linked, sortable list. The list consists of Nodes, and each Node must be guaranteed that the types added to it implement IComparer. You do so with the following statement: public class Node<T> : IComparable<Node<T>> where T : IComparable<T> This defines a generic Node that holds a type, T. Node of T implements the IComparable<T> interface, which means that two NodesofT can be compared. The Node class is constrained (where T : IComparable<T>) to hold only types that implement the IComparable interface. Thus, you may substitute any type for T as long as that type implements IComparable. Example 9-12 illustrates the complete implementation, with analysis to follow. Example 9-12. Using constraints using System; using System.Collections.Generic; namespace UsingConstraints { public class Employee : IComparable<Employee> { private string name; public Employee(string name) { this.name = name; } public override string ToString( ) { return this.name; } // implement the interface public int CompareTo(Employee rhs) { return this.name.CompareTo(rhs.name); } public bool Equals(Employee rhs) { return this.name == rhs.name; } } // node must implement IComparable of Node of T. // constrain Nodes to only take items that implement IComparable // by using the where keyword. Constraints | 191 public class Node<T> : IComparable<Node<T>> where T : IComparable<T> { // member fields private T data; private Node<T> next = null; private Node<T> prev = null; // constructor public Node(T data) { this.data = data; } // properties public T Data { get { return this.data; } } public Node<T> Next { get { return this.next; } } public int CompareTo(Node<T> rhs) { // this works because of the constraint return data.CompareTo(rhs.data); } public bool Equals(Node<T> rhs) { return this.data.Equals(rhs.data); } // methods public Node<T> Add(Node<T> newNode) { if (this.CompareTo(newNode) > 0) // goes before me { newNode.next = this; // new node points to me // if I have a previous, set it to point to // the new node as its next if (this.prev != null) { this.prev.next = newNode; newNode.prev = this.prev; } // set prev in current node to point to new node this.prev = newNode; Example 9-12. Using constraints (continued) [...]... void Main( ) { List empList = new List( ); List intList = new List( ); // populate the List for (int i = 0; i < 5; i++) { empList.Add(new Employee(i + 100)); intList.Add(i * 5); } // print all the contents for (int i = 0; i < intList.Count; i++) { Console.Write("{0} ", intList[i].ToString( )); } Console.WriteLine("\n"); // print all the contents of the Employee List for (int... a queue (continued) namespace Queue { public class Tester { static void Main( ) { Queue intQueue = new Queue( ); // populate the array for (int i = 0; i < 5; i++) { intQueue.Enqueue(i * 5); } // Display the Queue Console.Write("intQueue values:\t"); PrintValues(intQueue); // Remove an element from the queue Console.WriteLine( "\n(Dequeue)\t{0}", intQueue.Dequeue( )); // Display the Queue... System.Collections.Generic; using System.Text; namespace Stack { public class Tester { static void Main( ) { Stack intStack = new Stack( ); // populate the array for (int i = 0; i < 8; i++) { intStack.Push(i * 5); } // Display the Stack Console.Write("intStack values:\t"); PrintValues(intStack); // Remove an element from the stack Console.WriteLine("\n(Pop)\t{0}", intStack.Pop( )); // Display the Stack Console.Write("intStack . the List for (int i = 0; i < 5; i++) { empList.Add(new Employee(i + 100)); intList.Add(i * 5); } // print all the contents for (int i = 0; i < intList.Count; i++) { Console.Write("{0}