Programming C# 2nd Edition phần 4 doc

59 310 0
Programming C# 2nd Edition phần 4 doc

Đ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

Programming C#, 2nd Edition 172 Output: Value: Who Value: is Value: John Value: Galt Value: Galt Value: John Value: is Value: Who Value: We Value: Hold Value: These Value: Truths Value: To Value: Be Value: Self Value: Evident Value: Be Value: Evident Value: Hold Value: Self Value: These Value: To Value: Truths Value: We The example begins by creating myArray, an array of strings with the words: "Who", "is", "John", "Galt" This array is printed, and then passed to the Array.Reverse( ) method, where it is printed again to see that the array itself has been reversed: Value: Galt Value: John Value: is Value: Who Similarly, the example creates a second array, myOtherArray, containing the words: "We", "Hold", "These", "Truths", "To", "Be", "Self", "Evident", This is passed to the Array.Sort( ) method. Then Array.Sort( ) happily sorts them alphabetically: Value: Be Value: Evident Value: Hold Value: Self Value: These Value: To Value: Truths Value: We Programming C#, 2nd Edition 173 9.3 Indexers There are times when it is desirable to access a collection within a class as though the class itself were an array. For example, suppose you create a list box control named myListBox that contains a list of strings stored in a one-dimensional array, a private member variable named myStrings. A list box control contains member properties and methods in addition to its array of strings. However, it would be convenient to be able to access the list box array with an index, just as if the list box were an array. For example, such a property would permit statements like the following: string theFirstString = myListBox[0]; string theLastString = myListBox[Length-1]; An indexer is a C# construct that allows you to access collections contained by a class using the familiar [] syntax of arrays. An indexer is a special kind of property and includes get( ) and set( ) methods to specify its behavior. You declare an indexer property within a class using the following syntax: type this [type argument]{get; set;} The return type determines the type of object that will be returned by the indexer, while the type argument specifies what kind of argument will be used to index into the collection that contains the target objects. Although it is common to use integers as index values, you can index a collection on other types as well, including strings. You can even provide an indexer with multiple parameters to create a multidimensional array! The this keyword is a reference to the object in which the indexer appears. As with a normal property, you also must define get( ) and set( ) methods, which determine how the requested object is retrieved from or assigned to its collection. Example 9-9 declares a list box control ( ListBoxTest), which contains a simple array ( myStrings) and a simple indexer for accessing its contents. C++ programmers take note: the indexer serves much the same purpose as overloading the C++ index operator ([]). The index operator cannot be overloaded in C#, which provides the indexer in its place. Example 9-9. Using a simple indexer namespace Programming_CSharp { using System; // a simplified ListBox control public class ListBoxTest { // initialize the list box with strings public ListBoxTest(params string[] initialStrings) { // allocate space for the strings strings = new String[256]; Programming C#, 2nd Edition 174 // 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 list box public void Add(string theString) { if (ctr >= strings.Length) { // handle bad index } else strings[ctr++] = theString; } // allow array-like access public string this[int index] { get { if (index < 0 || index >= strings.Length) { // handle bad index } return strings[index]; } set { // add only through the add method if (index >= ctr ) { // handle error } else strings[index] = value; } } // publish how many strings you hold public int GetNumEntries( ) { return ctr; } private string[] strings; private int ctr = 0; } public class Tester { static void Main( ) { // create a new list box and initialize ListBoxTest lbt = new ListBoxTest("Hello", "World"); Programming C#, 2nd Edition 175 // add a few strings lbt.Add("Who"); lbt.Add("Is"); lbt.Add("John"); lbt.Add("Galt"); // test the access string subst = "Universe"; lbt[1] = subst; // access all the strings for (int i = 0;i<lbt.GetNumEntries( );i++) { Console.WriteLine("lbt[{0}]: {1}",i,lbt[i]); } } } } Output: lbt[0]: Hello lbt[1]: Universe lbt[2]: Who lbt[3]: Is lbt[4]: John lbt[5]: Galt To keep Example 9-9 simple, strip the list box control down to the few features we care about. The listing ignores everything having to do with being a user control and focuses only on the list of strings the list box maintains and methods for manipulating them. In a real application, of course, these are a small fraction of the total methods of a list box, whose principal job is to display the strings and enable user choice. The first thing to notice is the two private members: private string[] strings; private int ctr = 0; In this program, the list box maintains a simple array of strings: strings. Again, in a real list box you might use a more complex and dynamic container, such as a hash table (described later in this chapter). The member variable ctr will keep track of how many strings have been added to this array. Initialize the array in the constructor with the statement: strings = new String[256]; The remainder of the constructor adds the parameters to the array. Again, for simplicity, simply add new strings to the array in the order received. Because you cannot know how many strings will be added, use the keyword params, as described earlier in this chapter. Programming C#, 2nd Edition 176 The Add( ) method of ListBoxTest does nothing more than append a new string to the internal array. The key method of ListBoxTest, however, is the indexer. An indexer is unnamed, so use the this keyword: public string this[int index] The syntax of the indexer is very similar to that for properties. There is either a get( ) method, a set( ) method, or both. In the case shown, the get( ) method endeavors to implement rudimentary bounds checking, and assuming the index requested is acceptable, it returns the value requested: get { if (index < 0 || index >= strings.Length) { // handle bad index } return strings[index]; } The set( ) method checks to make sure that the index you are setting already has a value in the list box. If not, it treats the set as an error (new elements can only be added using Add with this approach). The set accessor takes advantage of the implicit parameter value that represents whatever is assigned using the index operator: set { if (index >= ctr ) { // handle error } else strings[index] = value; } Thus, if you write: lbt[5] = "Hello World" the compiler will call the indexer set( ) method on your object and pass in the string Hello World as an implicit parameter named value. 9.3.1 Indexers and Assignment In Example 9-9, you cannot assign to an index that does not have a value. Thus, if you write: lbt[10] = "wow!"; you would trigger the error handler in the set( ) method, which would note that the index you've passed in (10) is larger than the counter (6). Programming C#, 2nd Edition 177 Of course, you can use the set( ) method for assignment; you simply have to handle the indexes you receive. To do so, you might change the set( ) method to check the Length of the buffer rather than the current value of counter. If a value was entered for an index that did not yet have a value, you would update ctr: set { // add only through the add method if (index >= strings.Length ) { // handle error } else { strings[index] = value; if (ctr < index+1) ctr = index+1; } } This allows you to create a "sparse" array in which you can assign to offset 10 without ever having assigned to offset 9. Thus, if you now write: lbt[10] = "wow!"; the output would be: lbt[0]: Hello lbt[1]: Universe lbt[2]: Who lbt[3]: Is lbt[4]: John lbt[5]: Galt 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("John"); lbt.Add("Galt"); Before examining the values, modify the second value (at index 1): string subst = "Universe"; lbt[1] = subst; Programming C#, 2nd Edition 178 Finally, display each value in a loop: for (int i = 0;i<lbt.GetNumEntries( );i++) { Console.WriteLine("lbt[{0}]: {1}",i,lbt[i]); } 9.3.2 Indexing on Other Values C# does not 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 our list box, we might want to be able to index into the list box 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 provided. Notice that the overloaded indexer and the indexer from Example 9-9 are able to coexist. Example 9-10. Overloading an index namespace Programming_CSharp { using System; // a simplified ListBox control public class ListBoxTest { // initialize the list box 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 list box public void Add(string theString) { strings[ctr] = theString; ctr++; } Programming C#, 2nd Edition 179 // 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; } } 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; } private string[] strings; private int ctr = 0; } Programming C#, 2nd Edition 180 public class Tester { static void Main( ) { // create a new list box and initialize ListBoxTest lbt = new ListBoxTest("Hello", "World"); // add a few strings lbt.Add("Who"); lbt.Add("Is"); lbt.Add("John"); lbt.Add("Galt"); // 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 } // end namespace Output: lbt[0]: GoodBye lbt[1]: Universe lbt[2]: Who lbt[3]: Is lbt[4]: John lbt[5]: Galt 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 we 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 was done with an integer: lbt["Hel"] = "GoodBye"; 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) to findString. 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: Programming C#, 2nd Edition 181 myStrings[findString(index)] = value; The careful reader will note that if the string does not 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 message or otherwise allowing the user to recover from the error. 9.4 Collection Interfaces The .NET Framework provides standard interfaces for enumerating, comparing, and creating collections. The key collection interfaces are listed in Table 9-2. Table 9-2. Collection interfaces Interface Purpose IEnumerable Enumerates through a collection using a foreach statement. ICollection Implemented by all collections to provide the CopyTo( ) method as well as the Count, IsSynchronized, and SyncRoot properties. IComparer Compares two objects held in a collection so that the collection can be sorted. IList Used by array-indexable collections. IDictionary Used for key/value-based collections such as Hashtable and SortedList. IDictionaryEnumerator Allows enumeration with foreach of a collection that supports IDictionary. 9.4.1 The IEnumerable Interface You can support the foreach statement in ListBoxTest by implementing the IEnumerable interface. IEnumerable has only one method, GetEnumerator( ), whose job is to return a specialized implementation of IEnumerator. Thus, the semantics of an Enumerable class are that it can provide an Enumerator: public IEnumerator GetEnumerator( ) { return (IEnumerator) new ListBoxEnumerator(this); } The Enumerator must implement the IEnumerator methods and properties. These can be implemented either directly by the container class (in this case, ListBoxTest) or by a separate class. The latter approach is generally preferred because it encapsulates this responsibility in the Enumerator class rather than cluttering up the container. [...]... 9-18 Keys and Values collections namespace Programming_ CSharp { using System; using System.Collections; public class Tester { static void Main( ) { // Create and initialize a new Hashtable Hashtable hashTable = new Hashtable( ); 205 Programming C#, 2nd Edition hashTable.Add("00 044 0312", hashTable.Add("000123933", hashTable.Add("000 145 938", hashTable.Add("0007733 94" , "George Washington"); "Abraham Lincoln");... 206 Programming C#, 2nd Edition Example 9-19 Using the IDictionaryEnumerator interface namespace Programming_ CSharp { using System; using System.Collections; public class Tester { static void Main( ) { // Create and initialize a new Hashtable Hashtable hashTable = new Hashtable( ); hashTable.Add("00 044 0312", "George Washington"); hashTable.Add("000123933", "Abraham Lincoln"); hashTable.Add("000 145 938",... Programming C#, 2nd Edition Output: ID: ID: ID: ID: ID: 103 108 107 108 102 Years Years Years Years Years of of of of of Svc: Svc: Svc: Svc: Svc: 11 15 14 5 0 ID: ID: ID: ID: ID: 102 103 107 108 108 Years Years Years Years Years of of of of of Svc: Svc: Svc: Svc: Svc: 0 11 14 15 5 ID: ID: ID: ID: ID: 102 108 103 107 108 Years Years Years Years Years of of of of of Svc: Svc: Svc: Svc: Svc: 0 5 11 14. .. specified key In C#, the declaration for the Item property is: object this[object key] {get; set;} The Item property is implemented in C# with the index operator ([] ) Thus, you access items in any Dictionary object using the offset syntax, as you would with an array Example 9-17 demonstrates adding items to a Hashtable and then retrieving them with the Item property 2 04 Programming C#, 2nd Edition Example... Console.Write( "{0} ",enumerator.Current ); Console.WriteLine( ); } 200 Programming C#, 2nd Edition Output: intStack values: 35 30 25 20 15 10 (Pop) 35 intStack values: 30 25 20 15 10 5 (Pop) 30 intStack values: 25 20 15 10 5 0 (Peek) 25 intStack values: 25 20 15 10 5 0 Target array: 100 200 300 40 0 500 600 Target array after copy: 100 200 300 40 0 500 600 The new array: 25 20 15 10 5 700 25 800 20 900 15 10... empArray[i].ToString( )); } Console.WriteLine("\n"); } } } Output: 8 5 105 3 3 102 7 3 103 5 7 103 3 102 1 04 106 8 1 04 105 106 The output shows that the integer array and Employee array were generated with random numbers When sorted, the display shows the values have been ordered properly 191 Programming C#, 2nd Edition 9.5.2 Implementing IComparer When you call Sort( ) on the ArrayList the default implementation... (Employee) rhs; return l.CompareTo(r,WhichComparison); } public Employee.EmployeeComparer.ComparisonType WhichComparison { get { return whichComparison; } set { whichComparison=value; } } 1 94 Programming C#, 2nd Edition // private state variable private Employee.EmployeeComparer.ComparisonType whichComparison; } private int empID; private int yearsOfSvc = 1; } public class Tester { static void Main(... Main( ) { // create a new list box and initialize ListBoxTest lbt = new ListBoxTest("Hello", "World"); // add a few strings lbt.Add("Who"); lbt.Add("Is"); lbt.Add("John"); lbt.Add("Galt"); 1 84 Programming C#, 2nd Edition // 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: Value:... an enumerator for the Queue 196 Programming C#, 2nd Edition Peek() ToArray() Returns the object at the beginning of the Queue without removing it Copies the elements to a new array Add elements to your queue with the Enqueue command and take them off the queue with Dequeue or by using an enumerator Example 9-15 illustrates Example 9-15 Working with a queue namespace Programming_ CSharp { using System;... often is Count, which returns the number of elements in the collection: For (int i = 0;i . initialStrings) { // allocate space for the strings strings = new String[256]; Programming C#, 2nd Edition 1 74 // copy the strings passed in to the constructor foreach (string s in initialStrings). GetEnumerator( ) { return (IEnumerator) new ListBoxEnumerator(this); } Programming C#, 2nd Edition 1 84 // initialize the list box with strings public ListBoxTest(params string[]. { // } Programming C#, 2nd Edition 186 Here you are using the Count property of myIntArray to determine how many objects are in it so that you can print their values. 9 .4. 3 The IComparer

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

Tài liệu cùng người dùng

Tài liệu liên quan