Зачем нужны индексаторы c
Индексаторы позволяют индексировать объекты и обращаться к данным по индексу. Фактически с помощью индексаторов мы можем работать с объектами как с массивами. По форме они напоминают свойства со стандартными блоками get и set , которые возвращают и присваивают значение.
Формальное определение индексатора:
В отличие от свойств индексатор не имеет названия. Вместо него указывается ключевое слово this , после которого в квадратных скобках идут параметры. Индексатор должен иметь как минимум один параметр.
Посмотрим на примере. Допустим, у нас есть класс Person , который представляет человека, и класс Company , который представляет некоторую компанию, где работают люди. Используем индексаторы для определения класса Company:
Для хранения персонала компании в классе определен массив personal , который состоит из объектов Person. Для доступа к этим объектам определен индексатор:
Индексатор в принципе подобен стандартному свойству. Во-первых, для индексатора определяется тип в данном случае тип Person. Тип индексатора определяет, какие объекты будет получать и возвращать индексатор.
Во-вторых, для индексатора определен параметр int index , через который обращаемся к элементам внутри объекта Company.
Для возвращения объекта в индексаторе определен блок get :
Поскольку индексатор имеет тип Person, то в блоке get нам надо возвратить объект этого типа с помощью оператора return. Здесь мы можем определить разнообразную логику. В данном случае просто возвращаем объект из массива personal.
В блоке set , как и в обычном свойстве, получаем через параметр value переданный объект Person и сохраняем его в массив по индексу.
После этого мы можем работать с объектом Company как с набором объектов Person:
Стоит отметить, что если индексатору будет передан некорректный индекс, который отсутствует в массиве person, то мы получим исключение, как и в случае обращения напрямую к элементам массива. В этом случае можно предусмотреть какую-то дополнительную логику. Например, проверять переданный индекс:
Здесь в блоке get если переданный индекс имеется в массиве, то возвращаем объект по индексу. Если индекса нет в массиве, то генерируем исключение. Аналогично в блоке set устанавливаем значение по индексу, если индекс есть в массиве.
Индексы
Индексатор получает набор индексов в виде параметров. Однако индексы необязательно должны представлять тип int, устанавливаемые/возвращаемые значения необязательно хранить в массиве. Например, мы можем рассматривать объект как хранилище атрибутов/свойств и передавать имя атрибута в виде строки:
В данном случае индексатор в классе User в качестве индекса получает строку, которая хранит название атрибута (в данном случае название поля класса).
В блоке get в зависимости от значения строкового индекса возвращается значение того или иного поля класса. Если передано неизвестное название, то генерируется исключение. В блоке set похожая логика — по индексу узнаем, для какого поля надо установить значение.
Применение нескольких параметров
Также индексатор может принимать несколько параметров. Допустим, у нас есть класс, в котором хранилище определено в виде двухмерного массива или матрицы:
Теперь для определения индексатора используются два индекса — i и j. И в программе мы уже должны обращаться к объекту, используя два индекса:
Следует учитывать, что индексатор не может быть статическим и применяется только к экземпляру класса. Но при этом индексаторы могут быть виртуальными и абстрактными и могут переопределяться в произодных классах.
Блоки get и set
Как и в свойствах, в индексаторах можно опускать блок get или set, если в них нет необходимости. Например, удалим блок set и сделаем индексатор доступным только для чтения:
Также мы можем ограничивать доступ к блокам get и set, используя модификаторы доступа. Например, сделаем блок set приватным:
Перегрузка индексаторов
Подобно методам индексаторы можно перегружать. В этом случае также индексаторы должны отличаться по количеству, типу или порядку используемых параметров. Например:
В данном случае класс Company содержит две версии индексатора. Первая версия получает и устанавливает объект Person по индексу, а вторая — только получае объект Person по его имени.
Индексаторы
Вы знаете, что доступ к индивидуальным элементам, содержащимся в стандартных массивах, осуществляется через операцию индекса ([ ]). В языке C# имеется возможность проектировать специальные классы и структуры, которые могут быть индексированы подобно стандартному массиву, посредством определения индексатора (альтернатива массиву). Если вы можете обойтись массивами, забудьте об индексаторах. Важно, что основное назначение индексатора — предоставить пользователю функциональные возможности, аналогичные работе с массивом. Это языковое средство полезно при создании специальных типов коллекций , когда их размер заранее неизвестен.
Если все же вам еще интересно, зачем они нужны, то начнем с определений. Индексаторы могут быть одно- или многомерными.
Одномерные индексаторы
Общая форма одномерного индексатора:
где тип_элемента обозначает конкретный тип элемента индексатора. Следовательно, у каждого элемента, доступного с помощью индексатора, должен быть определенный тип_элемента. Этот тип соответствует типу элемента массива. Параметр индекс получает конкретный индекс элемента, к которому осуществляется доступ. Формально этот параметр совсем не обязательно должен иметь тип int, но чаще всего используется целочисленный тип данного параметра.
В теле индексатора определены два аксессора (средства доступа к данным, см. также статью о свойствах): get и set. Аксессор подобен методу, за исключением того, что в нем не объявляется тип возвращаемого значения или параметры. Аксессоры вызываются автоматически при использовании индексатора, и оба получают индекс в качестве параметра. Так, если индексатор указывается в левой части оператора присваивания, то вызывается аксессор set и устанавливается элемент, на который указывает параметр индекс. В противном случае вызывается аксессор get и возвращается значение, соответствующее параметру индекс. Кроме того, аксессор set получает неявный параметр value, содержащий значение, присваиваемое по указанному индексу.
Давайте рассмотрим пример:
В текущем классе MA определен индексатор а1, позволяющий вызывающему коду идентифицировать элементы (объекты) с применением числовых значений индекса (как в массиве). В статье Массивы найдите пример с объявлением массива объектов, сравните коды и найдите отличия.
На применение индексаторов накладываются два существенных ограничения. Во-первых, значение, выдаваемое индексатором, нельзя передавать методу в качестве параметра ref или out, поскольку в индексаторе не определено место в памяти для его хранения. И во-вторых, индексатор должен быть членом своего класса и поэтому не может быть объявлен как static.
Многомерные индексаторы
Пример индексатора, принимающего несколько параметров:

После инициализации индексатора далее с ним можем работать как с двухмерным массивом целых чисел. Небольшой плюс индексатора: размерности (число строк и столбцов) являются членами класса. Замените 2 строку в методе Main( ) на:
т.е. объявим массив a2. Тогда в задании верхних границ индексов будет ошибки типа:
«System.Array» не содержит определения для «r» и не был найден метод расширения «r», принимающий тип «System.Array» в качестве первого аргумента.
Впрочем, если объявите класс МА3:
и внесете изменения в вызов элементов массива (выделено красным):
то вы получите практически тоже, что и с индексатором. Плюс только в том, что вы обращаетесь к элементу индексатора a2[i, j], а к элементу массива через поле ar: a2.ar[i, j].
Примечания.
- Современные языки программирования предлагают вам различные варианты реализации тех или иных классов, выбор наиболее удобного – это ваш выбор.
- Объявление индексатора очень похоже на объявление свойств с аксессорами set и get. Поэтому понятно, почему свойства и индексаторы относятся к член-функциям класса.
NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.
Name already in use
csharp_for_beginners / c10.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
1 contributor
Users who have contributed to this file
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
ГЛАВА 10. Индексаторы и свойства
В этой главе рассматриваются две особые и тесно свя занные друг с другом разновидности членов класса: индексаторы и свойства. Каждый из них по-своему расширяет возможности класса, способствуя более полной его интеграции в систему типов С# и повышая его гибкость. В частности, индексаторы предоставляют механизм для ин дексирования объектов подобно массивам, а свойства — ра циональный способ управления доступом к данным экзем пляра класса. Эти члены класса тесно связаны друг с дру гом, поскольку оба опираются на еще одно доступное в C# средство: аксессор.
Как вам должно быть уже известно, индексирование массива осуществляется с помощью оператора []. Для создаваемых классов можно определить оператор [], но с этой целью вместо операторного метода создается индексатор, который позволяет индексировать объект, по добно массиву. Индексаторы применяются, главным об разом, в качестве средства, поддерживающего создание специализированных массивов, на которые накладывается одно или несколько ограничений. Тем не менее индексато ры могут служить практически любым целям, для которых выгодным оказывается такой же синтаксис, как и у масси вов. Индексаторы могут быть одно- или многомерными. Рассмотрим сначала одномерные индексаторы.
Создание одномерных индексаторов
Ниже приведена общая форма одномерного индексатора:
где тип_элемента обозначает конкретный тип элемента индексатора. Следовательно, у каждого элемента, доступного с помощью индексатора, должен быть определенный тип_элемента. Этот тип соответствует типу элемента массива. Параметр индекс по лучает конкретный индекс элемента, к которому осуществляется доступ. Формально этот параметр совсем не обязательно должен иметь тип int, но поскольку индексато ры, как правило, применяются для индексирования массивов, то чаще всего использу ется целочисленный тип данного параметра.
В теле индексатора определены два аксессора (т.е. средства доступа к данным): get и set. Аксессор подобен методу, за исключением того, что в нем не объявляется тип возвращаемого значения или параметры. Аксессоры вызываются автоматически при использовании индексатора, и оба получают индекс в качестве параметра. Так, если индексатор указывается в левой части оператора присваивания, то вызывается аксессор set и устанавливается элемент, на который указывает параметр индекс. В противном случае вызывается аксессор get и возвращается значение, соответствующее параметру индекс. Кроме того, аксессор set получает неявный параметр value, содержащий значение, присваиваемое по указанному индексу.
Преимущество индексатора заключается, в частности, в том, что он позволяет пол ностью управлять доступом к массиву, избегая нежелательного доступа. В качестве примера рассмотрим программу, в которой создается класс FailSoftArray, реали зующий массив для выявления ошибок нарушения границ массива, а следовательно, для предотвращения исключительных ситуаций, возникающих во время выполнения в связи с индексированием массива за его границами. Для этого массив инкапсулиру ется в качестве закрытого члена класса, а доступ к нему осуществляется только с помо щью индексатора. При таком подходе исключается любая попытка получить доступ к массиву за его границами, причем эта попытка пресекается без катастрофических последствий для программы. А поскольку в классе FailSoftArray используется ин дексатор, то к массиву можно обращаться с помощью обычной формы записи.
Вот к какому результату приводит выполнение этой программы.
Индексатор препятствует нарушению границ массива. Внимательно проанализи руем каждую часть кода индексатора. Он начинается со следующей строки.
В этой строке кода объявляется индексатор, оперирующий элементами типа int. Ему передается индекс в качестве параметра index. Кроме того, индексатор объявля ется открытым (public), что дает возможность использовать этот индексатор в коде за пределами его класса.
Рассмотрим следующий код аксессора get.
Аксессор get предотвращает ошибки нарушения границ массива, проверяя в пер вую очередь, находится ли индекс в установленных границах. Эта проверка границ вы полняется в методе ok(), который возвращает логическое значение true, если индекс правильный, а иначе — логическое значение false. Так, если указанный индекс на ходится б установленных границах, то по этому индексу возвращается соответствую щий элемент. А если индекс оказывается вне установленных границ, то никаких опе раций не выполняется, но в то же время не возникает никаких ошибок переполнения. В данном варианте класса FailSoftArray переменная ErrFlag содержит результат каждой операции. Ее содержимое может быть проверено после каждой операции на предмет удачного или неудачного выполнения последней. (В главе 13 будет представ лен более совершенный способ обработки ошибок с помощью имеющейся в C# под системы обработки исключительных ситуаций, а до тех пор можно вполне обойтись установкой и проверкой признака ошибки.)
А теперь рассмотрим следующий код аксессора set, предотвращающего ошибки нарушения границ массива.
Если параметр index метода ok() находится в установленных пределах, то соот ветствующему элементу массива присваивается значение, передаваемое из параметра value. В противном случае устанавливается логическое значение true переменной ErrFlag. Напомним, что value в любом аксессорном методе является неявным пара метром, содержащим присваиваемое значение. Его не нужно (да и нельзя) объявлять отдельно.
Наличие обоих аксессоров, get и set, в индексаторе не является обязательным. Так, можно создать индексатор только для чтения, реализовав в нем один лишь аксес сор get, или же индексатор только для записи с единственным аксессором set.
Индексатор может быть перегружен. В этом случае для выполнения выбирается тот вариант индексатора, в котором точнее соблюдается соответствие его параметра и аргумента, указываемого в качестве индекса. Ниже приведен пример программы, в ко торой индексатор массива класса FailSoftArray перегружается для индексов типа double. При этом индексатор типа double округляет свой индекс до ближайшего целого значения.
При выполнении этой программы получается следующий результат.
Как показывает приведенный выше результат, индексы типа double округляются до ближайшего целого значения. В частности, индекс 1.1 округляется до 1, а индекс 1.6 — до 2.
Представленный выше пример программы наглядно демонстрирует правомоч ность перегрузки индексаторов, но на практике она применяется нечасто. Как прави ло, индексаторы перегружаются для того, чтобы использовать объект определенного класса в качестве индекса, вычисляемого каким-то особым образом.
Индексаторы без базового массива
Следует особо подчеркнуть, что индексатор совсем не обязательно должен опери ровать массивом. Его основное назначение — предоставить пользователю функцио нальные возможности, аналогичные массиву. В качестве примера в приведенной ниже программе демонстрируется индексатор, выполняющий роль массива только для чте ния, содержащего степени числа 2 от 0 до 15. Обратите внимание на то, что в этой программе отсутствует конкретный массив. Вместо этого индексатор просто вычисляет подходящее значение для заданного индекса.
Вот к какому результату приводит выполнение этой программы.
Обратите внимание на то, что в индексатор класса PwrOfTwo включен только аксес сор get, но в нем отсутствует аксессор set. Как пояснялось выше, такой индексатор служит только для чтения. Следовательно, объект класса PwrOfTwo может указываться только в правой части оператора присваивания, но не в левой его части. Например, попытка ввести следующую строку кода в приведенную выше программу не приведет к желаемому результату.
Такой оператор присваивания станет причиной появления ошибки во время ком пиляции, поскольку для индексатора не определен аксессор set.
На применение индексаторов накладываются два существенных ограничения. Во-первых, значение, выдаваемое индексатором, нельзя передавать методу в качестве параметра ref или out, поскольку в индексаторе не определено место в памяти для его хранения. И во-вторых, индексатор должен быть членом своего класса и поэтому не может быть объявлен как static.
Индексаторы можно создавать и для многомерных массивов. В качестве примера ниже приведен двумерный отказоустойчивый массив. Обратите особое внимание на объявление индексатора в этом примере.
Вот к какому результату приводит выполнение этого кода:
Еще одной разновидностью члена класса является свойство. Как правило, свойство сочетает в себе поле с методами доступа к нему. Как было показано в приведенных ранее примерах программ, поле зачастую создается, чтобы стать доступным для поль зователей объекта, но при этом желательно сохранить управление над операциями, разрешенными для этого поля, например, ограничить диапазон значений, присваи ваемых данному полю. Этой цели можно, конечно, добиться и с помощью закрытой переменной, а также методов доступа к ее значению, но свойство предоставляет более совершенный и рациональный путь для достижения той же самой цели.
Свойства очень похожи на индексаторы. В частности, свойство состоит из имени и аксессоров get и set. Аксессоры служат для получения и установки значения пере менной. Главное преимущество свойства заключается в том, что его имя может быть использовано в выражениях и операторах присваивания аналогично имени обычной переменной, но в действительности при обращении к свойству по имени автоматиче ски вызываются его аксессоры get и set. Аналогичным образом используются аксес соры get и set индексатора.
Ниже приведена общая форма свойства:
где тип обозначает конкретный тип свойства, например int, а имя — присваиваемое свойству имя. Как только свойство будет определено, любое обращение к свойству по имени приведет к автоматическому вызову соответствующего аксессора. Кроме того, аксессор set принимает неявный параметр value, который содержит значение, при сваиваемое свойству.
Следует, однако, иметь в виду, что свойства не определяют место в памяти для хра нения полей, а лишь управляют доступом к полям. Это означает, что само свойство не предоставляет поле, и поэтому поле должно быть определено независимо от свойства. (Исключение из этого правила составляет автоматически реализуемое свойство, рассма триваемое далее.)
Ниже приведен простой пример программы, в которой определяется свойство MyProp, предназначенное для доступа к полю prop. В данном примере свойство до пускает присваивание только положительных значений.
Вот к какому результату приводит выполнение этого кода.
Рассмотрим приведенный выше код более подробно. В этом коде определяется одно закрытое поле prop и свойство MyProp, управляющее доступом к полю prop. Как пояснялось выше, само свойство не определяет место в памяти для хранения поля, а только управляет доступом к полю. Кроме того, поле prop является закрытым, а зна чит, оно доступно только через свойство MyProp.
Свойство MyProp указано как public, а следовательно, оно доступно из кода за пределами его класса. И в этом есть своя логика, поскольку данное свойство обеспе чивает доступ к полю prop, которое является закрытым. Аксессор get этого свойства просто возвращает значение из поля prop, тогда как аксессор set устанавливает зна чение в поле prop в том и только в том случае, если это значение оказывается поло жительным. Таким образом, свойство MyProp контролирует значения, которые могут храниться в поле prop. В этом, собственно, и состоит основное назначение свойств.
Тип свойства MyProp определяется как для чтения, так и для записи, поскольку оно позволяет читать и записывать данные в базовое поле. Тем не менее свойства можно создавать доступными только для чтения или только для записи. Так, если требуется создать свойство, доступное только для чтения, то достаточно определить единствен ный аксессор get. А если нужно создать свойство, доступное только для записи, то достаточно определить единственный аксессор set.
Воспользуемся свойством для дальнейшего усовершенствования отказоустойчивого массива. Как вам должно быть уже известно, у всех массивов имеется соответствую щее свойство длины (Length). До сих пор в классе FailSoftArray для этой цели ис пользовалось открытое целочисленное поле Length. Но это далеко не самый лучший подход, поскольку он допускает установку значений, отличающихся от длины отказоу стойчивого массива. (Например, программист, преследующий злонамеренные цели, может умышленно ввести неверное значение в данном поле.) Для того чтобы испра вить это положение, превратим поле Length в свойство «только для чтения», как по казано в приведенном ниже, измененном варианте класса FailSoftArray.
Теперь Length — это свойство, в котором местом для хранения данных служит закрытая переменная len. А поскольку в этом свойстве определен единственный ак- ceccop get, то оно доступно только для чтения. Это означает, что значение свойства Length можно только читать, но не изменять. Для того чтобы убедиться в этом, по пробуйте удалить символы комментария в начале следующей строки из приведенного выше кода.
При попытке скомпилировать данный код вы получите сообщение об ошибке, уведомляющее о том, что Length является свойством, доступным только для чтения.
Добавлением свойства Length в класс FailSoftArray усовершенствование рас сматриваемого здесь примера кода с помощью свойств далеко не исчерпывается. Еще одним членом данного класса, подходящим для превращения в свойство, служит пере менная ErrFlag, поскольку ее применение должно быть ограничено только чтением. Ниже приведен окончательно усовершенствованный вариант класса FailSoftArray, в котором создается свойство Error, использующее в качестве места для хранения дан ных исходную переменную ErrFlag, ставшую теперь закрытой.
Создание свойства Error стало причиной двух следующих изменений в классе FailSoftArray. Во-первых, переменная ErrFlag была сделана закрытой, посколь ку теперь она служит базовым местом хранения данных для свойства Error, а следо вательно, она не должна быть доступна непосредственно. И во-вторых, было введено свойство Error «только для чтения». Теперь свойство Error будет опрашиваться в тех программах, где требуется организовать обнаружение ошибок. Именно это и было продемонстрировано выше в методе Main(), где намеренно сгенерирована ошибка нарушения границ массива, а для ее обнаружения использовано свойство Error.
Автоматически реализуемые свойства
Начиная с версии C# 3.0, появилась возможность для реализации очень простых свойств, не прибегая к явному определению переменной, которой управляет свойство. Вместо этого базовую переменную для свойства автоматически предоставляет компи лятор. Такое свойство называется автоматически реализуемым и принимает следую щую общую форму:
где тип обозначает конкретный тип свойства, а имя — присваиваемое свойству имя. Обратите внимание на то, что после обозначений аксессоров get и set сразу же сле дует точка с запятой, а тело у них отсутствует. Такой синтаксис предписывает компиля тору создать автоматически переменную, иногда еще называемую поддерживающим по лем, для хранения значения. Такая переменная недоступна непосредственно и не имеет имени. Но в то же время она может быть доступна через свойство.
Ниже приведен пример объявления свойства, автоматически реализуемого под именем UserCount.
Как видите, в этой строке кода переменная явно не объявляется. И как пояснялось выше, компилятор автоматически создает анонимное поле, в котором хранится зна чение. А в остальном автоматически реализуемое свойство UserCount подобно всем остальным свойствам.
Но в отличие от обычных свойств автоматически реализуемое свойство не может быть доступным только для чтения или только для записи. При объявлении этого свой ства в любом случае необходимо указывать оба аксессора — get и set. Хотя добиться желаемого (т.е. сделать автоматически реализуемое свойство доступным только для чтения или только для записи) все же можно, объявив ненужный аксессор как private (подробнее об этом — в разделе «Применение модификаторов доступа в аксессорах»).
Несмотря на очевидные удобства автоматически реализуемых свойств, их примене ние ограничивается в основном теми ситуациями, в которых не требуется управление установкой или получением значений из поддерживающих полей. Напомним, что поддерживающее поле недоступно напрямую. Это означает, что на значение, которое может иметь автоматически реализуемое свойство, нельзя наложить никаких ограни чений. Следовательно, имена автоматически реализуемых свойств просто заменяют собой имена самих полей, а зачастую именно это и требуется в программе. Автомати чески реализуемые свойства могут оказаться полезными и в тех случаях, когда с помо щью свойств функциональные возможности программы открываются для сторонних пользователей, и для этой цели могут даже применяться специальные средства про ектирования.
Применение инициализаторов объектов в свойствах
Как пояснялось в главе 8, инициализатор объекта применяется в качестве альтерна тивы явному вызову конструктора при создании объекта. С помощью инициализато ров объектов задаются начальные значения полей или свойств, которые требуется ини циализировать. При этом синтаксис инициализаторов объектов оказывается одинако вым как для свойств, так и для полей. В качестве примера ниже приведена программа из главы 8, измененная с целью продемонстрировать применение инициализаторов объектов в свойствах. Напомним, что в версии этой программы из главы 8 использо вались поля, а приведенная ниже версия отличается лишь тем, что в ней поля Count и Str превращены в свойства. В то же время синтаксис инициализаторов объектов не изменился.
Как видите, свойства Count и Str устанавливаются в выражениях с инициализато ром объекта. Приведенная выше программа дает такой же результат, как и программа из главы 8, а именно:
Как пояснялось в главе 8, синтаксис инициализатора объекта оказывается наиболее пригодным для работы с анонимными типами, формируемыми в LINQ-выражениях. А в остальных случаях чаще всего используется синтаксис обычных конструкторов.
Ограничения, присущие свойствам
Свойствам присущ ряд существенных ограничений. Во-первых, свойство не опреде ляет место для хранения данных, и поэтому не может быть передано методу в качестве параметра ref или out. Во-вторых, свойство не подлежит перегрузке. Наличие двух разных свойств с доступом к одной и той же переменной допускается, но это, скорее, исключение, чем правило. И наконец, свойство не должно изменять состояние базо вой переменной при вызове аксессора get. И хотя это ограничительное правило не соблюдается компилятором, его нарушение считается семантической ошибкой. Дей ствие аксессора get не должно носить характер вмешательства в функционирование переменной.
Применение модификаторов доступа в аксессорах
По умолчанию доступность аксессоров set и get оказывается такой же, как и у индексатора и свойства, частью которых они являются. Так, если свойство объявляется как public, то по умолчанию его аксессоры set и get также становятся открытыми (public). Тем не менее для аксессора set или get можно указать собственный мо дификатор доступа, например private. Но в любом случае доступность аксессора, определяемая таким модификатором, должна быть более ограниченной, чем доступ ность, указываемая для его свойства или индексатора.
Существует целый ряд причин, по которым требуется ограничить доступность ак сессора. Допустим, что требуется предоставить свободный доступ к значению свойства, но вместе с тем дать возможность устанавливать это свойство только членам его класса. Для этого достаточно объявить аксессор данного свойства как private. В приведен ном ниже примере используется свойство MyProp, аксессор set которого указан как private.
В классе PropAccess аксессор set указан как private. Это означает, что он досту пен только другим членам данного класса, например методу IncrProp(), но недосту пен для кода за пределами класса PropAccess. Именно поэтому попытка Присвоить свойству ob.МуРrор значение в классе PropAccessDemo закомментирована.
Вероятно, ограничение доступа к аксессорам оказывается наиболее важным для работы с автоматически реализуемыми свойствами. Как пояснялось выше, создать автоматически реализуемое свойство только для чтения или же только для записи нельзя, поскольку оба аксессора, get и set, должны быть указаны при объявлении такого свойства. Тем не менее добиться желаемого результата все же можно, объявив один из аксессоров автоматически реализуемого свойства как private. В качестве примера ниже приведено объявление автоматически реализуемого свойства Length для класса FailSoftArray, которое фактически становится доступным только для чтения.
Свойство Length может быть установлено только из кода в его классе, поскольку его аксессор set объявлен как private. А изменять свойство Length за пределами его класса не разрешается. Это означает, что за пределами своего класса свойство, по существу, оказывается доступным только для чтения. Аналогичным образом можно объявить и свойство Error, как показано ниже.
Благодаря этому свойство Error становится доступным для чтения, но не для уста новки за пределами класса FailSoftArray.
Для опробования автоматически реализуемых вариантов свойств Length и Error в классе FailSoftArray удалим сначала переменные len и ErrFlag, поскольку они больше не нужны, а затем заменим каждое применение переменных len и ErrFlag свойствами Length и Error в классе FailSoftArray. Ниже приведен обновленный вариант класса FailSoftArray вместе с методом Main(), демонстрирующим его применение.
Этот вариант класса FailSoftArray действует таким же образом, как и предыду щий, но в нем отсутствуют поддерживающие поля, объявляемые явно.
На применение модификаторов доступа в аксессорах накладываются следующие ограничения. Во-первых, действию модификатора доступа подлежит только один ак сессор: set или get, но не оба сразу. Во-вторых, модификатор должен обеспечивать более ограниченный доступ к аксессору, чем доступ на уровне свойства или индексато ра. И наконец, модификатор доступа нельзя использовать при объявлении аксессора в интерфейсе или же при реализации аксессора, указываемого в интерфейсе. (Подроб нее об интерфейсах речь пойдет в главе 12.)
Применение индексаторов и свойств
В предыдущих примерах программ был продемонстрирован основной принцип действия индексаторов и свойств, но их возможности не были раскрыты в полную силу. Поэтому в завершение этой главы обратимся к примеру класса RangeArray, в котором индексаторы и свойства используются для создания типа массива с преде лами индексирования, определяемыми пользователем.
Как вам должно быть уже известно, индексирование всех массивов в C# начинается с нуля. Но в некоторых приложениях индексирование массива удобнее начинать с лю бой произвольной точки отсчета: с 1 или даже с отрицательного числа, например от -5 и до 5. Рассматриваемый здесь класс RangeArray разработан таким образом, чтобы допускать подобного рода индексирование массивов.
Используя класс RangeArray, можно написать следующий фрагмент кода.
Нетрудно догадаться, что в первой строке этого кода конструируется объект клас са RangeArray с пределами индексирования массива от -5 до 10 включительно. Пер вый аргумент обозначает начальный индекс, а второй — конечный индекс. Как только объект rа будет сконструирован, он может быть проиндексирован как массив в преде лах от -5 до 10.
Ниже приведен полностью класс RangeArray вместе с классом RangeArrayDemo, в котором демонстрируется индексирование массива в заданных пределах. Класс RangeArray реализован таким образом, чтобы поддерживать массивы типа int, но при желании вы можете изменить этот тип на любой другой.
При выполнении этого кода получается следующий результат.
Как следует из результата выполнения приведенного выше кода, объекты типа RangeArray можно индексировать в качестве массивов, начиная с любой точ ки отсчета, а не только с нуля. Рассмотрим подробнее саму реализацию класса RangeArray.
В начале класса RangeArray объявляются следующие закрытые переменные эк земпляра.
Переменная а служит для обращения к базовому массиву по ссылке. Память для него распределяется конструктором класса RangeArray. Нижняя граница индексиро вания массива хранится в переменной lowerBound, а верхняя граница — в перемен ной upperBound.
// Автоматически реализуемое и доступное только для чтения свойство Length. public int Length < get; private set; >// Автоматически реализуемое и доступное только для чтения свойство Error. public bool Error
// Построить массив по заданному размеру. public RangeArray(int low, int high) < high++; if(high <= low) < Console.WriteLine("Неверные индексы"); high = 1; // создать для надежности минимально допустимый массив low = 0; >а = new int[high — low]; Length = high — low; lowerBound = low; upperBound = —high; >
// Возвратить логическое значение true, если // индекс находится в установленных границах. private bool ok(int index)
Индексаторы. Одномерные и многомерные индексаторы. Индексаторы без базового массива. Перегрузка индексаторов
Индексатор – это средство языка C#, позволяющее индексировать объект так как массив с помощью прямоугольных скобок [ ] . С помощью индексаторов можно реализовывать собственные специализированные массивы, на которые могут накладываться различные ограничения.
Чтобы использовать индексаторы в программах нужно объявить класс, содержащий индексатор. Класс может иметь внутренний массив, к элементам которого осуществляется доступ с помощью индексатора. После объявления класса с индексатором можно использовать объект этого класса как массив (с помощью прямоугольных скобок [ ] ). Чтобы использовать индексатор, он должен быть объявлен как public.
Так как и массивы, индексаторы могут быть одномерные и многомерные.
Общая форма объявления индексатора в некотором классе:
- type – тип элемента индексатора;
- index – индекс или позиция элемента в массиве по которому осуществляется доступ к элементу;
- type_index – тип индекса (позиции) индексатора. Как правило это тип int . Однако, допускается наличие других типов (например double , char );
- this – ссылка на данный класс, в котором реализуется индексатор;
- get , set – аксессоры соответственно для чтения ( get ) значения элемента и записи ( set ) значения в элемент.
⇑
2. Пример объявления одномерного индексатора, который возвращает значение типа char
Одномерный индексатор соответствует одномерному массиву. В примере объявляется класс CharArray , содержащий индексатор.
В вышеприведенном коде объявляется индексатор, возвращающий значение элементов типа char . Значения индексов имеют тип int .
В индексаторе реализуются два аксесора: get , set . В теле реализации аксессоров осуществляется проверка на выход индекса за пределы допустимого диапазона [0 .. Size-1] .
Демонстрация использования класса в другом программном коде
⇑
3. Пример объявления одномерного индексатора, возвращающего значение типа int
В примере реализуется класс, который содержит одномерный индексатор, который возвращает значение типа int . Тип индексов в индексаторе также int .
Использование класса IntArray в другом программном коде
⇑
4. Пример объявления одномерного индексатора в котором реализован индекс типа char
Тип индекса в индексаторе не обязательно должен быть целочисленным. Можно объявлять и другой тип индекса, например double , char . Но в этом случае при использовании внутреннего массива нужно привести тип индекса из другого типа ( double , char , …) к типу int .
Следующий пример демонстрирует применение индексатора в классе, в котором значение индекса есть числом типа char . Данный пример реализует подсчет количества символов латинского алфавита ‘A’ … ‘Z’ в заданной строке.
Как видно из вышеприведенного кода, в аксессорах get и set , индекс массива Array приводится к типу int . Однако, из программного кода, к объекту класса CharIndex можно доступиться по индексу типа char .
Использование класса в другом программном коде
⇑
5. Пример объявления двумерного индексатора
В примере объявляется двумерный индексатор, который реализует двумерный массив чисел типа double .
Использование класса TwoDimIndexes в некотором программном коде
⇑
6. Пример объявления трехмерного индексатора
Объявляется класс, который содержит трехмерный индексатор
Использование класса в другом программном коде
⇑
7. Какие особенности использования индексаторов без базового массива
В классе не обязательно объявлять внутреннюю переменную-массив для того, чтобы использовать индексатор. Конечно, данные можно сохранять и во внутренней переменной (массиве). Однако, бывают случаи, когда значение элементов массива формируются программно, то есть поддаются некоторой закономерности или формуле. В этих случаях не обязательно объявлять внутренний массив, для сохранения которого нужна дополнительная память.
⇑
8. Пример объявления класса, который содержит индексатор, но не содержит базового массива. Вычисление значения n-го числа Фибоначчи
В классе CFibonacci объявляется индексатор, который формирует число Фибоначчи на основе его позиции в ряде. Для определения числа, в классе реализован скрытый ( private ) дополнительный метод GetNumber() . Класс не содержит конструктора. Индексатор в классе содержит только один аксессор get .
Реализация класса имеет следующий вид
Использование класса CFibonacci в другом программном коде
⇑
9. Что называется перегрузкой индексаторов?
Перегрузка индексатора – это реализация в классе нескольких индексаторов, которые имеют разные типы индекса (например int , char , double ).
В общем виде реализация такого класса выглядит следующим чином:
- ClassName – имя класса, в котором объявляется перегруженный индексатор;
- return_type – тип элемента индексатора. Этот тип есть общим для всех перегруженных индексаторов;
- type1 , type2 , …, typeN – типы индексов с именем index .
⇑
10. Пример объявления класса, в котором используются перегруженные индексаторы
В классе CDoubleArray демонстрируется использование перегруженного индексатора