Опубликован: 15.09.2010 | Доступ: свободный | Студентов: 4808 / 630 | Оценка: 3.97 / 3.80 | Длительность: 14:45:00
Лекция 7:

Классы: подробности

< Лекция 6 || Лекция 7: 123 || Лекция 8 >

Индексаторы

Индексатор представляет собой разновидность свойства. Если у класса есть скрытое поле, представляющее собой массив, то с помощью индексатора можно обратиться к элементу этого массива, используя имя объекта и номер элемента массива в квадратных скобках. Иными словами, индексатор — это такой "умный" индекс для объектов.

Синтаксис индексатора аналогичен синтаксису свойства:

атрибуты спецификаторы тип this [ список_параметров ]
{
    get код_доступа
    set код_доступа
}

Индексаторы чаще всего объявляются со спецификатором public, поскольку они входят в интерфейс объекта. Атрибуты и спецификаторы могут отсутствовать.

Код доступа представляет собой блоки операторов, которые выполняются при получении ( get ) или установке значения ( set ) элемента массива. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, индексатор доступен только для чтения (read-only), если отсутствует часть get, индексатор доступен только для записи (write-only).

Список параметров содержит одно или несколько описаний индексов, по которым выполняется доступ к элементу. Чаще всего используется один индекс целого типа.

Индексаторы в основном применяются для создания специализированных массивов, на работу с которыми накладываются какие-либо ограничения. Примеры работы с индексаторами приведены в учебнике.

Язык C# допускает использование многомерных индексаторов. Они описываются аналогично обычным и применяются в основном для контроля за занесением данных в многомерные массивы и выборке данных из многомерных массивов, оформленных в виде классов.

Операции класса

Класс — это полноценный тип данных, поэтому программист может определить для него собственные операции. Вы уже сталкивались с перегрузкой (т.е. переопределением) операций: например, знак " + " для арифметических типов означает сложение, а для строк — склеивание. Операции перегружают в основном для классов, с помощью которых задают какие-либо математические понятия, т.е. тогда, когда знаки операции имеют общепринятую семантику. Можно задать операцию сложения для класса Monster, описанного в листинге 5.8, но будет ли понятен ее смысл пользователям этого класса? Примеры перегрузки приведены именно для этого класса только для того, чтобы не тратить время на изучение нового класса.

Унарные операции

Можно определять в классе следующие унарные операции:

+     -     !     ~     ++     --     true     false

Синтаксис объявителя унарной операции:

тип operator унарная_операция ( параметр )

Примеры заголовков унарных операций:

public static int operator ++( MyObject m )
public static MyObject operator --( MyObject m )
public static bool operator true( MyObject m )

Параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. Операция должна возвращать:

  • для операций +, -, ! и ~ величину любого типа;
  • для операций ++ и -- величину типа класса, для которого она определяется;
  • для операций true и false величину типа bool.

Операции не должны изменять значение передаваемого им операнда. Операция, возвращающая величину типа класса, для которого она определяется, должна создать новый объект этого класса, выполнить с ним необходимые действия и передать его в качестве результата.

Примечание

Префиксный и постфиксный инкремент не различаются (для них может существовать только одна реализация, которая вызывается в обоих случаях).

Пример перегрузки операции инкремента (примем, что увеличение монстра на единицу должно увеличивать его здоровье):

class Monster {
  public static Monster operator ++( Monster m )
  { 
     Monster temp = new Monster();
     temp.health = m.health + 1;
     return temp;
  }
  …
}

 …
Monster vasia = new Monster();
++vasia; vasia++;

Бинарные операции

Можно определять в классе следующие бинарные операции:

+   -   *   /   %   &   |   ^   <<   >>   ==   !=   >   <   >=   <=

Синтаксис объявителя бинарной операции:

тип operator бинарная_операция (параметр1, параметр2)

Примеры заголовков бинарных операций:

public static MyObject operator +  ( MyObject m1, MyObject m2 )
public static bool     operator == ( MyObject m1, MyObject m2 )

Хотя бы один параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. Операция может возвращать величину любого типа.

Операции == и !=, > и <, >= и <= определяются только парами и обычно возвращают логическое значение. Чаще всего в классе определяют операции сравнения на равенство и неравенство для того, чтобы обеспечить сравнение объектов, а не их ссылок, как определено по умолчанию для ссылочных типов.

Сложные операции присваивания (например, += ) определять не требуется, да это и невозможно. При выполнении такой операции автоматически вызываются сначала операция сложения, а потом присваивания.

Пример описания бинарных операций для класса Monster (сложение с константой):

class Monster {
  public static Monster operator +( Monster m, int k )
  { Monster temp = new Monster();
     temp.ammo = m.ammo + k;
     return temp;
  }
 public static Monster operator +( int k, Monster m )
  { Monster temp = new Monster();
     temp.ammo = m.ammo + k;
     return temp;
  }
  …
}
 …
Monster vasia = new Monster();
Monster masha = vasia + 10;
Monster petya = 5 + masha;
…

Операции преобразования типа

Операции преобразования типа обеспечивают возможность явного и неявного преобразования между пользовательскими типами данных. Синтаксис объявителя операции преобразования типа:

implicit operator тип ( параметр )       // неявное преобразование
explicit operator тип ( параметр )       // явное преобразование

Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого определяется операция. Таким образом, операции выполняют преобразование либо типа класса к другому типу, либо наоборот. Преобразуемые типы не должны быть связаны отношениями наследования. Примеры операций преобразования типа для класса Monster, описанного ранее:

public static implicit operator int( Monster m )
{  
    return m.health;  
}  
public static explicit operator Monster( int h )
{  
    return new Monster( h, 100, "FromInt" );  
}

Ниже приведены примеры использования этих преобразований в программе. Не надо искать в них смысл, они просто иллюстрируют синтаксис:

Monster Masha = new Monster( 200, 200, "Masha" );
int i = Masha;                      // неявное преобразование
Masha = (Monster) 500;              // явное преобразование

Неявное преобразование выполняется автоматически:

  • при присваивании объекта переменной целевого типа, как в примере;
  • при использовании объекта в выражении, содержащем переменные целевого типа;
  • при передаче объекта в метод на место параметра целевого типа;
  • при явном приведении типа.

Явное преобразование выполняется при использовании операции приведения типа.

Все операции класса должны иметь разные сигнатуры. В отличие от других видов методов, для операций преобразования тип возвращаемого значения включается в сигнатуру, иначе нельзя было бы определять варианты преобразования данного типа в несколько других. Ключевые слова implicit и explicit в сигнатуру не включаются, следовательно, для одного и того же преобразования нельзя определить одновременно явную и неявную версию.

Неявное преобразование следует определять так, чтобы при его выполнении не возникала потеря точности и не генерировались исключения. Если эти ситуации возможны, преобразование следует описать как явное.

< Лекция 6 || Лекция 7: 123 || Лекция 8 >
Артур Гибадуллин
Артур Гибадуллин
Россия, г. Нижневартовск