Ссылочные параметры ref и out

Классы

Классы и структуры — это, по сути, шаблоны, по которым можно создавать объекты. Каждый объект содержит данные и методы, манипулирующие этими данными. Класс определяет, то какие данные и какую функциональность может иметь каждый конкретный объект (называемый экземпляром) этого класса.

Структуры отличаются от классов тем, как они сохраняются в памяти и как к ним осуществляется доступ (классы — это ссылочные типы, размещаемые в куче, структуры — типы значений, размещаемые в стеке), и некоторыми свойствами (например, структуры не поддерживают наследование). Из соображений производительности легче использовать структуры для небольших типов данных. В отношении синтаксиса структуры очень похожи на классы. Главное отличие состоит в том, что в их объявлении используется ключевое слово struct вместо class.

При создании, как классов, так и структур используется ключевое слово new для объявления экземпляра. В результате объект создается и инициализируется.

Класс — это абстракция (реальная или концептуальная) объекта, принадлежаще­го какой-либо предметной области. Один шаблон класса можно использовать для со­здания нескольких объектов (экземпляров класса), которые обладают свойствами, оп­ределенными в классе.

При решении разных вычислительных за­дач объекты различных классов взаимодействуют друг с другом, внося свои уникальные свойства в общую программу. Конструкция класса позволяет объединять данные (назы­ваемые состоянием объекта) с функциями (представляющими его поведение) для созда­ния объектов, составляющих структуру разрабатываемого ПО. До этого момента классы, состояли из переменных и методов экземпляра, как показано на рис. 3.1.

class <Идентификатор_класса>
{

<Переменные_экземпляра>

<Методы>

}

Элементы класса являются языковыми конструкциями, составляющими тело класса. К примеру, переменные и методы экземпляра представляют собой два фундаментальных элемента класса. Однако классы настолько разнообразны, что С# содержит и несколько других элементов, придающих классу гибкость и расширяющих его возможности по взаимодействию с другими классами программы.

Обзор элементов класса.

В синтаксическом блоке представлен синтаксис, определения класса и описаны элементы, которые можно включать в определение класса.

Определение_класса::=

class <Идентификатор_класса>

{

<Члены_класса>

где

<Члены__класса>

:= <Данные-члены>

:= <Функции-члены>

:= <Вложенные_типы>

<Данные члены>

:=<Переменные-члены>

:=<Константы>

:= <События>

<Функции-члены>

:= <Методы>

:= <Конструкторы>

:= <Деструкторы>

:= <Свойства>

:= <Индексаторы>

:= <Операции>

<Вложенные_типы>

:= <Вложенные_классы>

:= <Вложенные_структуры>

:= <Вложенные_перечисления>

В первых строках ото­бражен уже знакомый синтаксис: ключевое слово class,ими (идентификатор) класса и фигурные скобки, формирующие тело класса. Элементы класса можно разделить на три широкие категории:

· данные-члены,

· функции-члены;

· вложенные типы.

Данные-члены состоят из переменных-членов, констант и событий.

Переменные-члены (называемые также полями) используются для представления данных. Такая переменная может принадлежать или конкретному экземпляру (объекту) класса — в этом случае она называется переменной экземпляра, или самому классу — тогда она называется статической (объявленной static)перемен­ной (или переменной класса). Напомним, что статический метод принадлежит классу, а не объекту (и вызывается для класса). То же самое справедливо и для статической переменной.

Переменная-член может быть объявлена только для чтения (с ключевым словом readonly).Такие переменные тесно связаны с константами-членами, но существует важное различие — значения последних устанавливаются в программе в процес­се компиляции и существуют на протяжении ее исполнения. А значения readonlyпеременных-членов присваиваются им при создании объекта, и поэтому существу­ют, пока существует объект.

События объявляются подобно переменным-членам, но используются по-друго­му. При щелчке на кнопке (к примеру, ОК) в графическом пользовательском ин­терфейсе (GUI) соответствующий объект в программе генерирует (или возбужда­ет) событие (скажем, OKButtonClick),по которому определенная часть программы реагирует на действие пользователя. О приложении такого типа говорят, что оно управляется событиями, поскольку его следующее действие зависит от генерируе­мого события. Здесь уже неприменимо понятие программы, исполняющейся в той последовательности, в которой написаны ее операторы.

Функции-члены могут быть методами, конструкторами, деструкторами, свойствами индексаторами и операциями:

Метод — уже знакомая конструкция, которая, тем не менее, обладает многими еще неизученными свойствами.

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

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

Свойства. Доступ к свойствамосуществляется так же, как к переменным-членам. Различие состоит в том, что свойство содержит операторы, которые исполняются подобно операторам метода, когда происходит обращение к нему. Они часто используют­ся вместо аксессоров и мутаторов для доступа к закрытым переменным, поддерживая, таким образом, принцип инкапсуляции.

Индексаторы используются с классами, представляющими массивы. Так же, как свойства обеспечивают удобный доступ к отдельным переменным членам, индек­саторы выполняют эту функцию для массивов, размещенных внутри классов.

Операции. Иногда имеет смысл (для повышения читаемости кода) объединить два объекта с помощью операции. Например, можно написать оператор наподобие totalTime = myTime + yourTime;,где все три переменных — объекты класса Timelnterval.Для достижения такой функциональности в классы включают операции-члены, кото­рые задают набор команд, исполняющийся при объединении объектов в выраже­нии с данной операцией. Этот процесс называют перегрузкой операции.

Вложенные типы — это классы, структуры, перечисления и интерфейсы, определенные в пределах тела класса. Они позволяют скрыть типы, которые используются только пределах класса. Подобно тому, как вспомогательные методы объявляются закрытыми для уменьшения внешней сложности класса, вложенные типы помогают снизить количество классов, необходимых для сборки программы.

Класс Object

Классы .NET изначально унаследованы от System.Object. Фактически, если не указывать базовый класс при определении нового клас­са, компилятор автоматически предполагает, что он наследуется от Object. Все классы, которые мы виде­ли до сих пор, на самом деле унаследованы от System.Object. (Как упоминалось выше, для структур это наследование непрямое. Структуры всегда наследуются от System. ValueType, который, в свою очередь, унаследован от System.Object.)

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

Структуры

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

Рассмотрим пример:

class Dimensions

public double Length;

public double Width;

Этот код определяет класс Dimensions, который просто сохраняет длину и ширину некоторого элемента. Все, что нужно — два числа, которые удобнее хранить вместе, чем по отдельности. Как упоминалось ранее единственное, что нужно изменить в чтобы сделать этот тип структурой вместо класса — это просто заменить ключевое class на struct:

struct Dimensions

public double Length;

public double Width;

Определение функций для структур выглядит точно так же, как определение для классов. Следующий код демонстрирует структуру с конструктором и свойством:

struct Dim

{

public int a;

public int b;

Dim(int new_a, int new_b)

{

a = new_a;

b = new_b;

}

public int area

{

get

{

return a*b;

}

}

}

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

Отличия структур от классов заключаются в следующем:

· Структуры — это типы значений, а не ссылочные типы. Это значит, что он либо сохраняются в стеке, либо являются встроенными (последнее — если они являются частью другого объекта, хранимого в куче), и имеют те же ограниче­ния времени жизни, что и простые типы данных.

· Структуры не поддерживают наследование.

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

Поскольку структуры на самом деле предназначены для группирования данных, в большинстве случаев все их поля объявляются общедоступными. Строго говоря, это противоречит рекомендациям по написанию кода .NET. Согласно Microsoft, поля (кроме константных) всегда должны быть приватными и помещенными в оболочки общедоступных свойств. Однако что касается простых структур, многие разработчики считают применение общедоступных полей допустимой практикой программирования.

Стоит отметить, что реализация структур C++ и С# серьезно отличается, от реализации классов. В этом смысле ситуация совсем не похожа на C++, где структу­ры и классы - практически одно и тоже.

Допишем программу:

class Program

{

static void Main(string[] args)

{

Dim dim_1;

dim_1.b = 5;

dim_1.a = 6;

Console.WriteLine(dim_1.area);

Console.ReadLine();

}

}

Хотя структуры относятся к типам значений, очень часто их можно трактовать так же, как классы. Например, имея определение класса Dim из предыдущего примера, можно написать:

Dim dim_1 = new Dim();

dim_1.b = 5;

dim_1.a = 6;

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

Dim dim_1;

dim_1.b = 5;

dim_1.a = 6;

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

Структуры следуют тем же правилам, что и все другие типы данных: все должно быть инициализировано перед использованием. Структура рассматривается как полностью инициализированная, когда для ее создания была вызвана операция new, или же когда всем ее полям индивидуально были присвоены значения. И естественно структура, определенная как поле класса, инициализируется автоматическим обнуле­нием при инициализации ее включающего объекта.

Тот факт, что структуры — это типы значений, оказывает влияние на производительность. В зависимости от того, как использовать структуры, это может быть и плохо и хорошо. Положительным является то, что выделение памяти для структур происходит очень быстро, потому что они встроены или размещаются в стеке. То же самое касается удаления структур при выходе из контекста. С другой стороны, всякий раз когда вы передаете структуру в виде параметра, или присваиваете одну структуру другой (как в А=В, когда А и В — структуры), то происходит копирование всего содержимого структуры, в то время как в случае с классами копируется только ссылка. Это приводит к потерям производительности, которые зависят от размера структуры — это подчеркивает тот факт, что структуры на самом деле предназначены для хранения небольших порций данных. Отметим, однако, что когда структура передается методу в виде параметра, этих потерь производительности можно избежать, применяя ref - параметры — при этом передается только адрес в памяти, где находится структура. Это так же быстро, как и передача объекта класса. С другой стороны, если так делать, то необходимо помнить, что вызываемый метод может в принципе изменить значение переданной структуры.

Методы

OUT

Любой аргумент, передаваемый как ссылочный параметр, должен поддерживать присва­ивание значения (т.е. он не может быть, к примеру, Константой). Swap(ref 10, ref 20) так как числу 10 или 20 нельзя присвоить значение.

До данного момента требовалось, чтобы любая переменная, используемая как аргумент метода, в начале инициализировалась. Например, рассмотрим следующий фрагмент кода:

using System;

class c

{

public static void Main()

{

int a;

m(a);

Console.WriteLine(a);

Console.ReadLine();

}

public static void m(int new_value)

{

new_value=100;

}

}

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

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

Использование данного ключевого слова демонстрируется в с следующей программе:

using System;

class c

{

public static void Main()

{

int a;

m(out a);

Console.WriteLine(a);

Console.ReadLine();

}

public static void m( out int new_value)

{

new_value=100;

}

}

Конструкторы экземпляра

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

Определение конструктора экземпляра представлено в следующем синтаксическом блоке

Определение_конструктора_экэемпляра::=

[<Спецификатор_конструктора>] <Идентификатор__конструктора> ([<Список_формальных_параматров> ] )

[<Инициализатор_конструктора> ]

<Тело_конструктора>

где:

<Спецификатор_конструктора>

::= private

;:= public

::= protected

::= internal

<Инициализатор_конструктора>

::= : base ( [<Список_аргументов>] )

::= : this ( [<Список_аргументов>] )

{

<Операторы>

}

Стоит отметить, что Идентификатор_конструктора должен быть таким же, как идентификатор его класса. Скажем, конструктор класса Carтакже должен называться Car. Конструктор экземпляра не возвращает значения, поэтому тип возвращаемого зна­чения не указывается (не используется даже ключевое слово void).<Инициализатор_конструктора> вызывает исполнение другого конструктора экзем­пляра до того как исполнить операторы исходного.

Чтобы включить конструктор в класс, необходимо разместить его внутри блока опреде­ления класса вместе с другими элементами.

С конструктором экземпляра может применяться необязательный спецификатор доступности privateили public.Он управляет доступностью конструктора так же, как переменных и методов экземпляра, однако имеются и различия

Аналогично тому как в заголовке обычного метода, в объявлении конструктора экземпляра используются круглые скобки, включающие список формальных параметров. Значения аргументов передаются этим параметрам во время создания объекта с помощью ключевого слова new.

Подобно своим методам, конструктор экземпляра может быть перегружен путем объявления нескольких конструкторов одного класса с разными списками фор­мальных параметров.

Если класс содержит несколько конструкторов экземпляра (которые, таким образом, являются перегруженными), можно дополнительно указать конструктор того же клас­са, который будет исполняться перед операторами объявленного конструктора. Для этого после списка формальных параметров размешается инициализатор конструктора: this ( [<Список_аргументов>] ). Среда исполнения запускает тот конструктор экземпляра класса, список формальных параметров которого совпадает со списком аргументов ини­циализатора.

Конструктор экземпляра содержит тело, подобное телу метода. Это означает, что оно может состоять из пустого оператора (точка с запятой) или группы операторов, заклю­ченных в пару фигурных скобок. При вызове конструктора операторы исполняются в той же последовательности, как и при вызове метода. Однако они должны содержать только команды, непосредственно связанные с инициализацией и созданием объекта.

Статические конструкторы

В противоположность конструкторам экземпляра конструктор, объявленный static, не может быть вызван в исходном коде программы явно. Он автоматически вызывается средой исполнения в промежутке между запуском программы и порождением первого объекта класса. Статический конструктор объявляется с помощью ключевого слова static перед именем класса и пустых круглых скобок после него (см. синтаксический блок 13.3). Операторы расположены в блоке после заголовка.

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

Статический конструктор используется не только для инициализации статических переменных-членов, но и для других действий, которые должны предшествовать созданию первого экземпляра класса.

Рассмотрим соответствующий синтаксический блок.

static <идентификатор_конструктора> ( )

{

<Операторы>

}

Примечания

• <Идентификатор_конструктора> должен совпадать с именем его класса.

• Конструктор static не имеет аргументов.

• Конструктор static не имеет спецификаторов доступности.

Пример:

class Account

{

private static decimal balance;

static Account()

{

balance = 0;

}

}

Конструкторы структур

Можно объявлять конструкторы структур точно так же, как это делается для классов за исключением того, что не разрешается определять конструкторы без параметров. Это может показаться бессмысленным, но причина скрыта в реализации исполняющей системы .NET. Бывают некоторые редкие случаи, когда .NET не в состоянии вызвать определенный вами конструктор без параметров. Microsoft пошли по пути наименьшего сопротивления, запретив пользовательские конструкторы без параметров для структур в С#. Таким образом, конструктор по умолчанию, который инициализирует все поля нулевыми значениями, всегда присутствует неявно, даже если применяете другие конструкторы с параметрами. Также невозможно обойти конструктор, по умолчанию определяя начальные значения полей.

Деструкторы. Сборка мусора

Классы задаются в исходном коде до исполнения программы, а объекты создаются динамически, на этапе исполнения посредством операции 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

 
  Ссылочные параметры ref и out - student2.ru

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

 
  Ссылочные параметры ref и out - student2.ru

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

accountl = new Account();

account2 = new Account();

accountl = account2;

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

accountl = new Account();

account2 = new Account();

   
  Ссылочные параметры ref и out - student2.ru
 
  Ссылочные параметры ref и out - student2.ru

accountl = account2;

 
  Ссылочные параметры ref и out - student2.ru

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

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

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

bankl = new Bank();

 
  Ссылочные параметры ref и out - student2.ru

bankl.SetAccount(new Account());

 
  Ссылочные параметры ref и out - student2.ru

bankl = null;

 
  Ссылочные параметры ref и out - 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. Важно, что хотя бы один из них дол­жен присутствовать в свойстве.

Индексаторы

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

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