Скачиваний:
59
Добавлен:
24.03.2015
Размер:
224.77 Кб
Скачать

12.3. Автореализуемые свойства

В С# 3.0 (Visual C# 2008 и выше) включено новое средство, позволяющее частично автоматизировать кодирование классов одного достаточного ограниченного вида. Эту возможность обеспечивают свойства, специфицированные как автоматически реализуемые (Automatically implemented properties). Покажем это на примере.

Пример кода, который готовит программист [1]:

public class Point

{

public int X { get; set; }

public int Y { get; set; }

}

В классе Point нет явно определённых полей, а два открытых свойства представляют собой "полуфабрикаты" автореализуемых свойств. В автореализуемых свойствах отсутствуют тела аксессоров, но каждый из аксессоров может иметь модификаторы доступа. На основе такой декларации компилятор автоматически формирует эквивалентное ей объявление класса:

public class Point

{

private int x; private int y;

public int X { get { return x; } set { x = value; } }

public int Y { get { return y; } set { y = value; } }

}

В автоматически построенном объявлении появились два закрытых поля, типы которых совпадают с типами свойств. Поле, отнесённое к автореализуемому свойству, называют hidden backing field - скрытым полем заднего плана. Именно с этими полями ассоциированы свойства X и Y, которые были "запланированы" программистом. Имена скрытых полей компилятор формирует по правилам, которые известны только ему. В нашем примере х и у - это только условные обозначения, выбранные нами для иллюстрации. Никакие прямые обращения к этим скрытым полям невозможны в классе ни за его пределами. Доступ к этим полям обеспечивают только ассоциированные с ними автореализуемые свойства.

В приведённом примере скрытые поля класса Point доступны с помощью свойств X и Y как для чтения так и для изменений. При объявлении автореализуемого свойства можно ограничить доступ к связанному с ним полю, например, разрешив извне класса только чтение. Сразу возникает вопрос. Как задать значение такого поля, если имя его недоступно и неизвестно (известно только компилятору), а автореализуемое свойство разрешает только чтение? Наиболее прямое решение - явно объявить в классе с автореализуемыми полями конструктор и в нём через закрытое свойство устанавливать значение скрытого поля. Следующая программа иллюстрирует предлагаемые решение (только Visual C# 2008 и выше):

// 12_03.cs - автореализуемые свойства

public class Point

{

public int X { get; private set; }

public int Y { get; private set; }

public Point(int xi, int yi)

{

X = xi; Y = yi;

}

}

public static void Main()

{

Point a = new Point(12, 7);

Console.WriteLine(a.Y);

}

Результат выполнения программы:

7

12.4. Индексаторы

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

ссылка_на_объект [индексное_выражение]

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

Индексатор можно считать разновидностью свойства. Как и для свойства возможности индексатора определяются аксессорами get и set. В отличие от свойства у индексатора нет собственного уникального имени. При объявлении индексатор всегда именуется служебным словом this, то есть ссылкой на тот конкретный объект, для которого используется индексатор. Тип возвращаемого значения для индексатора соответствует типу элементов коллекции, с которой ассоциирован индексатор.

Объявление индексатора имеет следующий формат:

модификаторы_индексатораоpt

тип this [спецификация параметров] {

декларация get-aкceccopaopt

декларация set-aкceccopaopt }

Модификаторы_индексатора те же, что и для свойств за одним исключением - для индексаторов нельзя использовать модификатор static. Индексаторы не могут быть статическими и применимы только к объектам класса (не к классу в целом). Вслед за ключевым словом this в квадратных скобках - спецификация параметров индексатора. Разрешена перегрузка индексаторов, то есть в одном классе может быть несколько индексаторов и они должны отличаться друг от друга спецификациями параметров. В этом индексаторы подобны методам. За квадратными скобками размещается код, который можно назвать телом индексатора. Это конструкция, подобная телусвойства, - заключённые в фигурные скобки объявления аксессоров get и set.

Если у индексатора только один параметр, формат определения индексатора можно изобразить так:

модификаторыopt тип_результата this [тип_индекса индекс]

{ get { операторы get_aксeccopa }

set { операторы set_aкceccopa } }

Операторы get_aкceccopa предназначены для получения значения элемента, соответствующего значению индекса. Операторы set_aкceccopa выполняют присваивания значения элементу, соответствующему значению индекса Вызов индексатора, то есть выражение

имя_объекта [список аргументов]

может размещаться слева и справа от операции присваивания. В первом случае выполняется аксессор set, во втором - аксессор get. В теле аксессора set значение, которое нужно использовать для изменения элемента коллекции, всегда представлено служебным словом value.

В качестве примера класса с индексатором определим класс Work_hours для представления рабочих дней недели. В массиве должно быть 7 элементов, с индексами от 0 (для понедельника) до 6 (для воскресенья). Значения элементов массива - количества отработанных часов по дням недели от 0 (не рабочий день) до 14 часов (больше работать запрещено). Для обращения к элементам массива в класс введем индексатор.

В данном примере нужно в теле индексатора запретить обращения к массиву с индексом, выходящим за пределы его граничной пары (от 0 до 6). Кроме того, предусмотрим в индексаторе защиту от неверных значений, присваиваемых элементам массива. В соответствии со смыслом определяемого класса значения элементов массива (количество отработанных часов) не должны быть отрицательными и не должны превышать некоторого предельного значения. Для конкретности в условии предположено, что работа одного дня не должна превышать 14-ти часов.

//12_04.cs - индексатор - рабочие часы дней недели.

class Work_hours

{

int[] days; // часы по дням недели

public Work_hours() // конструктор

{

days = new int[7];

}

public int this[int d] // индексатор

{

get { return (d < 0 || d > 6) ? -1 : days[d]; }

set {

if (d < 0 || d > 6 || value < 0 || value > 14)

Console.WriteLine("Недопустимо: день={0}, часы={1}!", d, value);

else

days[d] = value;

}

}

}

public static void Main()

{

Work_hours week = new Work_hours();

week[0] = 7; // понедельник

week[2] = 17; //недопустимые данные

week[3] = 7; // четверг

week[6] = 7; // воскресенье

Console.WriteLine("Рабочая неделя: ");

for (int i = 0; i < 7; i++)

Console.Write("day[{0}]={1} ", i, week[i]);

Console.WriteLine();

Console.WriteLine("day[{0}]={1}\t", 8, week[8]);

}

Результат выполнения программы:

Недопустимо: день=2, часы = 17!

Рабочая неделя:

day[0] = 7 day[1] = 0 day[2] = 0 day[3] = 7 day[4] = 0 day[5] = 0 day[6] = 7

day[8] = -1

Параметр индексатора и соответствующее ему индексное выражение индексатора не обязательно должны иметь целочисленный тип. В следующем примере рассмотрим класс Dictionary, объект которого может служить простейшим словарем. В класс Dictionary включим в качестве полей два массива строк - массив исходных слов, например, на английском языке, и массив переводных эквивалентов. Массив исходных слов будем заполнять в конструкторе при создании словаря-объекта. Элементам массива переводных эквивалентов будем присваивать значения с использованием индексатора. Параметр индексатора будет иметь тип string. Задавая в квадратных скобках слово в виде строки, получим доступ к элементу с его переводным эквивалентом. Определение класса может быть таким (программа 12_05.cs):

class Dictionary // словарь

{

string[] words; // слова

string[] trans; // переводы

// конструктор

public Dictionary(params string[] str)

{

words = new string[str.Length];

trans = new string[str.Length];

int ind = 0;

foreach (string s in str)

words[ind++] = s; // заполнили массив слов

}

// поиск слова

int search(string str) // поиск слова

{

for (int i = 0; i < words.Length; i++)

if (words[i] == str)

return i;

return -1;

}

public string this[string w]

{

set

{

int ind = search(w);

if (ind == -1)

Console.WriteLine("Слова Нет!");

else

trans[ind] = value;

}

get

{

int ind = search(w);

if (ind == -1)

return "Слова Нет!";

else

return trans[ind];

}

}

}

В классе Dictionary две ссылки words и trans на массив слов и на массив их переводных эквивалентов. Собственно массивы как объекты создаются в конструкторе. У конструктора параметр с модификатором params, позволяющий использовать переменное число аргументов. Реальное количество аргументов определяется как str.Length. Это значение определяет размеры массивов, адресуемых ссылками words и trans. Строки-аргументы конструктора присваиваются элементам массива words[ ] в цикле foreach. Массив переводов trans[ ] остаётся незаполненным.

В классе определен вспомогательный закрытый метод search(), для поиска в словаре (в массиве words[ ] исходных слов) слова, заданного с помощью аргумента. Метод вернет индекс слова, либо -1, если слово отсутствует. Метод search() используется в индексаторе. В аксессоре set определяется индекс ind того элемента массива trans[ ], которому нужно присвоить новое значение переводного эквивалента. Если поиск неудачен - выводится сообщение, иначе элементу trans[ind], присваивается значение переводного эквивалента. Аксессор get возвращает значение trans[ind] либо строку с сообщением, что слова нет в словаре.

Для иллюстрации возможностей класса dictionary и его индексатора приведем следующий фрагмент кода:

public static void Main()

public static void Main()

{

Dictionary number = new Dictionary("zero", "one", "two");

number["zero"] = "нуль";

number["one"] = "один";

number["two"] = "2";

Console.WriteLine("number[\"one\"]: " + number["one"]);

Console.WriteLine("number[\"three\"]: " + number["three"]);

Console.WriteLine("number[\"two\"]: " + number["two"]);

}

В методе Main() создан один объект класса Dictionary. В объекте-словаре всего три слова, у которых вначале нет переводных эквивалентов. Для задания переводов используются выражения с индексами. При обращении к отсутствующему слову значением выражения number["three"] будет строка "Слова Нет!".

Результат выполнения программы:

number["one"]: один

number["three"]: Слова Нет!

number["two"]: 2

Соседние файлы в папке Lekc_C#