- •Обзор элементов класса.
- •Класс Object
- •Структуры
- •Методы Ссылочные параметры ref и out
- •Переменное число аргументов метода
- •Перегрузка методов. Вызов методов с одинаковым именем и разными аргументами
- •Конструкторы экземпляра
- •Вызов конструктора экземпляра
- •Закрытые конструкторы экземпляра
- •Статические конструкторы
- •Конструкторы структур
- •Член со спецификатором readonly
- •Деструкторы. Сборка мусора
- •Объект Account
- •Объект Account недоступен
- •Свойства
- •Индексаторы
- •Предпосылки появления наследования
- •Синтаксис наследования.
- •Типы наследования
- •Множественное наследование
- •Наследование интерфейса
- •Виртуальные методы
- •Спецификаторы доступности
- •Вызов базовых версий функций
- •Абстрактные классы и функции
- •Структуры и наследование
- •Полиморфизм
- •Потеря и восстановление информации о типе
- •Операции is и as
- •Файловый ввод-вывод
- •Потоки данных и файловый ввод-вывод
- •Виды файлов
- •Классы файлового ввода-вывода
- •Класс FileInfo
- •Текстовый ввод-вывод при помощи Stream Reader и Stream Writer
- •Бинарный ввод и вывод при помощи класса FileStream
Свойства
Свойства обеспечивают надежную и элегантную поддержку одного из принципов ООП - принципа инкапсуляции. Они позволяют обращаться к закрытым (private) переменным экземпляра так же, как и к открытым (public), т.е. без громоздких методов называемых аксессорами и мутаторами, рассмотренных ранее. Применение свойств не приводит к нарушению зашиты и сокрытия данных (что необходимо при использовании переменных экземпляра private), поскольку свойства реализуются внутри класса почти так же, как и методы.
Хотя свойство для внешнего мира представляет собой переменную экземпляра public, оно реализовано способом, похожим на метод (ведь свойство тоже может исполнять операторы). Благодаря таким возможностям свойства удобно применять вместо аксессоров и мутаторов.
В соответствии с принципом инкапсуляции, переменные экземпляра должны быть скрыты от остальной программы. Для этого в языке С# они объявляются со спецификатором доступности private. В примерах, представленных далее, доступ к закрытым переменным осуществляется посредством двух методов: аксессора и мутатора, имена которых по соглашению имеют префикс get и set. Например, GetDistance() и SetDistance(). Эти методы могут также проверять значения, присваиваемые переменным, или выполнять другие операции Пример использования аксессора и мутатора приведен в листинге программы.
Пример. Написать программу «Студент», состоящую из 2-х классов: класса «Студент» и класса «Тест». Класс «Студент» описывает объект студент и имеет 3 переменных экземпляра: Имя, Фамилия, и оценка по ЛИПО. Причем, последняя является закрытой переменной и для нее необходимо написать аксессор с мутатором, причем они должны проверять, чтобы оценка находилась в пределах от 2 до 5 .
using System;
using System.Collections.Generic;
using System.Text;
class student
{
public string first_name = "", second_name = "";
private byte mark;
// Аксессор:
public int get_mark()
{
return mark;
}
// Мутатор:
public void set_mark(byte new_mark)
{
if (new_mark < 1 || new_mark > 5)
{
Console.WriteLine("Ошибка, вводимые данные должны находится в диопазоне от 1 до 5 !!!");
}
else
{
mark = new_mark;
}
}
}
//----------------------------------------------------------------------
class test
{
static void Main(string[] args)
{
student student_1 = new student();
student_1.first_name = "Петров";
student_1.second_name = "Петр";
student_1.set_mark(2);
Console.WriteLine("Студент " + student_1.first_name + " " + student_1.second_name + " имеет оценку по ЛИПО: " + student_1.get_mark());
Console.ReadLine();
}
}
Класс student содержит закрытую переменную экземпляра: mark. В данном примере переменной mark нельзя присвоить значение, меньшее 1 или большее 5. Для решения задачи пишем два метода аксессор get_mark, который возвращает значение закрытой переменной экземпляра mark :
public int get_mark()
{
return mark;
}
и мутатор set_mark, который изменяет значение переменной экземпляра mark:
public void set_mark(byte new_mark)
{
if (new_mark < 1 || new_mark > 5)
{
Console.WriteLine("Ошибка, вводимые данные должны находится в диопазоне от 1 до 5 !!!");
}
else
{
mark = new_mark;
}
}
Метод Main класса Test - внешний по отношению к классу student. Для работы с переменной mark в нем используются описанные выше методы set_mark и get_mark.
class test
{
static void Main(string[] args)
{
student student_1 = new student();
student_1.first_name = "Петров";
student_1.second_name = "Петр";
student_1.set_mark(2);
Console.WriteLine("Студент " + student_1.first_name + " " + student_1.second_name + " имеет оценку по ЛИПО: " + student_1.get_mark());
Console.ReadLine();
}
}
Аксессор и мутатор широко используются программистами в таких языках, как Java и C++, где нет аналога свойствам С#. Их недостаток — несоответствие между синтаксисом доступа к переменным экземпляра внутри объекта и за его пределами.
При обращении к переменной экземпляра внутри объекта применяется не метод, а стандартный синтаксис: mark = new_mark; Совместимость синтаксиса предполагает, что и во внешнем по отношению к объекту коде можно использовать не метод-мутатор, а стандартный оператор присвоения: mark = 2;.
Однако, без использования свойств такие возможности могут быть достигнуты только объявлением переменной mark со спецификатором public (при этом будут отброшены важные операторы, содержащиеся в аксессоре и мутаторе).
Свойство (как структурный элемент языка С#) позволяет:
внешнему пользователю объекта обращаться к переменной экземпляра с синтаксисом (без метода), приведенным ранее;
переменным экземпляра оставаться закрытыми и в то же время, свойство может содержать операторы, которые в ином случае могут находиться только в методах доступа (аксессоре и мутаторе).
Рассмотрим синтаксический блок свойства:
Объявление_свойства::=
[<Спецификатор_доступности>]<Тип><Идентификатор>
{
[<блок_get>]
[<блок_set>]
}
где
<Спецификатор_доступности>:
::= public
::= private
::= protected
::= internal
::= protected internal
<блок_get>:
::= get
{
[<Операторы>]
return <Выражение>
}
<блок_set>:
::= set
{
[<Операторы> с использованием ключевого слова value]
}
Стоит отметить несколько особенностей синтаксиса свойств:
тип <Выражение>, расположенного после ключевого слова return в <блоке_get>, должен совпадать с <Тип> в заголовке свойства;
параметр value в <блоке_set> представляет значение, присваиваемое свойству, и принадлежит типу <Тип> из заголовка;
имя value задано компилятором — его нельзя изменить;
свойство может содержать только <Блок_get>, только <Блок_set>, или оба блока.
Свойство не представляет определенную переменную экземпляра (хотя зачастую свойства применяются именно так), а имитирует ее. К примеру, свойство может называться Average и подсчитывать в своем блоке get среднее значение нескольких переменных экземпляра.
Клиенту (клиентом называется часть программы, которая вызывает свойства и методы класса или объекта) удобнее использовать свойства, а не аксессоры и мутаторы по следующим причинам:
свойства обеспечивают согласованный синтаксис при обращении к переменной экземпляра внутри объекта и за его пределами;
свойства позволяют программисту не заботиться о том, является переменная экземпляра public или private, определены ли для нее аксессор и мутатор и каков их формат;
как правило, для одной переменной экземпляра (к которой требуется доступ) используется один аксессор и один мутатор (т. е. два метода), а свойство требуется только одно, таким образом, вместо анализа сорока аксессоров и мутаторов пользователю достаточно просмотреть двадцать свойств.
cвойства объединяют аксессор и мутатор в одну структуру, обычно, когда для доступа к переменной экземпляра используются два этих метода, при изменении в одном из них необходима и модификация другого, однако синтаксически эти методы различны и ничем не отличаются от любых других методов объекта (синтаксис никак не подчеркивает, что они должны применяться в паре), такая структура, как свойство решает эту проблему: обе смысловые части (аксессор — блок get, и мутатор — блок set) собраны в одной точке кода.
Далее рассмотрим программу, которая позволяет при помощи свойств реализовать аналогичную функциональность:
using System;
using System.Collections.Generic;
using System.Text;
namespace Студент_свойства_
{
class student
{
public string first_name = "", second_name = "";
private byte mark;
// Свойство:
public byte Mark
{
get
{
return mark;
}
set
{
if (value < 1 || value > 5)
{
Console.WriteLine("Ошибка, вводимые данные должны находится в диопазоне от 1 до 5 !!!");
}
else
{
mark = value;
}
}
}
}
//----------------------------------------------------------------------
class test
{
static void Main(string[] args)
{
student student_2 = new student();
student_2.first_name = "Иванов";
student_2.second_name = "Иван";
student_2.Mark=5;
Console.WriteLine("Студент " + student_2.first_name + " " + student_2.second_name + " имеет оценку по ЛИПО: " + student_2.Mark);
Console.ReadLine();
}
}
}
Класс student содержит свойств Mark, которое обеспечивает доступ к закрытой переменной экземпляра mark. Рассмотрим свойство Mark. Свойство состоит из заголовка и блока, заключенного в фигурные скобки В заголовке можно определить спецификатор доступности (в данном случае public), тип свойства (byte) и его имя (Mark). Блок свойства состоит из блока get , содержащего те же операторы, что и аксессор, и блока set, содержащего операторы мутатора. Клиентская часть (т. е., попросту, пользователь класса, а в данном случае — метод Main) может теперь вызвать свойство Mark как собственную переменную экземпляра:
student_2.Mark=5;
Специальный параметр value, представляет присваиваемое значение (в данном случае 5). Среда исполнения автоматически присваивает параметру value значение 5. Таким образом, значение 5 присваивается переменной экземпляра mark.
Чтение значения mark осуществляется следующим образом:
student_2.Mark
Инициируя исполнение операторов блока get. После исполнения оператора с ключевым словом return блок get завершается и возвращает выражение, следующее за словом return. Значение return в таком контексте идентично его значению в обычном методе.
Если требуется только установить значение переменной экземпляра (когда она предназначена только для записи) или только прочесть его (переменная только для чтения), можно определить только один блок: set или get. Важно, что хотя бы один из них должен присутствовать в свойстве.