Vous êtes sur la page 1sur 21

Using Indexers (C# Programming Guide)

Visual Studio 2010 Other Versions

3 out of 4 rated this helpful Rate this topic

Indexers are a syntactic convenience that enable you to create a class, struct, or interface that client applications can access just as an array. Indexers are most frequently implemented in types whose primary purpose is to encapsulate an internal collection or array. For example, suppose you have a class named TempRecord that represents the temperature in Farenheit as recorded at 10 different times during a 24 hour period. The class contains an array named "temps" of type float to represent the temperatures, and a DateTime that represents the date the temperatures were recorded. By implementing an indexer in this class, clients can access the temperatures in a TempRecord instance asfloat temp = tr[4] instead of as float temp = tr.temps[4]. The indexer notation not only simplifies the syntax for client applications; it also makes the class and its purpose more intuitive for other developers to understand. To declare an indexer on a class or struct, use the this keyword, as in this example: public int this[int index] { // get and set accessors } // Indexer declaration

Remarks
The type of an indexer and the type of its parameters must be at least as accessible as the indexer itself. For more information about accessibility levels, see Access Modifiers. For more information about how to use indexers with an interface, see Interface Indexers. The signature of an indexer consists of the number and types of its formal parameters. It does not include the indexer type or the names of the formal parameters. If you declare more than one indexer in the same class, they must have different signatures. An indexer value is not classified as a variable; therefore, you cannot pass an indexer value as a ref or out parameter. To provide the indexer with a name that other languages can use, use a name attribute in the declaration. For example:

[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this [int index] // Indexer declaration { } This indexer will have the name TheItem. Not providing the name attribute would make Item the default name.

Example 1
Description
The following example shows how to declare a private array field, temps, and an indexer. The indexer enables direct access to the instance tempRecord[i]. The alternative to using the indexer is to declare the array as a publicmember and access its members, tempRecord.temps[i], directly. Notice that when an indexer's access is evaluated, for example, in a Console.Write statement, the get accessor is invoked. Therefore, if no get accessor exists, a compile-time error occurs.

Code
C# class TempRecord { // Array of temperature values private float[] temps = new float[10] { 56.2F, 56.7F, 56.5F, 56.9F, 58.8F, 61.3F, 65.9F, 62.1F, 59.2F, 57.5F }; // To enable client code to validate input // when accessing your indexer. public int Length { get { return temps.Length; } } // Indexer declaration. // If index is out of range, the temps array will throw the exception. public float this[int index] { get { return temps[index]; } set { temps[index] = value; } } }

class MainClass { static void Main() { TempRecord tempRecord = new TempRecord(); // Use the indexer's set accessor tempRecord[3] = 58.3F; tempRecord[5] = 60.1F; // Use the indexer's get accessor for (int i = 0; i < 10; i++) { System.Console.WriteLine("Element #{0} = {1}", i, tempRecord[i]); } // Keep the console window open in debug mode. System.Console.WriteLine("Press any key to exit."); System.Console.ReadKey(); } } /* Output: Element Element Element Element Element Element Element Element Element Element */

#0 #1 #2 #3 #4 #5 #6 #7 #8 #9

= = = = = = = = = =

56.2 56.7 56.5 58.3 58.8 60.1 65.9 62.1 59.2 57.5

You want to see an example program that uses the indexer property in the C# language to provide a clear way to access the internal elements of the class. Properties such as indexers often access a backing store, and with indexers you often accept a parameter of int type and access a backing store of array type. An indexer is a member that enables an object to be indexed in the same way as an array.Hejlsberg et al., p. 498

Example
Let's look at a program that contains a class that has an indexer member, which itself contains a get accessor and a set accessor. These accessors are implicitly used when you assign the class instance elements in the same way as you can assign elements in an array. The indexer provides a level of indirection where you can insert bounds-checking, and in this way you can improve reliability and simplicity with indexers.

This C# example program uses an indexer. Indexers are properties. They have an array-like syntax.
Program that uses indexer with int [C#]

using System;

class Layout { string[] _values = new string[100]; // Backing store

public string this[int number] { get { // This is invoked when accessing Layout instances with the [ ]. if (number >= 0 && number < _values.Length) { // Bounds were in range, so return the stored value. return _values[number]; } // Return an error string. return "Error"; } set {

// This is invoked when assigning to Layout instances with the [ ]. if (number >= 0 && number < _values.Length) { // Assign to this element slot in the internal array. _values[number] = value; } } } }

class Program { static void Main() { // Create new instance and assign elements // ... in the array through the indexer. Layout layout = new Layout(); layout[1] = "Frank Gehry"; layout[3] = "I. M. Pei"; layout[10] = "Frank Lloyd Wright"; layout[11] = "Apollodorus"; layout[-1] = "Error"; layout[1000] = "Error";

// Read elements through the indexer. string value1 = layout[1]; string value2 = layout[3];

string value3 = layout[10]; string value4 = layout[11]; string value5 = layout[50]; string value6 = layout[-1];

// Write the results. Console.WriteLine(value1); Console.WriteLine(value2); Console.WriteLine(value3); Console.WriteLine(value4); Console.WriteLine(value5); // Is null Console.WriteLine(value6); } }

Output

Frank Gehry I. M. Pei Frank Lloyd Wright Apollodorus (null) Error

Overview. This program includes two classes: a Layout class containing an indexer we

define, and a Program class that contains the Main entry point. The Layout class contains an indexer that has get and set method bodies. The get and set accessors contain some logic that ensure the array will not will be accessed out-of-bounds. The array is termed the backing store for the indexer property in this example. Main method output. The program includes the Main entry point, which assigns elements in the Layout class backing store to various string literals. The string literals are the names of various famous architects from different times and places. The elements 1 and 1000 are used as parameters to the indexer, but these will not result in the backing store being changed because they are not valid. However, no exceptions are thrown by this because of our logic. String Literal Finally, the program displays the first four valid elements from the Layout class, and then an

element that was not changed from null and then the result of an invalid access.

Many parameters
The parameter list of your indexer can vary depending on your requirements. In a collection that maps string keys to values, you will want to have an indexer that accepts a parameter of type string. This will make the class have a lookup function based on the indexer. If you are trying to simulate a two-dimensional collection, you can use two parameters in the indexer. Because indexers are actually regular methods in the implementation, there are not many limitations regarding the parameter types. However, ref and out parameters are not allowed.

.NET Framework

Indexers have been used throughout the .NET Framework in Microsoft's own code since the framework was released. Almost all of the iterative collections and searchable collections use indexers as an option to access elements. This includes the Dictionary collection, which allows you to look up elements with the indexer. The indexer for the Dictionary often uses a string parameter as the indexer argument. The ArrayList and List classes also use indexers to simulate the built-in syntax of arrays in the C# language. This makes the List able to be used in syntactically the same way as an array when assigning or reading elements that are allocated. Dictionary ExamplesHashtable ExamplesArrayList TipsList Examples

Intermediate language

Here we examine the intermediate language for the indexer shown in this article. You can see that a separate metadata table exists that stores the get_Item and set_Item methods, which implement the logic for the get and set accessors in the indexer. In the .NET Framework, the metadata implements properties in the same way as methods but with an additional table to provide more information about the type of methods. There should be no performance difference between an indexer access and a method that internally contain the same logic. Intermediate Language
Intermediate language for example indexer [IL]

.property instance string Item { .get instance string Layout::get_Item(int32) .set instance void Layout::set_Item(int32, string) }

Summary

We looked at an example of the indexer type in the C# language, which is a property accessor type. Indexers have a somewhat more complicated syntax than other properties in the language, but the end result is they provide a function that is used when you do array-like accesses on the class instance. Indexers are very useful for helping describe and define intuitive types in a standard way through your class libraries. Additionally, they provide a useful level of indirection where you can insert bounds-checking, enhancing the reliability of array

accesses throughout your program.

Tutorial
Defining an indexer allows you to create classes that act like "virtual arrays." Instances of that class can be accessed using the [] array access operator. Defining an indexer in C# is similar to defining operator [] in C++, but is considerably more flexible. For classes that encapsulate array- or collection-like functionality, using an indexer allows the users of that class to use the array syntax to access the class. For example, suppose you want to define a class that makes a file appear as an array of bytes. If the file were very large, it would be impractical to read the entire file into memory, especially if you only wanted to read or change a few bytes. By defining a FileByteArray class, you could make the file appear similar to an array of bytes, but actually do file input and output when a byte was read or written. In addition to the example below, an advanced topic on Creating an Indexed Property is discussed in this tutorial. Example In this example, the class FileByteArray makes it possible to access a file as if it were a byte array. The Reverse class reverses the bytes of the file. You can run this program to reverse the bytes of any text file including the program source file itself. To change the reversed file back to normal, run the program on the same file again. // indexer.cs // arguments: indexer.txt using System; using System.IO; // Class to provide access to a large file // as if it were a byte array. public class FileByteArray { Stream stream; // Holds the underlying stream // used to access the file. // Create a new FileByteArray encapsulating a particular file. public FileByteArray(string fileName) { stream = new FileStream(fileName, FileMode.Open); } // Close the stream. This should be the last thing done // when you are finished. public void Close() { stream.Close(); stream = null; } // Indexer to provide read/write access to the file. public byte this[long index] // long is a 64-bit integer {

// Read one byte at offset index and return it. get { byte[] buffer = new byte[1]; stream.Seek(index, SeekOrigin.Begin); stream.Read(buffer, 0, 1); return buffer[0]; } // Write one byte at offset index and return it. set { byte[] buffer = new byte[1] {value}; stream.Seek(index, SeekOrigin.Begin); stream.Write(buffer, 0, 1); } } // Get the total length of the file. public long Length { get { return stream.Seek(0, SeekOrigin.End); } } } // Demonstrate the FileByteArray class. // Reverses the bytes in a file. public class Reverse { public static void Main(String[] args) { // Check for arguments. if (args.Length == 0) { Console.WriteLine("indexer <filename>"); return; } FileByteArray file = new FileByteArray(args[0]); long len = file.Length; // Swap bytes in the file to reverse it. for (long i = 0; i < len / 2; ++i) { byte t; // Note that indexing the "file" variable invokes the // indexer on the FileByteStream class, which reads // and writes the bytes in the file. t = file[i]; file[i] = file[len - i - 1]; file[len - i - 1] = t; }

file.Close(); } } Input: indexer.txt To test the program you can use a text file with the following contents (this file is called Test.txt in the Indexers Sample). public class Hello1 { public static void Main() { System.Console.WriteLine("Hello, World!"); } } To reverse the bytes of this file, compile the program and then use the command line: indexer indexer.txt To display the reversed file, enter the command: Type indexer.txt Sample Output } } ;)"!dlroW ,olleH"(eniLetirW.elosnoC.metsyS { )(niaM diov citats cilbup { 1olleH ssalc cilbup Code Discussion Since an indexer is accessed using the [] operator, it does not have a name. For indexer declaration syntax, seeIndexers. In the example above, the indexer is of type byte and takes a single index of type long (64-bit integer). The Get accessor defines the code to read a byte from the file, while the Set accessor defines the code to write a byte to the file. Inside the Set accessor, the predefined parameter value has the value that is being assigned to the virtual array element. An indexer must have at least one parameter. Although it is comparatively rare, an indexer can have more than one parameter in order to simulate a multidimensional "virtual array." Although integral parameters are the most common, the indexer parameter can be of any type. For example, the standard Dictionary class provides an indexer with a parameter of type Object. Although indexers are a powerful feature, it is important to use them only when the array-like abstraction makes sense. Always carefully consider whether using regular method(s) would be just as clear. For example, the following is a bad use of an indexer: class Employee

{ // VERY BAD STYLE: using an indexer to access // the salary of an employee. public double this[int year] { get { // return employee's salary for a given year. } } } Although legal, an indexer with only a Get accessor is rarely good style. Strongly consider using a method in this case.

Indexers can be overloaded (for more information, see 10.8.1 Indexer overloading).

Polymorphism (C# Programming Guide)


Visual Studio 2005 Other Versions

161 out of 212 rated this helpful Rate this topic

Through inheritance, a class can be used as more than one type; it can be used as its own type, any base types, or anyinterface type if it implements interfaces. This is called polymorphism. In C#, every type is polymorphic. Types can be used as their own type or as a Object instance, because any type automatically treats Object as a base type. Polymorphism is important not only to the derived classes, but to the base classes as well. Anyone using the base class could, in fact, be using an object of the derived class that has been cast to the base class type. Designers of a base class can anticipate the aspects of their base class that are likely to change for a derived type. For example, a base class for cars might contain behavior that is subject to change when the car in question is a minivan or a convertible. A base class can mark those class members as virtual, allowing derived classes representing convertibles and minivans to override that behavior. For more information, see Inheritance.

Polymorphism Overview
When a derived class inherits from a base class, it gains all the methods, fields, properties and events of the base class. To change the data and behavior of a base class, you have two choices: you can replace the base member with a new derived member, or you can override a virtual base member. Replacing a member of a base class with a new derived member requires the new keyword. If a base class defines a method, field, or property, the new keyword is used to create a new definition of that method, field, or property on a derived class. The new keyword is placed before the return type of a class member that is being replaced. For example: C# public class BaseClass { public void DoWork() { } public int WorkField; public int WorkProperty { get { return 0; } } } public class DerivedClass : BaseClass

{ public new void DoWork() { } public new int WorkField; public new int WorkProperty { get { return 0; } } }

When the new keyword is used, the new class members are called instead of the base class members that have been replaced. Those base class members are called hidden members. Hidden class members can still be called if an instance of the derived class is cast to an instance of the base class. For example: C# DerivedClass B = new DerivedClass(); B.DoWork(); // Calls the new method. BaseClass A = (BaseClass)B; A.DoWork(); // Calls the old method.

In order for an instance of a derived class to completely take over a class member from a base class, the base class has to declare that member as virtual. This is accomplished by adding the virtual keyword before the return type of the member. A derived class then has the option of using the override keyword, instead of new, to replace the base class implementation with its own. For example: C# public class BaseClass { public virtual void DoWork() { } public virtual int WorkProperty { get { return 0; } } } public class DerivedClass : BaseClass { public override void DoWork() { } public override int WorkProperty { get { return 0; } } }

Fields cannot be virtual; only methods, properties, events and indexers can be virtual. When a derived class overrides a virtual member, that member is called even when an instance of that class is being accessed as an instance of the base class. For example: C# DerivedClass B = new DerivedClass();

B.DoWork();

// Calls the new method.

BaseClass A = (BaseClass)B; A.DoWork(); // Also calls the new method.

Virtual methods and properties allow you to plan ahead for future expansion. Because a virtual member is called regardless of which type the caller is using, it gives derived classes the option to completely change the apparent behavior of the base class. Virtual members remain virtual indefinitely, no matter how many classes have been declared between the class that originally declared the virtual member. If class A declares a virtual member, and class B derives from A, and class C derives from B, class C inherits the virtual member, and has the option to override it, regardless of whether class B declared an override for that member. For example: C# public class A { public virtual void DoWork() { } } public class B : A { public override void DoWork() { } } C# public class C : B { public override void DoWork() { } }

A derived class can stop virtual inheritance by declaring an override as sealed. This requires putting the sealed keyword before the override keyword in the class member declaration. For example: C# public class C : B { public sealed override void DoWork() { } }

In the previous example, the method DoWork is no longer virtual to any class derived from C. It is still virtual for instances of C, even if they are cast to type B or type A. Sealed methods can be replaced by derived classes using thenew keyword, as the following example shows: C# public class D : C { public new void DoWork() { }

In this case, if DoWork is called on D using a variable of type D, the new DoWork is called. If a variable of type C, B, or A is used to access an instance of D, a call to DoWork will follow the rules of virtual inheritance, routing those calls to the implementation of DoWork on class C. A derived class that has replaced or overridden a method or property can still access the method or property on the base class using the base keyword. For example: C# public class A { public virtual void DoWork() { } } public class B : A { public override void DoWork() { } } C# public class C : B { public override void DoWork() { // Call DoWork on B to get B's behavior: base.DoWork(); // DoWork behavior specific to C goes here: // ... } }

Vous aimerez peut-être aussi