Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C# 2008 Step by Step.pdf
Скачиваний:
22
Добавлен:
25.03.2016
Размер:
13.96 Mб
Скачать

Chapter 16

Using Indexers

After completing this chapter, you will be able to:

Encapsulate logical arraylike access to an object by using indexers.

Control read access to indexers by declaring get accessors.

Control write access to indexers by declaring set accessors.

Create interfaces that declare indexers.

Implement indexers in structures and classes that inherit from interfaces.

The preceding chapter described how to implement and use properties as a means of providing controlled access to the fields in a class. Properties are useful for mirroring fields that contain a single value. However, indexers are invaluable if you want to provide access to items that contain multiple values by using a natural and familiar syntax.

What Is an Indexer?

An indexer is a smart array in exactly the same way that a property is a smart field. The syntax that you use for an indexer is exactly the same as the syntax that you use for an array. The best way to understand indexers is to work through an example. First we’ll examine a problem and examine a weak solution that doesn’t use indexers. Then we’ll work through the same problem and look at a better solution that does use indexers. The problem concerns integers, or more precisely, the int type.

An Example That Doesn’t Use Indexers

You normally use an int to hold an integer value. Internally, an int stores its value as a sequence of 32 bits, where each bit can be either 0 or 1. Most of the time, you don’t care about this internal binary representation; you just use an int type as a bucket to hold an integer value. However, sometimes programmers use the int type for other purposes: some programs manipulate the individual bits within an int. In other words, occasionally a program might use an int because it holds 32 bits and not because it can represent an integer value. (If you are an old C hack like I am, what follows should have a very familiar feel!)

295

296

Part III Creating Components

Note Some older programs used int types to try to save memory. Such programs typically date back to when the size of computer memory was measured in kilobytes rather than the gigabytes available these days and memory was at an absolute premium. A single int holds 32 bits, each of

which can be 1 or 0. In some cases, programmers assigned 1 to indicate the value true and 0 to indicate false and then employed an int as a set of Boolean values.

As an example, the following expression uses the left-shift (<<) and bitwise AND (&) operators to determine whether the sixth bit of the int named bits is set to 0 or to 1:

(bits & (1 << 6)) != 0

If the bit at position 6 is 0, this expression evaluates to false; if the bit at position 6 is 1, this expression evaluates to true. This is a fairly complicated expression, but it’s trivial in comparison with the following expression, which uses the compound assignment operator &= to set the bit at position 6 to 0:

bits &= ~(1 << 6)

Note The bitwise operators count the positions of bits from right to left, so bit 0 is the rightmost bit, and the bit at position 6 is the bit six places from the right.

Similarly, if you want to set the bit at position 6 to 1, you can use a bitwise OR (|) operator. The following complicated expression is based on the compound assignment operator |=:

bits |= (1 << 6)

The trouble with these examples is that although they work, it’s not clear why or how they work. They’re complicated, and the solution is a very low-level one: it fails to create an abstraction of the problem that it solves.

The Bitwise and Shift Operators

You might have noticed some unfamiliar symbols in the expressions shown in these examples—in particular, ~, <<, |, and &. These are some of the bitwise and shift operators, and they are used to manipulate the individual bits held in int and long data types.

The NOT (~) operator is a unary operator that performs a bitwise complement. For example, if you take the 8-bit value 11001100 (204 decimal) and apply the ~ operator to it, you obtain the result 00110011 (51 decimal)—all

the 1s in the original value become 0s, and all the 0s become 1s.

Chapter 16 Using Indexers

297

The left-shift (<<) operator is a binary operator that performs a left shift. The expression 204 << 2 returns the value 48. (In binary, 204 decimal is 11001100, and left-shifting it by two places yields 00110000, or 48 decimal.) The far-

left bits are discarded, and zeros are introduced from the right. There is a corresponding right-shift operator >>.

The OR (|) operator is a binary operator that performs a bitwise OR operation, returning a value containing a 1 in each position in which either of the oper-

ands has a 1. For example, the expression 204 | 24 has the value 220 (204 is

11001100, 24 is 00011000, and 220 is 11011100).

The AND (&) operator performs a bitwise AND operation. AND is similar to the

bitwise OR operator, except that it returns a value containing a 1 in each position where both of the operands have a 1. So 204 & 20 is 8 (204 is 11001100,

24 is 00011000, and 8 is 00001000).

The XOR (^) operator performs a bitwise exclusive OR operation, returning a 1 in each bit where there is a 1 in one operand or the other but not both.

(Two 1s yield a 0—this is the “exclusive” part of the operator.) So 204 ^ 24 is

212 (11001100 ^ 00011000 is 11010100).

The Same Example Using Indexers

Let’s pull back from the preceding low-level solution for a moment and stop to remind ourselves what the problem is. We’d like to use an int not as an int but as an array of 32

bits. Therefore, the best way to solve this problem is to use an int as if it were an array of 32 bits! In other words, what we’d like to be able to write to access the bit at index 6 of the bits

variable is something like this:

bits[6]

And, for example, to set the bit at index 6 to true, we’d like to be able to write:

bits[6] = true

Unfortunately, you can’t use the square bracket notation on an int—it works only on an array or on a type that behaves like an array. So the solution to the problem is to create a new type

that acts like, feels like, and is used like an array of bool variables but is implemented by using an int. You can achieve this feat by defining an indexer. Let’s call this new type IntBits. IntBits will contain an int value (initialized in its constructor), but the idea is that we’ll use IntBits as

an array of bool variables.

Tip The IntBits type is small and lightweight, so it makes sense to create it as a structure rather than as a class.

298Part III Creating Components

struct IntBits

{

public IntBits(int initialBitValue)

{

bits = initialBitValue;

}

// indexer to be written here

private int bits;

}

To define the indexer, you use a notation that is a cross between a property and an array. The indexer for the IntBits struct looks like this:

struct IntBits

{

...

public bool this [ int index ]

{

get

{

return (bits & (1 << index)) != 0;

}

set

{

if (value) // turn the bit on if value is true; otherwise, turn it off bits |= (1 << index);

else

bits &= ~(1 << index);

}

}

...

}

Notice the following points:

An indexer is not a method—there are no parentheses containing a parameter, but there are square brackets that specify an index. This index is used to specify which element is being accessed.

All indexers use the this keyword in place of the method name. A class or structure can define at most one indexer, and it is always named this.

Indexers contain get and set accessors just like properties. In this example, the get and set accessors contain the complicated bitwise expressions previously discussed.

The index specified in the indexer declaration is populated with the index value specified when the indexer is called. The get and set accessor methods can read this argument to determine which element should be accessed.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]