Что такое перегрузка методов c
Перейти к содержимому

Что такое перегрузка методов c

  • автор:

Урок #23 – Перегрузка методов в языке C#

Урок #23 – Перегрузка методов в языке C#

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

Видеоурок

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

Что такое перегрузка методов?

За счёт перегрузки методов мы можем создать множество методов, что будут принимать разное количество параметров или разный тип данных и при этом будут иметь одно и то же имя.

Отличным примером является метод «Write» или «WriteLine». В данный метод мы можем передавать различные типы данных, но при этом вывод информации происходит без каких-либо ошибок. Так происходит из-за того, что в классе Console есть множество перегрузок методов «Write» и «WriteLine».

Как создать перегрузку?

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

public static string getInfo(string some) < return some + "!"; >public static int getInfo(int a, int b)

Теперь при вызове метода компилятор будет понимать какие параметры были переданы и в зависимости от этого будут вызываться разные методы.

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

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

В общем, для перегрузки метода достаточно объявить разные его варианты, а об остальном позаботится компилятор. Но при этом необходимо соблюсти следующее важное условие: тип или число параметров у каждого метода должны быть разными.

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

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

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 < class UserInfo < // Перегружаем метод ui public void ui() < Console.WriteLine("Пустой метод\n"); >public void ui(string Name) < Console.WriteLine("Имя пользователя: ",Name); > public void ui(string Name, string Family) < Console.WriteLine("Имя пользователя: \nФамилия пользователя: ",Name,Family); > public void ui(string Name, string Family, byte Age) < Console.WriteLine("Имя пользователя: \nФамилия пользователя: \nВозраст: ", Name, Family, Age); > > class Program < static void Main(string[] args) < UserInfo user1 = new UserInfo(); // Разные реализации вызова перегружаемого метода user1.ui(); user1.ui("Ерохин", "Александр", 26); Console.ReadLine(); >> > 

Использование перегруженного метода C#

Как видите метод ui перегружается три раза. Модификаторы параметров ref и out также учитываются, когда принимается решение о перегрузке метода. Несмотря на то что модификаторы параметров ref и out учитываются, когда принимается решение о перегрузке метода, отличие между ними не столь существенно. Давайте добавим еще одну перегрузку в вышеуказанный пример:

// Используем модификатор параметров public void ui(string Name, string Family, ref byte Age)

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

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

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

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

Чтобы закрепить понятие перегрузки методов, давайте рассмотрим перегрузку встроенного метода IndexOf класса String пространства имен System:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 < class Program < static void Main(string[] args) < string s = "Всем привет, это сайт professorweb.ru :)"; char ch = 'е'; string smile = ":)"; Console.WriteLine("Исходная строка: \n\n----------------------\n",s); // Первая перегрузка if (s.IndexOf(ch) != -1) Console.WriteLine("Символ '' находится на позиции ",ch,s.IndexOf(ch)); // Вторая перегрузка if (s.IndexOf(ch, s.IndexOf(ch)+1) != -1) Console.WriteLine("Далее, этот символ встречается на позиции ", s.IndexOf(ch, s.IndexOf(ch) + 1)); // Третья перегрузка if (s.IndexOf(smile, 0, s.Length) != -1) Console.WriteLine("Смайл найден на позиции ", smile, s.IndexOf(smile, 0, s.Length)); // Четвертая перегрузка if (s.IndexOf(smile, StringComparison.Ordinal) != -1) Console.WriteLine("Теперь смайл найден другим способом"); Console.ReadLine(); > > >

Перегрузки IndexOf C#

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

Что такое перегрузка методов c

Иногда возникает необходимость создать один и тот же метод, но с разным набором параметров. И в зависимости от имеющихся параметров применять определенную версию метода. Такая возможность еще называется перегрузкой методов (method overloading).

И в языке C# мы можем создавать в классе несколько методов с одним и тем же именем, но разной сигнатурой. Что такое сигнатура? Сигнатура складывается из следующих аспектов:

  • Имя метода
  • Количество параметров
  • Типы параметров
  • Порядок параметров
  • Модификаторы параметров

Но названия параметров в сигнатуру НЕ входят. Например, возьмем следующий метод:

public int Sum(int x, int y)

У данного метода сигнатура будет выглядеть так: Sum(int, int)

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

  • Количеству параметров
  • Типу параметров
  • Порядку параметров
  • Модификаторам параметров

Например, пусть у нас есть следующий класс:

class Calculator < public void Add(int a, int b) < int result = a + b; Console.WriteLine($"Result is "); > public void Add(int a, int b, int c) < int result = a + b + c; Console.WriteLine($"Result is "); > public int Add(int a, int b, int c, int d) < int result = a + b + c + d; Console.WriteLine($"Result is "); return result; > public void Add(double a, double b) < double result = a + b; Console.WriteLine($"Result is "); > >

Здесь представлены четыре разных версии метода Add, то есть определены четыре перегрузки данного метода.

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

То есть мы можем представить сигнатуры данных методов следующим образом:

Add(int, int) Add(int, int, int) Add(int, int, int, int) Add(double, double)

После определения перегруженных версий мы можем использовать их в программе:

Calculator calc = new Calculator(); calc.Add(1, 2); // 3 calc.Add(1, 2, 3); // 6 calc.Add(1, 2, 3, 4); // 10 calc.Add(1.4, 2.5); // 3.9
Result is 3 Result is 6 Result is 10 Result is 3.9

Также перегружаемые методы могут отличаться по используемым модификаторам. Например:

void Increment(ref int val) < val++; Console.WriteLine(val); >void Increment(int val)

В данном случае обе версии метода Increment имеют одинаковый набор параметров одинакового типа, однако в первом случае параметр имеет модификатор ref. Поэтому обе версии метода будут корректными перегрузками метода Increment.

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

int Sum(int x, int y) < return x + y; >int Sum(int number1, int number2) < return number1 + number2; >void Sum(int x, int y)

Сигнатура у всех этих методов будет совпадать:

Sum(int, int)

Поэтому данный набор методов не представляет корректные перегрузки метода Sum и работать не будет .

_Generic макрос

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

Так как это нововведение Си 11, то для работы потребуется новая версия компилятора, которая поддерживает стандарт. Для примера используется gcc версии 5.3.0 с флагом -std=c11. На Windows без проблем ставится пакет MinGW, в состав которого входят все необходимые утилиты.

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

int foo_int(int a) < return a + 1; >float foo_float(float a) < return a + 1.0; >char foo_char(char a)

Далее сам макрос

#define foo(X) \ _Generic((X), \ int: foo_int, \ char: foo_char, \ float: foo_float, \ default: foo_int \ )(X)

Эта система похожа на оператор switch. Вместо foo будет подставлена одна из функций, а далее (X) – вызов с этим аргументом. Дефолтное значение – когда тип определить нельзя. Если тип не определён и нет дефолтного значения, или оно не может быть использовано функцией по умолчанию, то это ошибка. Вот программа

#include int foo_int(int a) < return a + 1; >float foo_float(float a) < return a + 1.0; >char foo_char(char a) < return a + 1; >#define foo(X) \ _Generic((X), \ int: foo_int, \ char: foo_char, \ float: foo_float, \ default: foo_int \ )(X) int main()

Для функции с двумя аргументами немного сложнее, надо описать все комбинации. В нашем случае функция может принимать аргументы типа float и int, складывать их и возвращать целое

#include int baz_int_int(int a, int b) < return a + b; >int baz_int_float(int a, float b) < return a + (int) b; >int baz_float_int(float a, int b) < return (int) a + b; >int baz_float_float(float a, float b) < return (int) a + (int) b; >#define baz_int(X, Y) _Generic((Y),\ int: baz_int_int,\ float: baz_int_float\ ) #define baz_float(X, Y) _Generic((Y),\ int: baz_float_int,\ float: baz_float_float\ ) #define bar(X, Y) _Generic((X),\ int: baz_int(X, Y),\ float: baz_float(X, Y)\ )(X, Y) int main()

Конечно, можно вместо трёх макросов объединить всё в один

#include int baz_int_int(int a, int b) < return a + b; >int baz_int_float(int a, float b) < return a + (int) b; >int baz_float_int(float a, int b) < return (int) a + b; >int baz_float_float(float a, float b) < return (int) a + (int) b; >#define bar(X, Y) _Generic((X),\ int: _Generic((Y),\ int: baz_int_int,\ float: baz_int_float\ ),\ float: _Generic((Y),\ int: baz_float_int,\ float: baz_float_float\ )\ )(X, Y) int main()

Вот другой пример: макрос, который выводит имя типа, переданного в него

#include #include #define typeof_name(X) _Generic ((X),\ int: «signed integer»,\ unsigned: «unsigned integer»,\ char: «signed char»,\ unsigned char: «unsigned char»,\ float: «float»,\ double: «double»,\ char*: «modifiable string»,\ const char*: «const string»,\ default: «unknown»\ ) #define print_type(X) printf(«%s\n», typeof_name(X)) int main()

Заметьте важные особенности – без явного указания ‘a’ будет рассматриваться как int, а строка как модифицируемая, хотя попытка её изменить приведёт к ошибке. Для точного определениятипа можно его явно привести

print_type((unsigned char)'a'); print_type((int64_t) 32); print_type((const char*)"string");

Также тип может быть аргументом макроса

#include #define is_compatible(x, T) _Generic((x), T:1, default: 0) int main()
ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

email

Всё ещё не понятно? – пиши вопросы на ящик

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *