Arrays, dictionaries, and lists all provide an indexer as a convenient way to get or set a member of a collection based on a key or index. As we’ve seen, to use the indexer, you simply put the index (or indices) in square brackets after the collection name. It is possible to define your own indexer; Listing 17.10 shows an example using Pair<T>.
An indexer is declared much as a property is declared, except that instead of the name of the property, you use the keyword this followed by a parameter list in square brackets. The body is also like a property, with get and set blocks. As Listing 17.10 shows, the parameter does not have to be an int. In fact, the index can take multiple parameters and can even be overloaded. This example uses an enum to reduce the likelihood that callers will supply an index for a nonexistent item.
The Common Intermediate Language (CIL) code that the C# compiler creates from an index operator is a special property called Item that takes an argument. Properties that accept arguments cannot be created explicitly in C#, so the Item property is unique in this aspect. Any additional member with the identifier Item, even if it has an entirely different signature, will conflict with the compiler-created member, so it will not be allowed.
As indicated earlier, the CIL property name for an indexer defaults to Item. Using the IndexerNameAttribute, you can specify a different name, however. Listing 17.11, for example, changes the name to "Entry".
This makes no difference to C# callers of the index, but it specifies the name for languages that do not support indexers directly.
The IndexerNameAttribute is merely an instruction to the compiler to use a different name for the indexer; the attribute is not actually emitted into metadata by the compiler, so it is not available via reflection.
An index operator can also take a variable parameter list. For example, Listing 17.12 defines an index operator for BinaryTree<T>, discussed in Chapter 12 (and again in the next section).
Each item within branches is a PairItem and indicates which branch to navigate down in the binary tree. For example,
tree[PairItem.Second, PairItem.First].Value
will retrieve the value located at the second item in the first branch, followed by the first branch within that branch.