Член со спецификатором readonly
Как и константы (определяемые с ключевым словом const),элементы readonlyиспользуются для представления неизменяемых значений. Но, тогда как константе значение должно быть присвоено в исходном коде, например:
const decimal pi=3.14;
значение элемента readonly может быть и неизвестно во время исполнения программы. Оно инициализируется конструктором и после этого не может быть изменено. Таким образом, константа неизменна во время исполнения программы, а элемент readonly— во все время существования объекта.
В синтаксическом блоке 13.4 показано, как объявляется член readonly.Кроме ключевого слова readonlyобщий вид объявления и значение различных его частей похожи на объявление обычной переменной экземпляра.
Объявление_ члена_readonly :=
/ <Спецификатор_ доступности> ] [static] readonly <Тип> <Идентификатор> [ = <Выражение> ];
где:
<Спецификатор_доступности>
::= private
::= public
…
Рассмотрим пример.
using System;
class Car
{
public readonly string CarMark;
public Car (string myCarMark)
{
CarMark = myCarMark;
}
}
class myCar
{
public static void Main()
{
Car myCar = new Car("ВАЗ-21103");
Console.WriteLine("Марка машины "
+ myCar.CarMark);
}
}
Деструкторы. Сборка мусора
Классы задаются в исходном коде до исполнения программы, а объекты создаются динамически, на этапе исполнения посредством операции new (как было сказано ранее). Каждый порожденный объект занимает определенный объем памяти, необходимый для хранения таких элементов, как, например, переменные экземпляра. Поскольку память ограничена, очень важно удалять из нее объекты, больше не используемые в программе. Это необходимо для освобождения памяти для вновь порождаемых объектов.
Прежде чем говорить о том, как в С# освобождается память, необходимо детальнее разобраться с понятием "объект больше не используется". На самом деле это значит, что объект становится недоступным для программы, так как больше не существует ссылок на него.
Рассмотрим программу, которая демонстрирует как все ссылки на объект могут быть утрачены, и таким образом он станет недоступен.
using System;
class Account
{
}
class Bank
{
private Account bankAccount;
public void SetAccount(Account newAccount)
{
bankAccount = newAccount;
}
}
class Tester
{
public static void Main()
{
Bank bankl;
Account accountl;
Account account2;
accountl=new Account();
accountl = null;
accountl = new Account();
account2 = new Account();
accountl = account2;
bankl = new Bank();
bankl.SetAccount(new Account());
bankl = null;
}
}
Эта программа ничего не выводит на экран.
Метод Main()класса Testerиллюстрирует четыре причины, приводящих к недоступности объекта.
1. Присвоение всем ссылкам на объект значения null.
В строке: Account accountl; создается объект, ссылка на который присваивается переменной accountl.В строке accountl = null; ей присваивается значение null,и она больше не содержит указателя на исходный объект. Объект Account становитсянедоступным, поскольку на него больше нет ссылок. Графически это выглядет следующим образом:
Account accountl
|
2. Переменной, хранящей единственную ссылку на объект, присваивается другая ссылка, и объектстановится недоступным.
accountl = new Account();
account2 = new Account();
accountl = account2;
Рассмотрим этот вариант графически:
accountl = new Account();
account2 = new Account();
accountl = account2;
3. Если объект А содержит единственную ссылку на объект В, то когда А становится недоступным, то же самое происходит с В.
В программе переменной banklприсваивается ссылка на новый объект Bank,Этот объект содержит ссылку на объект Account,присвоенную методом SetAccount. После выполнения строки bankl = null объект bankl больше не ссылается на объект Bank, и, значит, объект Account также становится недоступным.
Если объект Account ссылался на другой объект, а тот, в свою очередь, на третий и т. д., то цепочка объектов, связанных с помощью ссылок, будет доступна до тех пор, пока доступен первый объект.
bankl = new Bank();
bankl.SetAccount(new Account());
bankl = null;
Рассмотрим основные задачи сборщика мусора. Для освобождения памяти от недоступных более объектов в языке С# используется механизм, поддерживаемый средой исполнения .NET и называемый сборщиком мусора (СМ).
Методика автоматического управления памятью называется сборкой мусора (СМ).
Сборщик мусора выполняет две основные функции:
· обнаружение недоступных объектов;
· восстановление занимаемой ими памяти.
Рассмотрим основные принципы СМ.
1. Механизм CM .NET-платформы не удаляет доступные объекты.
2. СМ .NET-платформы, автоматически обнаруживает и удаляет все недоступные объекты. Поэтому не следует беспокоиться о запуске или прекращении работы этой системы.
3. Недоступный объект не удаляется сразу. Когда СМ работает, он занимает время процессора, которое могло бы быть потрачено на выполнение программы, что приводит к ее временному "замораживанию". Поэтому сборщик мусора должен найти баланс между освобождением памяти для новых объектов и минимальным использованием ресурсов процессора.
Сборщик мусора существует не во всех объектно-ориентированных языках программирования. Например, в языке C++ программист сам должен создать исходный код, обнаруживающий, когда объект становится не нужным, и запускающий специальный метод, называемый деструктором, для освобождения соответствующего блока памяти. Деструктор запускается сразу же после вызова, причем в нем могут содержаться команды освобождения и других ресурсов (файлов, сетевых соединений и т. д.). Следовательно, программирующий на C++ достаточно жестко управляет тем, когда объект освобождает память и другие ресурсы.
О6ъявленив_двструктора::=
<Идентификатор_деструктора>()
{
<Операторы>
}
Примечание
1. <Идентификатор_деструктора> подобно конструктору должен совпадать с идентификатором класса.
2. Деструктор не имеет формальных параметров.
Свойства
Свойства обеспечивают надежную и элегантную поддержку одного из принципов ООП - принципа инкапсуляции. Они позволяют обращаться к закрытым (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. Важно, что хотя бы один из них должен присутствовать в свойстве.
Индексаторы
Индексаторы предоставляют возможность доступа к коллекции данных (например, массиву), хранящейся внутри объекта, по такому принципу, как если бы этот объект сам был массивом. Таким образом, вместо применения метода для доступа к данным внутри индексаторы позволяют использовать для обычные квадратные скобки аналогично массивам.
Как было показано ранее, массив поддерживает множество полезных встроенных методов и свойств, например Sort и Length.Однако иногда требуются и дополнительные средства, не предоставляемые классом Array.Может понадобиться массив, способный вычислять сумму и среднее своих элементов или отображать значения четных и нечетных элементов массива. Для этого можно создать класс, подобный массиву с дополнительными возможностями (фактически он будет содержать массив или другую коллекцию как переменную экземпляра).
Напомним, что для обращения к отдельному элементу массива применяется индекс в квадратных скобках после имени:...accounts[4]...
Если объект представляет собой массив, было бы удобно обращаться к его элементам так же (по индексу в скобках). Дня этого в С# введен специальный элемент языка — индексатор.
: ■
Индексаторы позволяют использовать наглядный синтаксис доступа к элементам коллекции, инкапсулированной в объекте. Синтаксис состоит из квадратных скобок, внутри которых находится аргумент-индекс, следующий за именем объекта:...myArrayObject[4]...
Концепция индексаторов сходна с концепцией свойств. Свойство имитирует поле, но на самом деле исполняет блоки get и set, а индексатор имитирует массив, но также исполняет блоки get и set. В обеих конструкциях применяется идентичный синтаксис.
Несмотря на сходство индексаторов и свойств, между ними имеется и несколько существенных различий.
1. Индексатор нельзя объявить как static. Он должен быть элементом экземпляра, т. е. с помощью квадратных скобок можно обращаться к объектам, но не к классам.
2. Индексатор идентифицируется объектом, в котором он находится, и комбинацией аргументов в квадратных скобках. Следовательно, у самого индексатора имени нет. Поскольку объект трактуется как массив, а индексатор остается анонимным, в объявлении его всегда применяется ключевое слово this.
3. Заголовок объявления индексатора должен включать непустой список формальных параметров.
Рассмотрим синтаксический блок индексатора:
Объявление_свойства::=
[<Спецификатор_доступности>]<Тип> this [Список_ формальных_параметров]
{
[<блок_get>]
[<блок_set>]
}
где
<Спецификатор_доступности>:
::= public
::= private
::= protected
::= internal
::= protected internal
<блок_get>:
::= get
{
[<Операторы>]
return <Выражение>
}
<блок_set>:
::= set
{
[<Операторы> с использованием ключевого слова value]
}
Например, напишем программу, которая при помощи индексатора имитирует массив и в том же классе имеет метод, который выводит сумму элементов массива.
class Mass
{
int[] my_mass;
public Mass(int razm)
{
my_mass = new int [razm];
}
public int this[int index]
{
get
{
return my_mass[index];
}
set
{
my_mass[index] = value;
}
}
public int sum()
{
int sum = 0;
for (int i = 0; i < my_mass.Length; i++)
{
sum = sum + my_mass[i];
}
return sum;
}
}
class Program
{
static void Main(string[] args)
{
Mass myMass = new Mass(5);
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Введите элемент: {0}", i);
myMass[i] = Convert.ToInt32(Console.ReadLine());
}
for (int j = 0; j < 5; j++)
{
Console.WriteLine("Элемент массива с индексом {0} равен: {1}", j, myMass[j]);
}
Console.WriteLine(myMass.sum());
Console.ReadLine();
}
}
Стоит отметить несколько особенностей индексаторов.
1. В список формальных параметров могут входить формальные параметры любого типа, например типа String Соответствующий аргумент-индекс также будет принадлежать типу string. Такое свойство отличается от обычного массива, где применим только индекс, принадлежащий одному из типов: uint, int, ulong, long (или неявно преобразуемый в них).
2. Хотя объект с индексатором имитирует массив (с точки зрения пользователя объекта), его реализация в классе зависит от разработчика.
3. Хотя доступ к объекту осуществляется теми же средствами, что и к массиву, индексатор не поддерживает методы и свойства обычного массива.