Книга: C# 4.0: полное руководство

Перегрузка методов

Перегрузка методов

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

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

Ниже приведен простой пример, демонстрирующий перегрузку методов.

// Продемонстрировать перегрузку методов.
using System;
class Overload {
  public void OvlDemo() {
    Console.WriteLine("Без параметров");
  }
  // Перегрузка метода OvlDemo с одним целочисленным параметром,
  public void OvlDemo(int a) {
    Console.WriteLine("Один параметр: " + a);
  }
  // Перегрузка метода OvlDemo с двумя целочисленными параметрами,
  public int OvlDemo(int a, int b) {
    Console.WriteLine("Два параметра: " + a + " " + b); return a + b;
  }
  // Перегрузка метода OvlDemo с двумя параметрами типа double,
  public double OvlDemo(double a, double b) {
    Console.WriteLine("Два параметра типа double: " + a + " "+ b) ;
    return a + b;
  }

class OverloadDemo {
  static void Main() {
    Overload ob = new Overload();
    int resI;
    double resD;
    // Вызвать все варианты метода OvlDemo().
    ob.OvlDemo();
    Console.WriteLine();
    ob.OvlDemo(2);
    Console.WriteLine();
    resI = ob.OvlDemo(4, 6);
    Console.WriteLine("Результат вызова метода ob.OvlDemo(4, 6): "
           + resI);
    Console.WriteLine();
    resD = ob.OvlDemo(1.1, 2.32);
    Console.WriteLine("Результат вызова метода ob.OvlDemo(1.1, 2.32):"
          + resD);
  }
}

Вот к какому результату приводит выполнение приведенного выше кода.

Без параметров
Один параметр: 2
Два параметра: 4 6
Результат вызова метода ob.OvlDemo(4, 6): 10
Два параметра типа double: 1.1 2.32
Результат вызова метода ob.OvlDemo(1.1, 2.32): 3.42

Как видите, метод OvlDemo() перегружается четыре раза. Первый его вариант не получает параметров, второй получает один целочисленный параметр, третий — два целочисленных параметра, а четвертый — два параметра типа double. Обратите также внимание на то, что два первых варианта метода OvlDemo() возвращают значение типа void, а по существу, не возвращают никакого значения, а два других — возвращают конкретное значение. И это совершенно допустимо, но, как пояснялось выше, тип возвращаемого значения не играет никакой роли для перегрузки метода. Следовательно, попытка использовать два разных (по типу возвращаемого значения) варианта метода OvlDemo() в приведенном ниже фрагменте кода приведет к ошибке.

// Одно объявление метода OvlDemo(int) вполне допустимо,
public void OvlDemo(int a) {
  Console.WriteLine("Один параметр: " + a);
}
/* Ошибка! Два объявления метода OvlDemo(int) не допускаются, хотя они и возвращают разнотипные значения. */
public int OvlDemo(int a) {
  Console.WriteLine("Один параметр: " + a);
  return a * a;
}

Как следует из комментариев к приведенному выше коду, отличий в типах значений, возвращаемых обоими вариантами метода OvlDemo(), оказывается недостаточно для перегрузки данного метода.

И как пояснялось в главе 3, в C# предусмотрен ряд неявных (т.е. автоматических) преобразований типов. Эти преобразования распространяются также на параметры перегружаемых методов. В качестве примера рассмотрим следующую программу.

// Неявные преобразования типов могут повлиять на
// решение перегружать метод.
using System;
class Overload2 {
  public void MyMeth(int x) {
    Console.WriteLine("В методе MyMeth(int): " + x);
  }
  public void MyMeth(double x) {
    Console.WriteLine("В методе MyMeth(double): " + x);
  }
}
class TypeConv {
  static void Main() {
    Overload2 ob = new Overload2();
    int i = 10;
    double d = 10.1;
    byte b = 99;
    short s = 10;
    float f = 11.5F;
    ob.MyMeth(i); // вызвать метод ob.MyMeth(int)
    ob.MyMeth(d); // вызвать метод ob.MyMeth(double)
    ob.MyMeth(b); // вызвать метод ob.MyMeth(int) — с преобразованием типа
    ob.MyMeth(s); // вызвать метод ob.MyMeth(int) — с преобразованием типа
    ob.MyMeth(f); // вызвать метод ob.MyMeth(double) — с преобразованием типа
  }
}

При выполнении этой программы получается следующий результат.

В методе MyMeth(int): 10
В методе MyMeth(double): 10.1
В методе MyMeth(int): 99
В методе MyMeth(int): 10
В методе MyMeth(double): 11.5

В данном примере определены только два варианта метода MyMeth(): с параметром типа int и с параметром типа double. Тем не менее методу MyMeth() можно передать значение типа byte, short или float. Так, если этому методу передается значение типа byte или short, то компилятор C# автоматически преобразует это значение в тип int и в итоге вызывается вариант MyMeth (int) данного метода. А если ему передается значение типа float, то оно преобразуется в тип double и в результате вызывается вариант MyMeth (double) данного метода.

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

// Добавить метод MyMeth(byte).
using System;
class Overload2 {
  public void MyMeth(byte x) {
    Console.WriteLine("В методе MyMeth(byte): " + x);
  }
  public void MyMeth(int x) {
    Console.WriteLine("В методе MyMeth(int): " + x) ;
  }
  public void MyMeth(double x) {
    Console.WriteLine("В методе MyMeth(double): " + x);
  }
}
class TypeConv {
  static void Main() {
    Overload2 ob = new Overload2();
    int i = 10;
    double d = 10.1;
    byte b = 99;
    short s = 10;
    float f = 11.5F;
    ob.MyMeth(i); // вызвать метод ob.MyMeth(int)
    ob.MyMeth(d); // вызвать метод ob.MyMeth(double)
    ob.MyMeth(b); // вызвать метод ob.MyMeth(byte) —
                 // на этот раз без преобразования типа
    ob.MyMeth(s); // вызвать метод ob.MyMeth(int) — с преобразованием типа
    ob.MyMeth(f); // вызвать метод ob.MyMeth(double) — с преобразованием типа
  }
}

Выполнение этой программы приводит к следующему результату.

В методе MyMeth(int): 10
В методе MyMeth(double): 10.1
В методе MyMeth(byte): 99
В методе MyMeth(int): 10
В методе MyMeth(double): 11.5

В этой программе присутствует вариант метода MyMeth(), принимающий аргумент типа byte, поэтому при вызове данного метода с аргументом типа byte выбирается его вариант MyMeth (byte) без автоматического преобразования в тип int.

Оба модификатора параметров, ref и out, также учитываются, когда принимается решение о перегрузке метода. В качестве примера ниже приведен фрагмент кода, в котором определяются два совершенно разных метода.

public void MyMeth(int x) {
  Console.WriteLine("В методе MyMeth(int): " + x);
}
public void MyMeth(ref int x) {
  Console.WriteLine("В методе MyMeth(ref int): " + x);
}

Следовательно, при обращении

ob.MyMeth(i)

вызывается метод MyMeth (int x), но при обращении

ob.MyMeth(ref i)

вызывается метод MyMeth(ref int x).

Несмотря на то что модификаторы параметров ref и out учитываются, когда принимается решение о перегрузке метода, отличие между ними не столь существенно. Например, два следующих варианта метода MyMeth() оказываются недействительными.

// Неверно!
public void MyMeth(out int x) {//...
public void MyMeth(ref int x) { // ...

В данном случае компилятор не в состоянии различить два варианта одного и того же метода MyMeth() только на основании того, что в одном из них используется параметр out, а в другом — параметр ref.

Перегрузка методов поддерживает свойство полиморфизма, поскольку именно таким способом в C# реализуется главный принцип полиморфизма: один интерфейс — множество методов. Для того чтобы стало понятнее, как это делается, обратимся к конкретному примеру. В языках программирования, не поддерживающих перегрузку методов, каждому методу должно быть присвоено уникальное имя. Но в программировании зачастую возникает потребность реализовать по сути один и тот же метод для обработки разных типов данных. Допустим, что требуется функция, определяющая абсолютное значение. В языках, не поддерживающих перегрузку методов, обычно приходится создавать три или более вариантов такой функции с несколько отличающимися, но все же разными именами. Например, в С функция abs() возвращает абсолютное значение целого числа, функция labs() — абсолютное значение длинного целого числа, а функция fabs() — абсолютное значение числа с плавающей точкой обычной (одинарной) точности.

В С перегрузка не поддерживается, и поэтому у каждой функции должно быть свое, особое имя, несмотря на то, что все упомянутые выше функции, по существу, делают одно и то же — определяют абсолютное значение. Но это принципиально усложняет положение, поскольку приходится помнить имена всех трех функций, хотя они реализованы по одному и тому же основному принципу. Подобные затруднения в C# не возникают, поскольку каждому методу, определяющему абсолютное значение, может быть присвоено одно и то же имя. И действительно, в состав библиотеки классов для среды .NET Framework входит метод Abs(), который перегружается в классе System.Math для обработки данных разных числовых типов. Компилятор C# сам определяет, какой именно вариант метода Abs() следует вызывать, исходя из типа передаваемого аргумента.

Главная ценность перегрузки заключается в том, что она обеспечивает доступ к связанным вместе методам по общему имени. Следовательно, имя Abs обозначает общее выполняемое действие, а компилятор сам выбирает конкретный вариант метода по обстоятельствам. Благодаря полиморфизму несколько имен сводятся к одному. Несмотря на всю простоту рассматриваемого здесь примера, продемонстрированный в нем принцип полиморфизма можно расширить, чтобы выяснить, каким образом перегрузка помогает справляться с намного более сложными ситуациями в программировании.

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

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

Оглавление книги


Генерация: 1.027. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз