Член со спецификатором 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

 
  Член со спецификатором readonly - student2.ru

Объект Account недоступен
accountl = null

 
  Член со спецификатором readonly - student2.ru

2. Переменной, хранящей единственную ссылку на объект, присваивается другая ссылка, и объектстановится недоступным.

accountl = new Account();

account2 = new Account();

accountl = account2;

Рассмотрим этот вариант графически:

accountl = new Account();

account2 = new Account();

   
  Член со спецификатором readonly - student2.ru
 
  Член со спецификатором readonly - student2.ru

accountl = account2;

 
  Член со спецификатором readonly - student2.ru

3. Если объект А содержит единственную ссылку на объект В, то когда А становит­ся недоступным, то же самое происходит с В.

В программе переменной banklприсваивается ссылка на новый объект Bank,Этот объект содержит ссылку на объект Account,присвоенную методом SetAccount. После выполнения строки bankl = null объект bankl больше не ссылается на объект Bank, и, значит, объект Account также становится недоступ­ным.

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

bankl = new Bank();

 
  Член со спецификатором readonly - student2.ru

bankl.SetAccount(new Account());

 
  Член со спецификатором readonly - student2.ru

bankl = null;

 
  Член со спецификатором readonly - student2.ru

Рассмотрим основные задачи сборщика мусора. Для освобождения памяти от недоступных более объектов в языке С# используется механизм, поддерживаемый средой исполнения .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. Хотя доступ к объекту осуществляется теми же средствами, что и к массиву, индексатор не поддерживает методы и свойства обычного массива.

Наши рекомендации