Выбрать главу

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

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

Как и методы, конструкторы также могут перегружаться. Это дает возможность конструировать объекты самыми разными способами. В качестве примера рассмотрим следующую программу. // Продемонстрировать перегрузку конструктора. using System; class MyClass { public int x; public MyClass() { Console.WriteLine("В конструкторе MyClass()."); x = 0; } public MyClass(int i) { Console.WriteLine("В конструкторе MyClass(int)."); x = i; } public MyClass(double d) { Console.WriteLine("В конструкторе MyClass(double)."); x = (int) d; } public MyClass(int i, int j) { Console.WriteLine("В конструкторе MyClass(int, int)."); x = i * j; } } class OverloadConsDemo { static void Main() { MyClass t1 = new MyClass(); MyClass t2 = new MyClass(88); MyClass t3 = new MyClass(17.23); MyClass t4 = new MyClass(2, 4); Console.WriteLine("t1.x: " + t1.x); Console.WriteLine("t2.х: " + t2.x); Console.WriteLine("t3.x: " + t3.x); Console.WriteLine("t4.x: " + t4.x); } }

При выполнении этой программы получается следующий результат. В конструкторе MyClass(). В конструкторе MyClass(int). В конструкторе MyClass(double). В конструкторе MyClass(int, int). t1.x: 0 t2.x: 88 t3.x: 17 t4.x: 8

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

Одна из самых распространенных причин для перегрузки конструкторов заключа ется в необходимости предоставить возможность одним объектам инициализировать другие. В качестве примера ниже приведен усовершенствованный вариант разработан ного ранее класса Stack, позволяющий конструировать один стек из другого. // Класс для хранения символов в стеке. using System; class Stack { // Эти члены класса являются закрытыми. char[] stck; // массив, содержащий стек int tos; // индекс вершины стека // Сконструировать пустой объект класса Stack по заданному размеру стека. public Stack(int size) { stck = new char[size]; // распределить память для стека tos = 0; } // Сконструировать объект класса Stack из существующего стека. public Stack(Stack ob) { // Распределить память для стека. stck = new char[ob.stck.Length]; // Скопировать элементы в новый стек. for(int i=0; i < ob.tos; i++) stck[i] = ob.stck[i]; // Установить переменную tos для нового стека. tos = ob.tos; } // Поместить символы в стек. public void Push(char ch) { if (tos==stck.Length) { Console.WriteLine(" - Стек заполнен."); return; } stck[tos] = ch; tos++; } // Извлечь символ из стека. public char Pop() { if (tos==0) { Console.WriteLine(" - Стек пуст."); return (char) 0; } tos--; return stck[tos]; } // Возвратить значение true, если стек заполнен. public bool IsFull() { return tos==stck.Length; } // Возвратить значение true, если стек пуст. public bool IsEmpty() { return tos==0; } // Возвратить общую емкость стека. public int Capacity() { return stck.Length; } // Возвратить количество объектов, находящихся в настоящий момент в стеке. public int GetNum() { return tos; } } // Продемонстрировать применение класса Stack. class StackDemo { static void Main() { Stack stk1 = new Stack(10); char ch; int i; // Поместить ряд символов в стек stk1. Console.WriteLine("Поместить символы А-J в стек stk1."); for(i=0; !stk1.IsFull(); i++) stk1.Push((char) ('A' + i)); // Создать копию стека stck1. Stack stk2 = new Stack(stk1); // Вывести содержимое стека stk1. Console.Write("Содержимое стека stk1: "); while( !stk1.IsEmpty() ) { ch = stk1.Pop(); Console.Write(ch); } Console.WriteLine(); Console.Write("Содержимое стека stk2: "); while( !stk2.IsEmpty() ) { ch = stk2.Pop(); Console.Write(ch); } Console.WriteLine("\n"); } }

Результат выполнения этой программы приведен ниже. Поместить символы А-J в стек stk1. Содержимое стека stk1: JIHGFEDCBA Содержимое стека stk2: JIHGFEDCBA

В классе StackDemo сначала конструируется первый стек (stk1), заполняемый символами. Затем этот стек используется для конструирования второго стека (stk2). Это приводит к выполнению следующего конструктора класса Stack. // Сконструировать объект класса Stack из существующего стека. public Stack(Stack ob) { // Распределить память для стека. stck = new char[ob.stck.Length]; // Скопировать элементы в новый стек. for(int i=0; i < ob.tos; i++) stck[i] = ob.stck[i]; // Установить переменную tos для нового стека. tos = ob.tos; }

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

Вызов перегружаемого конструктора с помощью ключевого слова this Когда приходится работать с перегружаемыми конструкторами, то иногда очень по лезно предоставить возможность одному конструктору вызывать другой. В С# это дает ся с помощью ключевого слова this. Ниже приведена общая форма такого вызова. имя_конструктора(список_параметров1) : this(список_параметров2) { // ... Тело конструктора, которое может быть пустым. }

В исходном конструкторе сначала выполняется перегружаемый конструктор, спи сок параметров которого соответствует критерию список_параметров2, а затем все остальные операторы, если таковые имеются в исходном конструкторе. Ниже приве ден соответствующий пример. // Продемонстрировать вызов конструктора с помощью ключевого слова this. using System; class XYCoord { public int x, y; public XYCoord() : this(0, 0) { Console.WriteLine("В конструкторе XYCoord()"); } public XYCoord(XYCoord obj) : this(obj.x, obj.y) { Console.WriteLine("В конструкторе XYCoord(obj)"); } public XYCoord(int i, int j) { Console.WriteLine("В конструкторе XYCoord(int, int)"); x = i; у = j; } } class OverloadConsDemo { static void Main() { XYCoord t1 = new XYCoord(); XYCoord t2 = new XYCoord(8, 9); XYCoord t3 = new XYCoord(t2); Console.WriteLine("t1.x, t1.y: " + t1.x + ", " + t1.y); Console.WriteLine("t2.x, t2.y: " + t2.x + ", " + t2.y); Console.WriteLine("t3.x, t3.y: " + t3.x + ", " + t3.y); } }

Выполнение этого кода приводит к следующему результату. В конструкторе XYCoord(int, int) В конструкторе XYCoord() В конструкторе XYCoord(int, int) В конструкторе XYCoord(int, int) В конструкторе XYCoord(obj) t1.х, t1.у: 0, 0 t2.х, t2.у: 8, 9 t3.х, t3.у: 8, 9

Код в приведенном выше примере работает следующим образом. Единственным конструктором, фактически инициализирующим поля х и у в классе XYCoord, явля ется конструктор XYCoord(int, int). А два других конструктора просто вызывают этот конструктор с помощью ключевого слова this. Например, когда создается объект t1, то вызывается его конструктор XYCoord(), что приводит к вызову this(0, 0), который в данном случае преобразуется в вызов конструктора XYCoord(0, 0). То же самое происходит и при создании объекта t2.

Вызывать перегружаемый конструктор с помощью ключевого слова this полез но, в частности, потому, что он позволяет исключить ненужное дублирование кода. В приведенном выше примере нет никакой необходимости дублировать во всех трех конструкторах одну и ту же последовательность инициализации, и благодаря при менению ключевого слова this такое дублирование исключается. Другое преимуще ство организации подобного вызова перезагружаемого конструктора заключается в возможности создавать конструкторы с задаваемыми "по умолчанию" аргументами, когда эти аргументы не указаны явно. Ниже приведен пример создания еще одного конструктора XYCoord. public XYCoord(int х) : this(х, х) { }

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

Инициализаторы объектов предоставляют еще один способ создания объекта и ини циализации его полей и свойств. (Подробнее о свойствах речь пойдет в главе 10.) Если используются инициализаторы объектов, то вместо обычного вызова конструктора класса указываются имена полей или свойств, инициализируемых первоначально зада ваемым значением. Следовательно, синтаксис инициализатора объекта предоставляет альтернативу явному вызову конструктора класса. Синтаксис инициализатора объекта используется главным образом при создании анонимных типов в LINQ-вьгражениях. (Подробнее об анонимных типах и LINQ-вьгражениях — в главе 19.) Но поскольку ини циализаторы объектов можно, а иногда и должно использовать в именованном классе, то ниже представлены основные положения об инициализации объектов.