Опубликован: 02.12.2009 | Уровень: специалист | Доступ: платный | ВУЗ: Тверской государственный университет
Лекция 2:

Классы

< Лекция 1 || Лекция 2: 123456 || Лекция 3 >
Методы-свойства

Методы, называемые свойствами ( Properties ), представляют специальную синтаксическую конструкцию, предназначенную для обеспечения эффективной работы со свойствами. Напомню, правильной стратегией является закрытие полей от клиента - поля объявляются с модификатором protected или private. Клиенты класса не должны использовать информацию о том, как устроены поля. Это облегчает возможную модификацию класса в будущем. Класс сможет изменить представление данных, сохранив интерфейс, предоставляемый клиентам. В этом случае изменения в полях не отразятся на клиентах.

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

  • чтение, запись ( Read, Write );
  • чтение, запись при первом обращении ( Read, Write-once );
  • только чтение ( Read-only );
  • только запись ( Write-only );
  • ни чтения, ни записи ( Not Read, Not Write ).

Для эффективной поддержки этих стратегий и введены специальные методы, называемые свойствами. Приведу вначале пример, а потом уточню синтаксис этих методов. Рассмотрим класс Person, у которого пять полей: fam, status, salary, age, health, характеризующих, соответственно, фамилию, статус, зарплату, возраст и здоровье персоны. Все поля закрыты для клиента, так что клиент не может непосредственно читать или записывать данные в поля класса. Для каждого из этих полей может быть разумной своя стратегия доступа. При проектировании класса будем предполагать, что возраст доступен для чтения и записи, фамилию можно задать только один раз, статус можно только читать, зарплата недоступна для чтения, а здоровье закрыто для доступа и только специальные методы класса могут сообщать некоторую информацию о здоровье персоны. Вот как на C# можно обеспечить эти стратегии доступа к закрытым полям класса:

/// <summary>
/// Класс, задающий общие свойства 
/// и поведение личности
/// </summary>
public class Person
{
   public enum Status
    {
        ребенок, школьник,
        студент, работник, пенсионер
    }
    //поля (все закрыты)
    string fam = "",  health = "";
    int age = 0, salary = 0;
    Status status = Status.работник;
    //методы - свойства
    /// <summary>
    ///стратегия: Read,Write-once (Чтение, запись при первом обращении)
    /// </summary>
    public string Fam
    {
        set { if (fam == "") fam = value; }
        get { return (fam); }
    }
    /// <summary>
    ///стратегия: Read-only(Только чтение)
    /// </summary>
    public Status GetStatus
    {
        get { return (status); }
    }
    /// <summary>
    ///стратегия: Read,Write (Чтение, запись)
    /// </summary>
    public int Age
    {
        set
        {
            age = value;
            //Изменение статуса
            if (age < 7) status = Status.ребенок;
            else if (age < 17) status = Status.школьник;
            else if (age < 22) status = Status.студент;
            else if (age < 65) status = Status.работник;
            else status = Status.пенсионер;
        }
        get { return (age); }
    }
    /// <summary>
    ///стратегия: Write-only (Только запись)
    /// </summary>
    public int Salary
    {
        set { salary = value; }
    }
}

Рассмотрим теперь общий синтаксис методов-свойств. Пусть name - это закрытое свойство. Тогда для него можно определить открытый метод-свойство (функцию), возвращающую тот же тип, что и поле name. Имя метода обычно близко к имени поля, отличаясь от него, например, только заглавной буквой ( Name ). Тело свойства содержит два метода - get и set, один из которых может быть опущен. Метод get возвращает значение закрытого поля, метод set устанавливает значение, используя значение, передаваемое ему в момент вызова и хранящееся в служебной переменной со стандартным именем value. Поскольку get и set - это обычные процедуры языка, программно можно реализовать сколь угодно сложные стратегии доступа. В нашем примере фамилия меняется, только если ее значение равно пустой строке, и это означает, что фамилия персоны ни разу еще не задавалась. Статус персоны пересчитывается автоматически при всяком изменении возраста, явно изменять его нельзя. Вот пример, показывающий, как некоторый клиент создает поля персоны и работает с ними:

/// <summary>
/// Тестирование свойств объектов
/// класса Person
/// </summary>
public void TestPersonProps()
{           
    Person pers1 = new Person(), 
           pers2 = new Person();
    pers1.Fam = "Хохлова";
    pers1.Age = 21;
    ConsolePrintPerson(pers1);           
    pers2.Fam = "Петров"; 
    pers2.Age = pers1.Age + 1;
    ConsolePrintPerson(pers2);
    pers1.Fam = "Петрова";
    ConsolePrintPerson(pers1);
}//TestPersonProps
/// <summary>
/// Вывод на консоль полей, открытых для чтения
/// </summary>
void ConsolePrintPerson(Person pers)
{
    Console.WriteLine("Фам={0}, возраст={1}, статус={2}",
        pers.Fam, pers.Age, pers.GetStatus);
}

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

Индексаторы

Свойства являются частным случаем метода класса с особым синтаксисом. Еще одним частным случаем является индексатор. Метод-индексатор является обобщением метода-свойства. Он обеспечивает доступ к закрытому полю, представляющему массив. Объекты класса индексируются по этому полю.

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

Добавим в класс Person свойство children, задающее детей персоны, сделаем это свойство закрытым, а доступ к нему обеспечит индексатор:

const int Child_Max = 20; //максимальное число детей
Person[] children = new Person[Child_Max];
int count_children = 0; //число детей
/// <summary>
/// Метод-свойство
/// </summary>
public int Count_children 
{ get { return count_children; } }
   /// <summary>
/// Индексатор по массиву children
/// </summary>
/// <param name="i">индекс элемента в массиве</param>
/// <returns>элемент с заданным индексом</returns>
public Person this[int i] //индексатор
{
    get
    {
        if (i >= 0 && i < count_children) return (children[i]);
        else return (children[0]);
    }
    set
    {
        if (i == count_children && i < Child_Max)
        { children[i] = value; count_children++; }
    }
}

Имя у индексатора this, в квадратных скобках в заголовке перечисляются индексы. В методах get, set, обеспечивающих доступ к массиву children, по которому ведется индексирование, анализируется корректность задания индекса. Закрытое поле count_children, хранящее текущее число детей, доступно только для чтения благодаря добавлению соответствующего метода-свойства. Запись в это поле происходит в методе set индексатора, когда к массиву children добавляется новый элемент.

Протестируем процесс добавления детей персоны и работу индексатора:

/// <summary>
/// Тест индексатора 
/// </summary>
public void TestPersonChildren()
{
    Person pers1, pers2, pers3, pers4;
    pers1 = new Person();
    pers1.Fam = "Петров"; pers1.Age = 42; 
    pers2 = new Person();
    pers2.Fam = pers1.Fam; pers2.Age = 7;
    pers1[pers1.Count_children] = pers2;
    pers3 = new Person();
    pers3.Fam = pers1.Fam + "а"; pers3.Age = 4;
    pers1[pers1.Count_children] = pers3;
    pers4 = new Person();
    pers4.Fam = pers1.Fam; pers4.Age = 2;
    pers1[pers1.Count_children] = pers4;
    ConsolePrintPerson(pers1);
    ConsolePrintPerson(pers1[0]);
    ConsolePrintPerson(pers1[1]);
    ConsolePrintPerson(pers1[2]);
}

Заметьте, индексатор позволяет индексировать объект pers1 по соответствующему полю, в данном случае - по полю children.

< Лекция 1 || Лекция 2: 123456 || Лекция 3 >
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дмитрий Штаф
Дмитрий Штаф
Россия
Дмитрий Слапогузов
Дмитрий Слапогузов
Россия, Бийск