Прочитав эту главу, вы научитесь:
• инкапсулировать с помощью индексаторов логический доступ к объекту, работающий наподобие доступа к элементам массива;
• управлять доступом к индексаторам для чтения из них данных, объявляя методы доступа get;
• управлять доступом к индексаторам для записи в них данных, объявляя методы доступа set;
• создавать интерфейсы, объявляющие индексаторы;
• осуществлять реализацию в структурах и классах индексаторов, наследуемых из интерфейсов.
В главе 15 «Реализация свойств для доступа к полям» рассматривались способы реализации и использования свойств как средств предоставления управляемого доступа к полям класса. Свойства могут использоваться для зеркального отображения полей, содержащих единственное значение. Но индексаторы незаменимы при обеспечении доступа к элементам, содержащим несколько значений, и при реализации этого доступа с использованием понятного и знакомого синтаксиса.
Индексатор можно представить себе в качестве более разумно организованного массива, что во многом похоже на представление свойства в качестве более разумно организованного поля. Свойство инкапсулирует в классе одно значение, а индексатор делает то же самое для набора значений. Для индексатора используется точно такой же синтаксис, как и для массива.
Разобраться с тем, что такое индексатор, лучше всего на примере. Сначала будет рассмотрена задача, в которой не используются индексаторы. Затем та же задача будет решена более эффективно за счет применения индексаторов. В задаче используются целые числа, или, если точнее, значения int-типа.
Обычно int-тип используется для хранения целочисленного значения. Внутри машины значения int-типа сохраняются в виде последовательности из 32 битов, где каждый бит может содержать либо 0, либо 1. Чаще всего это внутреннее двоичное представление вас абсолютно не касается, вы просто используете int-тип в качестве контейнера, содержащего целочисленное значение. Но иногда программисты применяют int-тип для других целей — некоторые из них используют int в качестве набора двоичных флагов и работают внутри значения int-типа с отдельными битами. Если вы такой же приверженец языка C старой закалки, как и я, то все, что вы прочтете дальше, навеет весьма живые воспоминания.
ПРИМЕЧАНИЕ В некоторых устаревших программах int-типы используются с целью экономии памяти. Эти программы создавались во времена, когда объемы компьютерной памяти измерялись в килобайтах, а не гигабайтах, как сегодня, и память была дефицитной. Тип int с одинарной точностью хранил 32 бита, каждый из которых мог быть либо единицей, либо нулем. В некоторых случаях программисты присваивали единицу, чтобы показать истинное значение (true), и нуль, чтобы показать ложное значение (false), а затем использовали int-тип как набор булевых значений.
В C# предоставляется набор операторов, которыми можно воспользоваться для доступа к отдельным битам в int-значении и работы с ними. К таким операторам относятся:
• оператор НЕ (~). Это унарный оператор, выполняющий поразрядное дополнение. Например, если взять 8-битное значение 11001100 (это десятичное число 204) и применить к нему оператор ~, то в результате получится значение 00110011 (это десятичное число 51) — все единицы в исходном значении стали нулями, а нули превратились в единицы;
ПРИМЕЧАНИЕ Показанные здесь примеры имеют чисто иллюстративное назначение и верны только для 8 бит. В C# int-тип имеет размерность 32 бита, поэтому при попытке применения любого из этих примеров в приложении C# будет получаться 32-битный результат, который может отличаться от результата, показанного в данной подборке примеров. Например, при 32 битах число 204 имеет вид 00000000000000000000000011001100, поэтому в C# ~204 будет иметь вид 11111111111111111111111100110011 (что фактически является представлением в C# десятичного числа –205).
• оператор сдвига влево (<<). Это бинарный оператор, выполняющий сдвиг влево. Выражение 204 << 2 возвращает значение 48. (В двоичном виде десятичное число 204 отображается как 11001100, и его сдвиг влево на две позиции дает результат 00110000, или десятичное число 48.) Самые левые биты удаляются, а справа появляются нули. Существует также аналогичный оператор, осуществляющий сдвиг вправо (>>);
• оператор ИЛИ (|). Это бинарный оператор, выполняющий поразрядную операцию ИЛИ, возвращающую значение, содержащую единицу в каждом разряде, в котором в любом из операндов имеется единица. Например, выражение 204 | 24 дает значение 220 (204 — это 11001100, 24 — это 00011000, а 220 — это 11011100);
• оператор И (&). Этот оператор выполняет поразрядную операцию И. Оператор И похож на оператор ИЛИ, но он возвращает значение, содержащее единицу в каждом разряде, в котором у обоих операндов имеется единица. Поэтому выражение 204 & 24 дает результат 8 (204 — это 11001100, 24 — это 00011000, а 8 — это 00001000);
• оператор исключающего ИЛИ (^). Этот оператор выполняет поразрядную операцию исключающего ИЛИ, возвращая единицу в каждом разряде, в котором имеется единица в каком-либо из операндов, но не в обоих вместе. (Две единицы дают нуль, в этом и выражается «исключающая» часть оператора.) Следовательно, выражение 204 ^ 24 дает результат 212 (11001100 ^ 00011000 является 11010100).
Для определения значений отдельных битов в int-типе эти операторы могут использоваться вместе. Например, в следующем выражении используются операторы сдвига влево (<<) и поразрядного И (&), в результате чего определяется, какое значение имеет шестой разряд справа байтовой переменной по имени bits — нуль или единицу:
(bits & (1 << 5)) != 0
ПРИМЕЧАНИЕ Поразрядные операторы вычисляют позиции разрядов справа налево, а разряды отсчитываются, начиная с нуля. Соответственно, самым правым является нулевой разряд, а разряд в позиции с номером 5 — шестым справа.
Предположим, что переменная bits содержит десятичное число 42. В двоичном выражении оно имеет вид 00101010. Десятичное значение 1 соответствует двоичному значению 00000001, а выражение 1 << 5 имеет значение 00100000, где шестой разряд установлен в единицу. В двоичном виде выражение bits & (1 << 5) выглядит как 00101010 & 00100000, и значение этого выражения в том же двоичном виде выглядит как 00100000, то есть не является нулем. Если переменная bits содержит значение 65, или в двоичном виде 01000001, значение выражения выглядит как 01000001 & 00100000, что дает двоичный результат 00000000, или нуль.
Это довольно сложный пример, но по сравнению со следующим выражением, в котором для установки бита в позиции 6 в нуль используется оператор составного присваивания &=, он представляется простым:
bits &= ~(1 << 5)
Аналогично этому, если требуется установить бит в позиции 6 в единицу, можно воспользоваться поразрядным оператором ИЛИ (|). Следующее сложное выражение основано на применении составного оператора |=:
bits |= (1 << 5)
Применение таких примеров затруднено тем, что, несмотря на их полную состоятельность, в них неимоверно сложно разобраться. К тому же решение с их применением находится на одном из самых низких уровней программирования: на его основе невозможно создать абстракцию выполняемой задачи, что существенно усложняет сопровождение кода, выполняющего подобные операции.
Давайте немного отвлечемся от предыдущего низкоуровневого решения и вспомним, что представляет собой эта задача. Вам нужно воспользоваться int-типом не по прямому его назначению, а в качестве массива битов. Поэтому наилучшим способом решения этой задачи станет использование int-типа таким образом, будто он является массивом битов, или, иными словами, обеспечение возможности получения доступа к шестому биту справа в переменной bits путем использования следующего выражения (не забываем, что индексирование массивов начинается с нуля):
bits[5]
А для установки для четвертого бита справа значения true хотелось бы иметь возможность написать следующее:
bits[3] = true
ПРИМЕЧАНИЕ Для опытных разработчиков приложений на языке C булево значение true является синонимом двоичного значения 1, а булево значение false — двоичного значения 0. Соответственно, выражение bits[3] = true означает: «Установить в переменной bits для четвертого бита справа значение 1».
К сожалению, воспользоваться системой записи с квадратными скобками в отношении int-значения невозможно, она работает только в отношении массива или типа данных с поведением массива. Поэтому решение задачи заключается в создании нового типа, работающего как массив, воспринимаемого как массив и используемого качестве массива булевых переменных, но реализуемого с использованием int-значения. Достичь желаемого результата можно с помощью индексатора. Давайте назовем этот новый тип IntBits. Он будет содержать int-значение (инициализированное в своем конструкторе), но суть замысла в том, что значение типа IntBits будет использоваться в качестве массива булевых переменных.
СОВЕТ Тип IntBits представляется весьма простым, поэтому имеет смысл создать его в виде структуры, а не в виде класса.
struct IntBits
{
private int bits;
public IntBits(int initialBitValue)
{
bits = initialBitValue;
}
// здесь будет написан индексатор
}
Для определения индексатора используется система записи, являющаяся гибридом записи свойства и массива. Индексатор вводится с помощью ключевого слова this, при этом указывается тип возвращаемого индексатором значения, а в квадратных скобках указывается тип значения, используемого в индексаторе в качестве индекса. В индексаторе для структуры IntBits в качестве типа его индекса используется целое число, а возвращает он булево значение:
struct IntBits
{
...
public bool this [ int index ]
{
get
{
return (bits & (1 << index)) != 0;
}
set
{
if (value) // если value равно true, установка, а иначе – сброс бита
bits |= (1 << index);
else
bits &= ~(1 << index);
}
}
}
Обратите внимание на следующие особенности.
• Индексатор не является методом: параметры в круглых скобках отсутствуют, а в квадратных скобках указывается индекс, который используется для указания того, к какому элементу происходит обращение.
• Индексаторы используют ключевое слово this. В классе или структуре может быть определено не более одного индексатора (хотя его можно переопределить и иметь несколько реализаций), и он всегда называется this.
• В индексаторах, как и в свойствах, содержатся методы доступа get и set. В данном примере методы доступа get и set содержат ранее рассмотренные сложные поразрядные выражения.
• Индекс, указанный в объявлении индексатора, заполняется индексным значением, указанным при вызове индексатора. Методы доступа get и set могут считать этот аргумент для определения элемента, к которому осуществляется доступ.
ПРИМЕЧАНИЕ Во избежание любых неожиданных исключений, выдаваемых в коде индексатора, в индексаторе нужно проверять значение индекса на его принадлежность к допустимому диапазону значений.
После объявления индексатора вы можете вместо int-переменной воспользоваться переменной типа IntBits и, как показано в следующем примере, применить к ней систему записи с использованием квадратных скобок:
int adapted = 126; // Двоичный вид числа 126 — 01111110
IntBits bits = new IntBits(adapted);
bool peek = bits[6]; // извлечение булева значения по индексу 6;
// оно должно быть true (1)
bits[0] = true; // установка бита по индексу 0 в true (1)
bits[3] = false; // установка бита по индексу 3 в false (0)
// теперь в bits содержится значение 01110111,
или 119 в десятичном исчислении
Вполне очевидно, что разобраться в этом синтаксисе будет гораздо проще. В нем довольно лаконично и непосредственно отражается суть задачи.
При чтении с использованием индексатора компилятор автоматически преобразует ваш код, имеющий признаки массива, в вызов метода доступа get данного индексатора. Рассмотрим следующий пример:
bool peek = bits[6];
Эта инструкция преобразуется в вызов метода доступа get для индексатора bits, а для аргумента index устанавливается значение 6.
Аналогично этому при записи с использованием индексатора компилятор автоматически преобразует ваш код, имеющий признаки массива, в вызов метода доступа set данного индексатора, устанавливая для аргумента index значение, заключенное в квадратные скобки:
bits[3] = true;
Эта инструкция преобразуется в вызов метода доступа set для индексатора bits, а для аргумента index устанавливается значение 3. Как и в обычных свойствах, данные, записываемые в индексатор (в данном случае true), становятся доступными внутри метода доступа set при использовании ключевого слова value. Тип у переменной value такой же, как тип у самого индексатора (в данном случае bool).
Индексатор также можно применять в сложном контексте чтения/записи. В таком случае используются оба метода доступа, и get, и set. Посмотрите на следующую инструкцию, в которой используется оператор ИЛИ (^), чтобы инвертировать значение бита с индексом 6 в переменной bits:
bits[6] ^= true;
Это выражение будет автоматически преобразовано в следующий код:
bits[6] = bits[6] ^ true;
Работоспособность этого кода обеспечивается наличием обоих методов доступа, get и set.
ПРИМЕЧАНИЕ Можно объявить индексатор, содержащий только метод доступа get (индексатор только для чтения) или только метод доступа set (индексатор только для записи).
При использовании индексатора намеренно применяется синтаксис, очень похожий на тот, что применяется в массивах. Но между индексаторами и массивами имеется ряд весьма важных различий.
• В индексаторах могут использоваться нечисловые индексы, например строки (как показано в следующем примере), а в массивах могут использоваться только целочисленные индексы:
public int this [ string name ] { ... } // Допустимо
• Индексаторы могут перегружаться (точно так же, как методы), а массивы не могут:
public Name this [ PhoneNumber number ] { ... }
public PhoneNumber this [ Name name ] { ... }
• Индексаторы не могут использоваться в качестве ref- или out-параметров, а массивы могут:
IntBits bits; // bits содержит индексатор
Method(ref bits[1]); // ошибка в ходе компиляции
Свойства, массивы и индексаторы
Свойство может возвращать массив, но следует помнить, что массивы относятся к ссылочным типам, поэтому предоставление массива в качестве свойства позволяет случайно перезаписать большой массив данных. Посмотрите на следующую структуру, предоставляющую свойство по имени Data, содержащее массив:
struct Wrapper
{
private int[] data;
...
public int[] Data
{
get { return this.data; }
set { this.data = value; }
}
}
Теперь посмотрите на следующий код, использующий это свойство:
Wrapper wrap = new Wrapper();
...
int[] myData = wrap.Data;
myData[0]++;
myData[1]++;
Его внешний вид вроде бы ничем не настораживает. Но поскольку массивы относятся к ссылочным типам, переменная myData ссылается на тот же самый объект, что и закрытая переменная data в структуре Wrapper. Любые изменения, вносимые в элементы в myData, делаются в массиве data, а выражение myData[0]++ производит точно такой же эффект, что и выражение data[0]++. Если это противоречит вашим замыслам, нужно в методах доступа get и set свойства Data воспользоваться методом Clone, чтобы возвращалась копия массива data, или сделать копию устанавливаемого значения, как показано в следующем примере кода. (Метод Clone для копирования массивов рассматривался в главе 8 «Основные сведения о значениях и ссылках».) Учтите, что метод Clone возвращает объект, который следует привести к типу целочисленного массива:
struct Wrapper
{
private int[] data;
...
public int[] Data
{
get { return this.data.Clone() as int[]; }
set { this.data = value.Clone() as int[]; }
}
}
Но с точки зрения использования памяти этот подход может оказаться слишком грубым и неэкономичным. А индексаторы позволяют решить эту задачу намного проще и естественнее — не нужно предоставлять в качестве свойства весь массив, вместо этого лучше через индексатор сделать доступными его отдельные элементы:
struct Wrapper
{
private int[] data;
...
public int this [int i]
{
get { return this.data[i]; }
set { this.data[i] = value; }
}
}
В следующем коде индексатор используется примерно так же, как и в показанном ранее свойстве:
Wrapper wrap = new Wrapper();
...
int[] myData = new int[2];
myData[0] = wrap[0];
myData[1] = wrap[1];
myData[0]++;
myData[1]++;
Теперь увеличение значений в массиве myData на единицу не влияет на исходный массив Wrapper-объекта. Если же действительно понадобится изменить данные во Wrapper-объекте, придется написать следующую инструкцию:
wrap[0]++;
Так будет намного понятнее и безопаснее!
Индексаторы можно объявлять в интерфейсе. Для этого указываются ключевое слово get, ключевое слово set или же оба этих слова, но вместо тела метода доступа get или set ставится точка с запятой. Любые реализующие интерфейс класс или структура должны включать и реализацию методов доступа к индексатору, объявленному в интерфейсе:
interface IRawInt
{
bool this [ int index ] { get; set; }
}
struct RawInt : IRawInt
{
...
public bool this [ int index ]
{
get { ... }
set { ... }
}
...
}
Если индексатор интерфейса реализуется в классе, объявление индексатора можно сделать виртуальным. Это позволит последующим производным классам перегружать методы доступа get и set:
class RawInt : IRawInt
{
...
public virtual bool this [ int index ]
{
get { ... }
set { ... }
}
...
}
Можно также выбрать реализацию индексатора с использованием синтаксиса явной реализации, рассмотренного в главе 13 «Создание интерфейсов и определение абстрактных классов». Явная реализация индексатора не может иметь модификатор доступа public и объявляться виртуальной (и, соответственно, не может быть перегружена):
struct RawInt : IRawInt
{
...
bool IRawInt.this [ int index ]
{
get { ... }
set { ... }
}
...
}
В следующем упражнении будет исследовано и завершено приложение простой телефонной книги. В классе PhoneBook будут созданы два индексатора: один, принимающий параметр типа Name (имя) и возвращающий значение типа PhoneNumber (номер телефона), и второй, действующий наоборот, то есть принимающий параметр PhoneNumber и возвращающий Name. (Структуры Name и PhoneNumber будут содержаться в упражнении в готовом виде.) Вам также нужно будет вызвать эти индексаторы из подходящих для этого мест в программе.
Откройте в среде Microsoft Visual Studio проект Indexers, который находится в папке \Microsoft Press\VCSBS\Chapter 16\Indexers вашей папки документов.
С помощью этого графического приложения пользователь может искать номер телефона делового партнера, а также искать имя делового партнера по заданному номеру телефона.
Щелкните в меню Отладка на пункте Начать отладку. Произойдут сборка и запуск приложения и появится форма с пустыми текстовыми полями Name (Имя) и Phone Number (Номер телефона). Изначально в форме имеются две кнопки: одна для поиска телефона при заданном имени, другая для поиска имени при заданном номере телефона. Если раскрыть панель управления в нижней части формы, появится кнопка Add (Добавить), которая добавит к списку имен и номеров телефона, хранящемуся в приложении, еще одну пару «имя — номер телефона». Все кнопки, включая Add на панели управления, в данный момент ничего не делают. Приложение имеет вид, показанный на рис. 16.1.
Вам предстоит завершить приложение и заставить кнопки работать.
Вернитесь в среду Visual Studio 2015 и остановите отладку.
Выведите в окно редактора файл Name.cs проекта Indexers. Изучите структуру Name. Она предназначена для работы в качестве хранилища имен. Имя предоставляется конструктору в виде строки. Это имя может быть извлечено путем использования строкового свойства Text, предназначенного только для чтения. (Методы Equals и GetHashCode используются для сравнения имен при сквозном поиске в массиве из Name-значений, и пока их можно проигнорировать.)
Выведите в окно редактора файл PhoneNumber.cs и изучите структуру PhoneNumber. Она похожа на структуру Name.
Выведите в окно редактора файл PhoneBook.cs и изучите класс PhoneBook.
Рис. 16.1
В этом классе содержатся два закрытых массива: массив Name-значений по имени names и массив PhoneNumber-значений по имени phoneNumbers. Класс PhoneBook содержит также метод Add, добавляющий номер телефона и имя в телефонную книгу. Этот метод вызывается, когда пользователь щелкает на имеющейся в форме кнопке Add. Метод enlargeIfFull вызывается методом Add для проверки заполненности массивов, когда пользователь добавляет еще одну запись. Этот метод создает два новых, более крупных массива, копируя в них содержимое существующих массивов, после чего уничтожает старые массивы.
Метод Add намеренно упрощен и не проверяет, добавлялись ли уже в телефонную книгу данные имя или телефон.
Класс PhoneBook пока не предоставляет функциональных возможностей, с помощью которых пользователь смог бы найти имя или номер телефона. В следующем упражнении для реализации такой возможности вами будут добавлены два индексатора.
Удалите из файла PhoneBook.cs комментарий // TODO: write 1st indexer here, заменив его открытым, предназначенным только для чтения индексатором для класса PhoneBook, выделенным в следующем примере кода жирным шрифтом. Индексатор будет возвращать Name-значение, получив в качестве своего индекса PhoneNumber-значение. Оставьте тело метода доступа get пустым.
Индексатор должен иметь следующий вид:
sealed class PhoneBook
{
...
public Name this[PhoneNumber number]
{
get
{
}
}
...
}
Реализуйте метод доступа get, выделенный в следующем примере кода жирным шрифтом.
Метод доступа предназначен для поиска имени, соответствующего указанному номеру телефона. Для этого вам нужно вызвать статический метод IndexOf, принадлежащий классу Array. Метод IndexOf осуществляет сквозной поиск в массиве, возвращая индекс первого же элемента массива, соответствующего указанному значению. Первым аргументом IndexOf является массив для выполнения сквозного поиска (phoneNumbers). Вторым аргументом IndexOf является искомый элемент. IndexOf возвращает целочисленный индекс элемента, если таковой будет найден, в противном случае возвращает –1. Если индексатор находит номер телефона, он должен возвращать соответствующее имя, а в противном случае — пустое Name-значение. (Учтите, что Name является структурой, следовательно, пассивный конструктор установит для его закрытого поля name значение null.)
sealed class PhoneBook
{
...
public Name this [PhoneNumber number]
{
get
{
int i = Array.IndexOf(this.phoneNumbers, number);
if (i != -1)
{
return this.names[i];
}
else
{
return new Name();
}
}
}
...
}
Замените комментарий // TODO: write 2nd indexer here вторым открытым, предназначенным только для чтения индексатором для класса PhoneBook, возвращающим PhoneNumber-значение и принимающим один Name-параметр. Реализуйте этот индексатор точно так же, как и первый индексатор. (Тут снова следует учесть, что PhoneNumber является структурой, всегда имеющей пассивный конструктор.)
Второй индексатор должен иметь следующий вид:
sealed class PhoneBook
{
...
public PhoneNumber this [Name name]
{
get
{
int i = Array.IndexOf(this.names, name);
if (i != -1)
{
return this.phoneNumbers[i];
}
else
{
return new PhoneNumber();
}
}
}
...
}
Обратите внимание на возможность сосуществования этих перегружаемых индексаторов, обусловленную тем, что их индексы имеют разные типы, а значит, и разные сигнатуры. Если бы структуры Name и PhoneNumber были заменены простыми строками (которые в них заключены), перегрузка имела бы одинаковую сигнатуру и класс не прошел бы компиляцию.
Щелкните в меню Сборка на пункте Собрать решение, исправьте все синтаксические ошибки, если таковые обнаружатся, а затем, если это необходимо, выполните повторную сборку решения.
Выведите в окно редактора файл MainPage.xaml.cs и найдите метод findByNameClick. Этот метод вызывается при щелчке на кнопке Find By Name (Найти по имени). На данный момент код в нем отсутствует. Замените комментарий // TODO: кодом, выделенным в следующем примере жирным шрифтом. Этот код выполняет следующие задачи:
• считывает значение свойства Tex, имеющегося в форме текстового поля name. В этой строке содержится имя делового партнера, набранное пользователем;
• если строка не является пустым значением, с помощью индексатора ищет номер телефона, соответствующий этому имени в телефонной книге PhoneBook. (Заметьте, что в классе MainPage содержится закрытое PhoneBook-поле по имени phoneBook.) Код создает из строки Name-объект и передает его в качестве параметра индексатору PhoneBook;
• если возвращенное индексатором свойство Text, принадлежащее структуре PhoneNumber, не содержит значение null или не является пустым, записывает значение этого свойства в имеющееся в форме текстовое поле phoneNumber. В противном случае в это поле выводится текст «Not Found», свидетельствующий о том, что поиск не удался.
В окончательном виде метод findByNameClick должен выглядеть следующим образом:
private void findByNameClick(object sender, RoutedEventArgs e)
{
string text = name.Text;
if (!String.IsNullOrEmpty(text))
{
Name personsName = new Name(text);
PhoneNumber personsPhoneNumber = this.phoneBook[personsName];
phoneNumber.Text = String.IsNullOrEmpty(personsPhoneNumber.Text) ?
"Not Found" : personsPhoneNumber.Text;
}
}
Кроме инструкции, которая обращается к индексатору, у этого кода есть еще две интересные особенности.
• Статический String-метод по имени IsNullOrEmpty используется для определения того, не является ли строка пустой и не содержит ли она null-значение. Это метод лучше всего подходит для тестирования строки на наличие в ней значения. Он возвращает true, если строка содержит значение null или является пустой, а в противном случае возвращает значение false.
• Инструкция, заполняющая свойство Text текстового поля phoneNumber, использует оператор ? :, который действует как встроенная в выражение инструкция if…else. Этот оператор работает с тремя операндами: булевым выражением, выражением для вычисления и возвращения в случае вычисления булева выражения в true и еще одним выражением для вычисления и возвращения в случае вычисления булева выражения в false. Если в предыдущем коде выражение String.IsNullOrEmpty(personsPhoneNumber.Text) вычисляется в true, что означает, что совпадений в записях телефонной книги найдено не было, то в форме выводится текст «Not Found»; в противном случае выводится значение, хранящееся в свойстве Text переменной personsPhoneNumber.
В общем виде оператор ? : имеет следующий синтаксис:
Результат = <булево выражение> ? <Вычисляется, если true> : <Вычисляется,
если false>
Найдите в файле MainPage.xaml.cs метод findByPhoneNumberClick, который находится ниже метода findByNameClick. Метод findByPhoneNumberClick вызывается при щелчке на кнопке Find By Phone Number (Найти по номеру телефона). Пока в этом методе нет ничего, кроме комментария // TODO:. Вам нужно реализовать этот метод, вставив в него код. (Полный код метода показан в следующем примере жирным шрифтом.)
Прочитайте значение свойства Text из имеющегося в форме текстового поля phoneNumber. Это строка, содержащая номер телефона, набранный пользователем.
Если в строке есть содержимое, воспользуйтесь индексатором для поиска имени, соответствующего этому номеру телефона в PhoneBook.
Запишите в принадлежащее форме поле name значение возвращенного индексатором свойства Text, принадлежащего структуре Name.
В окончательном варианте метод должен приобрести следующий вид:
private void findByPhoneNumberClick(object sender, RoutedEventArgs e)
{
string text = phoneNumber.Text;
if (!String.IsNullOrEmpty(text))
{
PhoneNumber personsPhoneNumber = new PhoneNumber(text);
Name personsName = this.phoneBook[personsPhoneNumber];
name.Text = String.IsNullOrEmpty(personsName.Text) ?
"Not Found" : personsName.Text;
}
}
Щелкните в меню Сборка на пункте Собрать решение и исправьте любые выявленные ошибки.
Щелкните в меню Отладка на пункте Начать отладку. Наберите в соответствующих полях свое имя и номер телефона, затем раскройте панель управления и щелкните на кнопке Add (Добавить) (панель управления раскрывается по щелчку на символе многоточия). При щелчке на кнопке Add (Добавить) метод Add сохраняет информацию в телефонной книге и очищает текстовые поля, подготавливая их к поиску.
Наберите несколько разных имен и номеров, чтобы в телефонной книге появилась подборка записей. Учтите, что приложение не проверяет вводимые имена и номера телефонов и одно и то же имя и один и тот же номер телефона можно ввести более одного раза. Чтобы не мешать демонстрации работы программы и исключить путаницу, постарайтесь задавать разные имена и номера телефонов.
Наберите одно из ранее введенных имен в поле Name и щелкните на кнопке Find By Name (Найти по имени). Из телефонной книги будет извлечен и выведен в текстовое поле Phone Number номер телефона, ранее присвоенный вами этому деловому партнеру.
Наберите в поле Phone Number номер телефона другого делового партнера, а затем щелкните на кнопке Find By Phone Number (Найти по номеру телефона). Из телефонной книги будет извлечено и показано в поле Name имя делового партнера.
Наберите в поле Name имя, которое не вводилось в телефонную книгу, а затем щелкните на кнопке с надписью Find By Name (Найти по имени). На этот раз в поле с надписью Phone Number будет выведено сообщение о том, что номер не найден, — «Not Found».
Закройте форму и вернитесь в среду Visual Studio 2015.
В этой главе вы узнали, как использовать индексаторы, предоставляющие к данным класса такой же доступ, как и к элементам массива. Вы научились создавать индексаторы, которые могут принимать индекс и возвращать соответствующее значение с использованием логики, определяемой в методе доступа get, и увидели, как используется с индексом метод доступа set, предназначенный для того, чтобы заполнить значение в индексаторе.
Если хотите продолжить работу и изучить следующую главу, оставьте открытой среду Visual Studio 2015 и переходите к главе 17 «Введение в обобщения».
Если сейчас вы хотите выйти из среды Visual Studio 2015, то в меню Файл щелкните на пункте Выход. Увидев диалоговое окно с предложением сохранить изменения, щелкните на кнопке Да и сохраните проект.
Чтобы | Сделайте следующее |
Создать индексатор для класса или структуры | Объявите тип индексатора, затем наберите ключевое слово this, а после него укажите в квадратных скобках аргументы индексатора. В теле индексатора может содержаться метод доступа get и/или set, например: struct RawInt { ... public bool this [ int index ] { get { ... } set { ... } } ... } |
Определить индексатор в интерфейсе | Определите индексатор с ключевым словом get и/или set, например: interface IRawInt { bool this [ int index ] { get; set; } } |
Реализовать интерфейсный индексатор в классе или структуре | Определите в классе или структуре, реализующей интерфейс, индексатор и реализуйте методы доступа к нему, например: struct RawInt : IRawInt { ... public bool this [ int index ] { get { ... } set { ... } } ... } |
Реализовать индексатор, определенный в интерфейсе, путем явной реализации интерфейса в классе или структуре | Укажите индексатор в классе или в структуре, реализующей интерфейс, но не указывайте степень его доступности, например: struct RawInt : IRawInt { ... bool IRawInt.this [ int index ] { get { ... } set { ... } } ... } |