Xem mẫu

320 Part III Creating Components Note You can declare an indexer that contains only a get accessor (a read-only indexer) or only a set accessor (a write-only accessor) . Comparing Indexers and Arrays When you use an indexer, the syntax is deliberately very array-like . However, there are some important differences between indexers and arrays: Indexers can use non-numeric subscripts, such as a string as shown in the following example . Arrays can use only integer subscripts: public int this [ string name ] { ... } // OK Tip Many collection classes, such as Hashtable, that implement an associative lookup based on key/value pairs implement indexers to provide a convenient alternative to using the Add method to add a new value and as an alternative to iterating through the Values property to locate a value in your code . For example, instead of this: Hashtable ages = new Hashtable(); ages.Add("John", 42); you can use this: Hashtable ages = new Hashtable(); ages["John"] = 42; Indexers can be overloaded (just like methods), whereas arrays cannot: public Name this [ PhoneNumber number ] { ... } public PhoneNumber this [ Name name ] { ... } Indexers cannot be used as ref or out parameters, whereas array elements can: IntBits bits; // bits contains an indexer Method(ref bits[1]); // compile-time error Properties, .Arrays, .and .Indexers It is possible for a property to return an array, but remember that arrays are reference types, so exposing an array as a property makes it possible to accidentally overwrite a lot of data . Look at the following structure that exposes an array property named Data: struct Wrapper { private int[] data; ... public int[] Data { Chapter 16 Using Indexers 321 get { return this.data; } set { this.data = value; } } } Now consider the following code that uses this property: Wrapper wrap = new Wrapper(); ... int[] myData = wrap.Data; myData[0]++; myData[1]++; This looks pretty innocuous . However, because arrays are reference types, the variable myData refers to the same object as the private data variable in the Wrapper structure . Any changes you make to elements in myData are made to the data array; the expres-sion myData[0]++ has exactly the same effect as data[0]++ . If this is not the intention, you should use the Clone method in the get and set accessors of the Data property to return a copy of the data array, or make a copy of the value being set, as shown here . (The Clone method returns an object, which you must cast to an integer array .) struct Wrapper { private int[] data; ... public int[] Data { get { return this.data.Clone() as int[]; } set { this.data = value.Clone() as int[]; } } } However, this approach can become very messy and expensive in terms of memory use . Indexers provide a natural solution to this problem—don’t expose the entire array as a property; just make its individual elements available through an indexer: struct Wrapper { private int[] data; ... public int this [int i] { get { return this.data[i]; } set { this.data[i] = value; } } } The following code uses the indexer in a similar manner to the property shown earlier: Wrapper wrap = new Wrapper(); ... int[] myData = new int[2]; myData[0] = wrap[0]; 322 Part III Creating Components myData[1] = wrap[1]; myData[0]++; myData[1]++; This time, incrementing the values in the MyData array has no effect on the original array in the Wrapper object . If you really want to modify the data in the Wrapper object, you must write statements such as this: wrap[0]++; This is much clearer, and safer! Indexers .in .Interfaces You can declare indexers in an interface . To do this, specify the get keyword, the set keyword, or both, but replace the body of the get or set accessor with a semicolon . Any class or struc-ture that implements the interface must implement the indexer accessors declared in the interface . For example: interface IRawInt { bool this [ int index ] { get; set; } } struct RawInt : IRawInt { ... public bool this [ int index ] { get { ... } set { ... } } ... } If you implement the interface indexer in a class, you can declare the indexer implementa-tions as virtual . This allows further derived classes to override the get and set accessors . For example: class RawInt : IRawInt { ... public virtual bool this [ int index ] { get { ... } set { ... } } ... } Chapter 16 Using Indexers 323 You can also choose to implement an indexer by using the explicit interface implementation syntax covered in Chapter 12, “Working with Inheritance .” An explicit implementation of an indexer is nonpublic and nonvirtual (and so cannot be overridden) . For example: struct RawInt : IRawInt { ... bool IRawInt.this [ int index ] { get { ... } set { ... } } ... } Using .Indexers .in .a .Windows .Application In the following exercise, you will examine a simple phone book application and complete its implementation . You will write two indexers in the PhoneBook class: one that accepts a Name parameter and returns a PhoneNumber and another that accepts a PhoneNumber parameter and returns a Name . (The Name and PhoneNumber structures have already been written .) You will also need to call these indexers from the correct places in the program . Familiarize yourself with the application . 1 . . Start Microsoft Visual Studio 2010 if it is not already running . . 2 . . Open the Indexers project, located in the \Microsoft Press\Visual CSharp Step By Step\ Chapter 16\Indexers folder in your Documents folder . This is a Windows Presentation Foundation (WPF) application that enables a user to search for the telephone number for a contact and also find the name of a contact that matches a given telephone number . . 3 . . On the Debug menu, click Start Without Debugging . The project builds and runs . A form appears, displaying two empty text boxes labeled Name and Phone Number . The form also contains three buttons—one to add a name/ phone number pair to a list of names and phone numbers held by the application, one to find a phone number when given a name, and one to find a name when given a phone number . These buttons currently do nothing . Your task is to complete the application so that these buttons work . . 4 . . Close the form, and return to Visual Studio 2010 . . 5 . . Display the Name .cs file in the Code and Text Editor window . Examine the Name structure . Its purpose is to act as a holder for names . 324 Part III Creating Components The name is provided as a string to the constructor . The name can be retrieved by using the read-only string property named Text . (The Equals and GetHashCode methods are used for comparing Names when searching through an array of Name values—you can ignore them for now .) . 6 . . Display the PhoneNumber .cs file in the Code and Text Editor window, and examine the PhoneNumber structure . It is similar to the Name structure . . 7 . . Display the PhoneBook .cs file in the Code and Text Editor window, and examine the PhoneBook class . This class contains two private arrays: an array of Name values named names, and an array of PhoneNumber values named phoneNumbers . The PhoneBook class also con-tains an Add method that adds a phone number and name to the phone book . This method is called when the user clicks the Add button on the form . The enlargeIfFull method is called by Add to check whether the arrays are full when the user adds another entry . This method creates two new bigger arrays, copies the contents of the existing arrays to them, and then discards the old arrays . Write the indexers . 1 . . In the PhoneBook .cs file, add a public read-only indexer to the PhoneBook class, as shown in bold in the following code . The indexer should return a Name and take a PhoneNumber item as its index . Leave the body of the get accessor blank . The indexer should look like this: sealed class PhoneBook { ... public Name this [PhoneNumber number] { get { } } ... } . 2 . . Implement the get accessor as shown in bold in the following code . The purpose of the accessor is to find the name that matches the specified phone number . To do this, youneed to call the static IndexOf method of the Array class . The IndexOf method performs a search through an array, returning the index of the first item in the array that matches the specified value . The first argument to IndexOf is the array to search through (phoneNumbers) . The second argument to IndexOf is the item you are search-ing for .IndexOf returns the integer index of the element if it finds it; otherwise, IndexOf returns –1 . If the indexer finds the phone number, it should return it; otherwise, it should return an empty Name value . (Note that Name is a structure and so the default constructor sets its private name field to null .) ... - tailieumienphi.vn
nguon tai.lieu . vn