Перегрузка методов. Вызов методов с одинаковым именем и разными аргументами
Для того чтобы лучше понять, что такое перегрузка методов необходимо рассмотреть пример вычисления расстояния между двумя точками на плоскости. Предположим, что на плоскости расположены две точки с координатами: и . Воспользовавшись простейшим правилом геометрии расстояние между этими точками будет равно: Ниже представлен код программы.
Предположим, что нам необходимо написать программу вычисляющую значение длинны отрезка с параметрами типа int. Код будет выглядеть следующим образом:
class Plane
{
…
public double DistanceInt(int x1, int yl, int x2, int y2)
{
return Math.Sqrt(Math.Pow(x1-x2,2) + Math.Pow(y1-y2. 2));
}
…
}
Список формальных параметров метода DistanceIntсодержит четыре значения типаint(или неявно преобразуемого в него). Однако если потребуется найти расстояние для четырех аргументов типа double , будет необходимо создать другой метод класса Plane, DistanceDouble,который выглядит следующим образом:
class Plane
{
…
public double DistanceDouble(double x1, double yl, double x2, double y2)
{
return
Math.Sqrt(Math.Pow(x1-x2,2) + Math.Pow(y1-y2. 2));
}
…
}
Возможен еще и другой случай, когда для представления точек на плоскости используется некий класс Point. Каждый объект этого класса содержит две закрытые переменные экземпляра . Для доступа к ним применяются соответствующие методы (асессор и мутатор).
class Point
{
private int x = 0;
private int y = 0;
public void setX(int newX)
{
x = newX;
}
public void setY(int newY)
{
y=newY;
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
}
Теперь для расчета расстояния (с помощью методов созданных ранее) удобнее указать четыре числовых аргумента и два объекта :
public double DistanceObjects(location L1, location L2)
return
Math.Sqrt(Math.Pow(L1.getX()-L2.getX(),2)+Math.Pow(L1.getY() - L2.getY(),2));
}
Из написанных кодов можно сделать следующий вывод, что три метода с разными именами, по существу, решают одну задачу — вычисляют расстояние. Разумеется, было бы проще и естественнее (независимо от типа аргументов — int, double или Location) использовать одно имя Distance.
При этом компилятор автоматически выбирает правильный путь для обработки аргументов. Такой подход возможен благодаря перегрузке метода.
Эта концепция позволяет определять в одном классе несколько различных методов с одним именем, но разными наборами формальных параметров и реализацией. Для перегрузки метода в данном случае (чтобы три приведенных вызова были корректными вместе с возвращаемыми ими значениями) достаточно просто присвоить всем трем методам одно имя — Distance. Тело каждого из них останется без изменений, а заголовки будут выглядеть так:
public double Distance(int x1, int y1, int х2, int y2)
public double Distance{double x1, double yl. double x2, double y2)
public double Distance(Location LI, Location L2)
Теперь, когда компилятор встречает оператор Plane.Distance(10, 10, 20, 30), он совершает следующие действия. Вначале в классе Plane производится поиск метода с именем Distance.Компилятор находит три таких метода. Он выбирает тот из них, чей набор формальных параметров совпадает с аргументами в вызове. Такое совпадение требует, чтобы:
1 Число аргументов в вызове было равно числу формальных параметров.
2 Каждый аргумент имеет тип, совместимый с типом соответствующего ему формального параметра.
Следуя этой процедуре, компилятор выбирает метод Distance с заголовком:
public double Distance(int x1, int у1, int х2, int y2)
Приведем код программы, которая реализует данный подход:
using System;
class DistanceCalculator
{
public static void Main()
{
Plane myPlane = new Plane();
Point location1 = new Point();
Point location2 = new Point();
location1.setX(10);
location1.setY(10);
location2.setX(10);
location2.setY(20);
Console.WriteLine("integers: " + myPlane.Distance(5,10,5,30));
Console.WriteLine("doubles: " + myPlane.Distance(15.4, 20.6, 15.4, 30.60));
Console.WriteLine("objects:" + myPlane.Distance(location1, location2));
}
}
class Plane
{
public double Distance (int x1, int y1, int x2, int y2)
{
Console. WriteLine("\nUsing the integer version");
return Math.Sqrt( Math.Pow(x1-x2,2) + Math.Pow(y1-y2,2));
}
public double Distance (double x1, double y1, double x2, double y2)
{
Console.WriteLine("\nUsing the double version");
return Math.Sqrt( Math.Pow(x1-x2, 2) + Math.Pow(y1-y2, 2));
}
public double Distance ( Point L1, Point L2)
{
Console.WriteLine("\nUsing the Location objects version");
return Math.Sqrt(Math.Pow(L1.getX()-L2.getX(),2)+Math.Pow(L1.getY() - L2.getY(),2));
}
}
class Point
{
private int x = 0;
private int y = 0;
public void setX(int newX)
{
x = newX;
}
public void setY(int newY)
{
y=newY;
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
}
В классе Planeопределены три метода с одним именем, но с различными наборами формальных параметров. Три вызова Distance содержат аргументы, согласующиеся с наборами формальных параметров и, следовательно, с различными реализациями перегруженного метода Distance.В каждом методе содержится оператор WriteLine,выводящий информацию о том, какой конкретно метод исполняется.
С перегрузкой метода связана важная концепция — сигнатура метода. Имя метода вместе с числовым типом и порядком формальных параметров составляют сигнатуру метода.
Однако стоит отметить следующие исключения.
Возвращаемое значение не включено в сигнатуру метода. Таким образом, следующие три заголовка имеют одинаковую сигнатуру (несмотря на то, что первый возвращает значение double,второй int, а третий не возвращает значения):
public double Sum (double a, double b)
public int Sum (double a, double b)
public void Sum (double a, double b)
Ключевое слово paramsвсигнатуре метода игнорируется. Следовательно, следующие два заголовка имеют одинаковую сигнатуру:
public double Sum (params double[] numbers)
public double Sum (double[] numbers)
Имена формальных параметров не включаются в сигнатуру метода. Сигнатуры двух следующих методов совпадают:
public int Sum (double x, double y)
public int Sum (double a, double b)
Все методы в классе должны иметь разные сигнатуры. Поскольку имя метода — один из нескольких атрибутов, определяющих сигнатуру метода, в одном классе можно определить несколько методов с одним именем при условии, что остальные элементы сигнатур различаются. Такие методы называются перегруженными,
Перегруженные методы широко используются в классах .NET.
Рассмотрим, как согласуется перегрузка методов и неявное преобразование.
Конструкторы экземпляра
Конструктор экземпляра класса представляет собой специальный метод, запускаемый по ключевому слову new. Он используется для инициализации переменных и других операций при создании объекта.
Определение конструктора экземпляра представлено в следующем синтаксическом блоке
Определение_конструктора_экэемпляра::=
[<Спецификатор_конструктора>] <Идентификатор__конструктора> ([<Список_формальных_параматров> ] )
[<Инициализатор_конструктора> ]
<Тело_конструктора>
где:
<Спецификатор_конструктора>
::= private
;:= public
::= protected
::= internal
<Инициализатор_конструктора>
::= : base ( [<Список_аргументов>] )
::= : this ( [<Список_аргументов>] )
{
<Операторы>
}
Стоит отметить, что Идентификатор_конструктора должен быть таким же, как идентификатор его класса. Скажем, конструктор класса Carтакже должен называться Car. Конструктор экземпляра не возвращает значения, поэтому тип возвращаемого значения не указывается (не используется даже ключевое слово void).<Инициализатор_конструктора> вызывает исполнение другого конструктора экземпляра до того как исполнить операторы исходного.
Чтобы включить конструктор в класс, необходимо разместить его внутри блока определения класса вместе с другими элементами.
С конструктором экземпляра может применяться необязательный спецификатор доступности privateили public.Он управляет доступностью конструктора так же, как переменных и методов экземпляра, однако имеются и различия
Аналогично тому как в заголовке обычного метода, в объявлении конструктора экземпляра используются круглые скобки, включающие список формальных параметров. Значения аргументов передаются этим параметрам во время создания объекта с помощью ключевого слова new.
Подобно своим методам, конструктор экземпляра может быть перегружен путем объявления нескольких конструкторов одного класса с разными списками формальных параметров.
Если класс содержит несколько конструкторов экземпляра (которые, таким образом, являются перегруженными), можно дополнительно указать конструктор того же класса, который будет исполняться перед операторами объявленного конструктора. Для этого после списка формальных параметров размешается инициализатор конструктора: this ( [<Список_аргументов>] ). Среда исполнения запускает тот конструктор экземпляра класса, список формальных параметров которого совпадает со списком аргументов инициализатора.
Конструктор экземпляра содержит тело, подобное телу метода. Это означает, что оно может состоять из пустого оператора (точка с запятой) или группы операторов, заключенных в пару фигурных скобок. При вызове конструктора операторы исполняются в той же последовательности, как и при вызове метода. Однако они должны содержать только команды, непосредственно связанные с инициализацией и созданием объекта.