немедленному выходу из метода.
Оператор return может применяться и в методах, имеющих возвращаемый тип void
, для немедленного выхода их метода. В этом случае, никакого значения после оператора return не указывается.
Работа с данными через методы, а не напрямую, позволяет создавать более надёжные программы, а также производить первичную обработку данных. Напри- мер, в методе
SetSType из заданного строкового представления типа фигуры уда- ляются все концевые пробелы и осуществляется контроль, чтобы данное поле имело хоть какое-нибудь значение.
Внесение изменений в класс изменит способы работы с полем sType
:
Figure firstFigure = new Figure();
// firstFigure.sType = "Квадрат"; // Теперь этот оператор недопустим firstFigure.SetSType("Квадрат"); // Правильный способ задать значение
Метод может возвращать не только переменные типов значения, но и создан- ные в методе объекты (в том числе строки, массивы, т.е. элементы любого ссылоч-
ного типа). Например, дополним класс
Figure методом дублирования объекта:
51 public Figure Duplicate()
{
Figure tempFigure = new Figure(); tempFigure.sType = sType; tempFigure.posX = posX; tempFigure.posY = posY; return tempFigure;
}
Объект, созданный в методе и возвращаемый из него
не уничтожается при выходе из метода. Он будет уничтожен, как только не будет ссылок на него после окончания работы метода.
Параметры, указанные в описании метода, называются
формальными пара- метрами (параметр n_sType в методе
SetSType
), в то время как параметры, ука- занные при вызове метода –
фактическими (например, слово «Квадрат» в послед- ней строки примера, показанного выше). При выполнении метода каждому фор- мальному параметру присваивается значение фактического, однако метод присваи- вания зависит от
модификатора параметра:
если модификатор
не указан, то формальному параметру присваивается копия фактического. Поэтому изменение формального параметра внутри метода не ска- жется на значении фактического параметра. В качестве фактического параметра может выступать переменная, константное значение, результат вычислений и т.д.;
ref
– параметр передаётся по ссылке, поэтому изменение формального параметра в методе приведёт к изменению фактического параметра. В качестве фактическо- го параметра должна использоваться
инициализированная переменная. При вы- зове метода перед фактическим параметром также указывается ключевое слово ref
;
out
– параметр передаётся по ссылке, поэтому изменение формального параметра в методе приведёт к изменению фактического параметра. В качестве фактическо- го
параметра должна использоваться переменная, которая может быть
неинициа-лизированной. В методе формальному параметру
должно быть присвоено зна- чение. При вызове метода перед фактическим параметром также указывается ключевое слово out
;
params
– позволяет определить параметр метода, принимающий аргумент, в ко- тором количество аргументов является переменным. В объявлении метода после ключевого слова params дополнительные параметры не допускаются, и в объяв- лении метода допускается только одно ключевое слово params
. Параметр должен быть одномерным массивом, а все фактические параметры должны быть совме- стимы с типом элемента массива. Например: class MyClass
{ public int Sum(params int[] mas)
{ int sum=0; for (int i=0; i
52 return sum;
}
}
MyClass c = new MyClass(); int[] m = new int[4] {5,6,7,8}; int s1 = c.Sum(m); // s1 = 26 int s2 = c.Sum(1, 2, 3); // s2 = 6 int s3 = c.Sum(1, 2.1, 3); // ошибка
У метода идентификаторы формальных параметров могут совпадать с иден- тификаторами полей класса, к которому принадлежит метод. В этом случае, внутри метода видимым является формальный параметр, а для доступа к полю класса мо- жет быть использовано ключевое слово
this, например: class MyClass
{ private int x; public void SetX(int x)
{ this.x = x;
}
}
Ключевое слово this означает объект, который вызвал метод, поэтому оно может быть использовано в любом методе класса, например, в конструкторе.
3.5.1Перегрузка методов Язык C# допускает наличие в классе нескольких методов с одинаковым иден- тификатором. Такое описание называется
перегрузкой методов. В этом случае все методы с одинаковым идентификатором должны отличаться количеством и/или ти- пом параметров. Это
требование необходимо для того, чтобы компилятор мог рас- познать необходимый для применения метод. Если ни один метод не подходит для заданного набора параметров, то программа не может быть скомпилирована.
Пример: класс, в котором определено вещественное поле и перегруженный метод, позволяющий выдавать значение этого поля в разных форматах. class MyClass
{ public double d; public string GetD()
{ return d.ToString();
} public string GetD(int n)
{ return String.Format("{0:F"+n+"}",d);
}
53 public string GetD(int n, char c)
{ return String.Format("{0:"+c+n+"}", d);
}
}
MyClass c = new MyClass(); c.d = 1234.567; string s1 = c.GetD(); // s1 = "1234,567" string s2 = c.GetD(2); // s2 = "1234,57" string s3 = c.GetD(2,'N'); // s3 = "1 234,57"
При перегрузке методов возможно изменение возвращаемого типа, но только с изменением типа и/или количества параметров.
Пример: класс, имеющий перегруженный метод, выполняющий целочислен- ное или обычное деление одного числа на другое в зависимости от их типа. class MyClass
{ public int Divide(int a, int b)
{ return a/b;
} public double Divide(double a, double b)
{ return a/b;
}
}
MyClass c = new MyClass(); int i = c.Divide(7,2); // i = 3 double d1 = c.Divide(7,2); // d1 = 3 double d2 = c.Divide(7,2.0); // d2 = 3.5
Перегруженные методы могут иметь одинаковое количество и тип параметров в случае, если в одной реализации метода часть параметров передаётся по ссылке
(
ref или out
), а в другой – по значению. public void Method(int a) {...}; public void Method(ref int a) {...};
Однако нельзя перегрузить метод, если в одной реализации передача осу- ществляется по ссылке out
, а в другой – по ссылке ref public void Method(out int a) {...}; public void Method(ref int a) {...};
54
3.5.2Новое в версии C# 4.0 Начиная с версии C# 4.0 параметрам метода, конструктора или индексатора могут быть заданы значения по умолчанию. Это позволяет делать параметры необя- зательными. Для указания значения по умолчанию необходимо при описании пара- метра присвоить ему значение, например: public class MyClass
{ public int Method(int a = 1, int b = 1, int c = 1)
{ return a * b * c;
}
}
MyClass cl = new MyClass(); int i1 = cl.Method(2, 3, 4); // i1 = 24 int i2 = cl.Method(5, 6); // i2 = 30 int i3 = cl.Method(7); // i3 = 7 int i4 = cl.Method(); // i4 = 1
Если в методе требуются обязательные и необязательные параметры, то в опи- сании метода обязательные параметры
должны указываться первыми, т.е. описа- ние метода public int Method(int a = 1, int b, int c = 1) {return a * b * c;} будет недопустимым.
Введение для параметров значений по умолчанию может привести к сложно- стям при использовании перегрузки методов, например: public class MyClass
{ public int Method(int a, int b = 1)
{ return a * b;
} public double Method(int a, double b = 1.0)
{ return a * b;
}
}
MyClass cl = new MyClass(); int i = cl.Method(2, 3); // i = 6 double d = cl.Method(2, 3.0); // d = 6.0 d = cl.Method(2); // Неоднозначность i = cl.Method(2); // Неоднозначность
В строке d = cl.Method(2);
возникает неоднозначность, т.к.
компилятор не мо- жет определить, какой из перегруженных методов использовать. Изменение типа
55 возвращаемого значения для сужения диапазона возможных значений
(
i = cl.Method(2)
) также не приведёт к устранению неоднозначности. В таких случаях, лучше не использовать значения по умолчанию в перегружаемых методах.
Ещё одним новшеством при работе с методами является возможность исполь- зовать имена параметров при вызове метода. Формат описания параметра при этом имеет вид:
<идентификатор параметра> : <значение>
Применение именованных параметров позволяет указывать их не в той после- довательности, в которой они описаны в методе, например: public class MyClass
{ public int Method(int a, int b)
{ return a - b;
}
}
MyClass cl = new MyClass(); int i1 = cl.Method(5, 3); // i1 = 2 int i2 = cl.Method(a: 5, b: 3); // i2 = 2 int i3 = cl.Method(b: 5, a: 3); // i3 = -2 int i4 = cl.Method(5, b: 3); // i4 = 2 int i5 = cl.Method(5, a: 3); // Ошибка
Строка (
int i5 = cl.Method(5, a: 3)
) недопустима, т.к. в ней выполня- ется повторное присваивание значения параметру «
a
».
Именованные параметры могут применяться в комбинации с значениями по умолчанию, что позволяет указывать при вызове метода только требуемые парамет- ры, например: public class MyClass
{ public double Method(double a = 1.0, double b = 1.0, double c = 1.0)
{ return (a - b) / c;
}
}
MyClass cl = new MyClass(); double d1 = cl.Method(a: 2); // d1 = 1 double d2 = cl.Method(b: 2); // d2 = -1 double d3 = cl.Method(c: 2); // d3 = 0
56
3.6
Конструкторы
Конструкторы представляют собой специальные методы, вызываемые при со- здании объекта. Каждый класс имеет конструктор по умолчанию
1
, не имеющий па- раметров и устанавливающий значения всех членов-данных в «нулевое» состояние
(если не переопределён), а также может иметь произвольное количество конструк- торов с разным набором параметров.
Основное назначение конструктора – инициализация членов-данных и подго- товка объекта к работе.
Формальная схема конструктора имеет вид:
<доступ> <имя_класса>([<список параметров>])
{
<тело конструктора>
}
Чаще всего
<доступ>
имеет значение public
, т.к. конструктор вызывается при создании объектов из-за пределов класса.
В классе может быть описано несколько конструкторов, т.е. конструкторы, как и любые методы, могут быть перегружены.
Пример: дополним класс
Figure конструкторами с разными наборами пара- метров. Будем считать, что значения по умолчанию для полей posX
, posY
– 1, а для поля sType
– «Не задан». Инициализацию полей posX
, posY
, для примера, будем проводить только в конструкторах. class Figure
{ private string sType="Не задан"; public int posX, posY; public string GetSType()
{ return sType;
} public void SetSType(string n_sType)
{ if (n_sType.Trim() != "") sType = n_sType.Trim();
} public Figure Duplicate()
{ return new Figure(sType,posX,posY);
} public Figure() // Переопределение конструктора по умолчанию
{ posX = posY = 1;
} public Figure(string n_sType) // Конструктор с одним параметром
1
Конструктор по умолчанию скрывается, если определен хотя бы один другой конструктор.
57
{
SetType(n_sType); posX = posY = 1;
}
// Конструктор с тремя параметрами public Figure(string n_sType, int n_posX, int n_posY)
{
SetType(n_sType); posX = n_posX; posY = n_posY;
}
}
// Вызов конструктора по умолчанию
Figure figure1 = new Figure();
// Вызов конструктора с одним параметром
Figure figure2 = new Figure("Квадрат");
// Вызов конструктора с тремя параметрами
Figure figure3 = new Figure("Круг",5,6);
// Ошибка: конструктора с одним целочисленным параметром нет
// Figure figure4 = new Figure(5);
Также в приведённом выше примере был изменён метод
Duplicate
, т.к. имея конструктор с параметрами можно легко создать объект с копией свойств.
Если у класса определено несколько конструкторов, то один из них может вы- зывать другой. Для этого используется конструкция вида:
<доступ> <имя_класса>([<список параметров1>]) : this([<список параметров2>])
{
<тело конструктора>
}
Здесь
<список параметров1>
определяет набор формальных параметров текущего конструктора, а
<список параметров2>
– набор фактических парамет- ров вызываемого конструктора. При этом, в
<список параметров2>
могут ис- пользоваться параметры из
<список параметров1>
В некоторых случаях
<тело конструктора>
может оставаться пустым, т.к. все действия выполняются в другом вызываемом конструкторе.
Например, конструкторы без параметров и с одним параметром из приведён- ного выше примера могут быть описаны с использованием конструктора с тремя па- раметрами следующим образом: public Figure() : this("Не задан",1,1)
{
} public Figure(string n_sType) : this(n_sType,1,1)
{
}
58
Если один конструктор вызывает другой, то сначала выполняется вызываемый конструктор, а потом вызывающий.
В отличии от некоторых других языков программирования в C# отсутствует конструктор копии, но его можно создать самостоятельно. Например, вышеприве- дённый класс может быть дополнен таким конструктором: public Figure(Figure sourceFigure)
{ sType = sourceFigure.sType; posX = sourceFigure.posX; posY = sourceFigure.posY;
}
Реализованный конструктор копии в сочетании с ключевым словом this поз- воляет улучшить метод
Duplicate следующим образом: public Figure Duplicate()
{ return new Figure(this);
}
Применение конструктора копий целесообразнее, чем
просто конструктора с параметрами, т.к. по определению этот конструктор должен полностью копировать все члены-данные существующего объекта, в то время, как любой конструктор с па- раметрами может использовать только часть членов-данных класса.
3.7Деструкторы В языке С# имеется возможность определить метод, который будет вызывать- ся непосредственно перед окончательным уничтожением объекта. Такой метод называется
деструктором и может использоваться в ряде особых случаев.
Формальная схема деструктора имеет вид:
<имя_класса>()
{
<тело деструктора>
}
Деструктор вызывается непосредственно перед «сборкой мусора». Это означа- ет, что заранее нельзя знать, когда именно следует вызывать деструктор
1
. Кроме то- го, программа может завершиться до того, как произойдёт «сборка мусора», а следо- вательно, деструктор может быть вообще не вызван.
Как правило, деструктор должен воздействовать только на переменные экзем- пляра, определённые в его классе.
1
Существует возможность принудительно выполнить «сборку мусора», вызвав метод Collect, но в боль- шинстве случаев этого следует избегать, потому что это может привести к проблемам с производительностью.
59
Деструктор не имеет возвращаемого параметра, спецификатора доступа, спис- ка принимаемых параметров и не может быть перегружен.
В силу редкого применения деструкторов в С#, для класса
Figure описывать деструктор не будем.
3.8
Инициализаторы объектов
Задание начальных свойств объекта возможно при его создании, даже если класс не имеет конструкторов с параметрами. Для этого используется структура с
инициализаторами, формальный вид которой: new <идентификатор класса>[(<параметры>)]
{<идентификатор 1>=<значение 1> [,<идентификатор 2>=<значение 2> ...
]}; где
<идентификатор N>
– идентификатор поля или свойства;
<значение N>
– значение, присваиваемое полю или свойству.
При создании объекта с помощью инициализаторов сначала вызывается тре- буемый конструктор, а затем указанным полям и свойствам присваиваются задан- ные значения. Порядок следования полей значения не имеет.
Пример: создания объекта класса
Figure с начальным значением posY
рав- ным 10 с помощью инициализаторов.
Figure firstFigure = new Figure {posY=10};
// sType="Не задан", posX=1, posY=10
Figure firstFigure = new Figure("Круг") {posY=10};
// sType="Круг", posX=1, posY=10
3.9
Свойства
Свойство – это программная конструкция, обладающая следующими характе- ристиками:
свойство объединяет поле с методами доступа к нему, что обеспечивает контроль за правильностью работы с ним и создание в целом более надёжного кода;
свойство выглядит для использующего его как поле. Это упрощает синтаксис за счёт лёгкого использования свойства в выражениях и задания значения свойства с помощью операторов присваивания.
Формальная схема описания свойства имеет вид:
[<доступ>] <тип> <идентификатор> {get {<код метода доступа get>} set
{<код метода доступа set>}}
60 где:
<код метода доступа get>
– код, определяющий действия, выполняемые при запросе значения свойства;
<код метода доступа set>
– код, определяющий действия, выполняемые при установке значения свойства.
Метод доступа get должен содержать оператор return
, определяющий зна- чение, возвращаемое методом get при запросе значения свойства.
Метод доступа set имеет неявно заданный параметр value
, который позво- ляет указать значение, присваиваемое свойству.
Однако свойство само по себе не хранит никакого значения, поэтому оно должно либо быть соотнесено с некоторым полем, объявленным в классе (как пра- вило, с спецификатором доступа private
), либо быть расчётным на основе других членов класса.
Пример: перепишем класс
Figure с применением свойств. Будут использова- ны следующие методы именования идентификаторов: поля класса начинаются с символа «_»; свойства – идентификатор поля без символа «_». На положение фигу- ры накладывается ограничение: координаты не могут быть отрицательными. class Figure
{ private string _sType; private int _posX, _posY; public string sType
{ get { return _sType; } set { if (value.Trim() != "") _sType = value.Trim(); }
} public int posX
{ get { return _posX; } set { if (value >= 0) _posX = value; }
} public int posY
{ get { return _posY; } set { if (value >= 0) _posY = value; }
} public Figure Duplicate()
{ return new Figure(this);
} public Figure() : this("Не задан",1,1)
{
} public Figure(string n_sType) : this(n_sType,1,1)
{
} public Figure(string n_sType, int n_posX, int n_posY)
{
_sType = n_sType.Trim() != "" ? n_sType.Trim() : "Не задан";
61
_posX = n_posX >= 0 ? n_posX : 1;
_posY = n_posY >= 0 ? n_posY : 1;
} public Figure(Figure sourceFigure)
{
_sType = sourceFigure._sType;
_posX = sourceFigure._posX;
_posY = sourceFigure._posY;
}
}
Figure firstFigure = new Figure();
// Теперь этот оператор снова допустим, но контроль ввода также
// проводится firstFigure.sType = "Квадрат";
Если при описании свойства не указан метод set
, то свойство доступно толь- ко для чтения, а если метод get
– то только для записи.