Зачем нужна перегрузка методов
Перейти к содержимому

Зачем нужна перегрузка методов

  • автор:

Зачем нужна перегрузка методов

Иногда возникает необходимость создать один и тот же метод, но с разным набором параметров. И в зависимости от имеющихся параметров применять определенную версию метода. Такая возможность еще называется перегрузкой методов (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 и работать не будет .

Урок #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)

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

Зачем нужна перегрузка операторов

avp, на java программируете? иногда писать MyFirstObject+=MySecondObject; удобнее, чем MyFirstObject=MyFirstObject.Add(MySecondObject); хоть реализуется оно практически одинково, даже возможно одно через другое

10 сен 2011 в 19:03

(Оффтопя) К слову, ad-hoc полимфоризм (который, в частности, используется при перегрузке операторов) не является «примочкой» ООП и прекрасно существует безотносительно этой парадигмы. ООП — это, в основном, к полимфоризму через подтипы (который, обычно, и называют просто «полиморфизмом»).

10 сен 2011 в 19:47

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

10 сен 2011 в 22:41
1. АДА — первый международно стандартизованный язык ООП. 2. Смотрите перегрузку в haskell.
10 сен 2011 в 23:47

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

Чтобы Вы могли совершать с классами те же операции, что и со стандартными типами данных. Например:

class CMoneys < int iDol; int iCen; public: CMoneys(int Dollars, int Cents) < iDol=Dollars; iCen=Cents; >CMoneys(const CMoneys& Money); //////Some code////// CMoneys operator +(CMoneys mon) < return (iDol + Money.iDol, iCen + Money.iCen); >> 
CMoneys M1(1, 2), M2(2, 3), M; M = M1 + M2; /////////////// Понимаем, как М (M1.iDol + M2.iDol, M1.iCen + M2.iCen) 

То есть благодаря перегрузке операторов мы можем выполнять со своими классами те же операции, что и со встроенными типами данных. О перегрузке хорошо написано Здесь

Отслеживать
14k 33 33 серебряных знака 46 46 бронзовых знаков
ответ дан 10 сен 2011 в 17:38
912 6 6 серебряных знаков 13 13 бронзовых знаков
Stop Cclass madness!
10 сен 2011 в 23:46

Why «madness»? You not like this programming style? I always write ‘C’ before class name, such as letter ‘i’ before int variable or ‘ch’ before char variable.

11 сен 2011 в 18:59
11 сен 2011 в 19:30

Не убедительно. Если Вы хороший программист, то Вы должны знать, когда класс, имя которого начинается на ‘C’ будет конфликтовать с разработанным Вами. Тем более, что в библиотеках языка С++ я ни разу не встречал классы, конфликтующие с моими собственными. И никто ещё из моего окружения их не встречал. Так что это дело сугуб личное. Вот если компилятор будет ругаться — тогда да, тогда префикс надо будет сменить.

12 сен 2011 в 13:22
Классы в С. Кажется, я что-то пропустил, когда учил этот язык
12 сен 2011 в 13:29

В некоторых случаях перегрузка все-таки удобнее своих функций с именами, особенно если подобные операторы введены вне программирования — например действия с полиномами (надеюсь, никто не будет спорить что два полинома можно сложить, вычесть и умножить и получить новый полином), сложение интенсивностей звука, действия с комплексными числами, приведенные выше действия с суммами денег или любыми другими величинами, записанными в виде X рублей Y копеек/ X футов Y дюймов и т.п.

Отслеживать
ответ дан 11 сен 2011 в 10:46
4,152 13 13 серебряных знаков 19 19 бронзовых знаков
Желательно бы увидеть пример кода для комплексных чисел. Сталоб понятнее)
11 сен 2011 в 10:52

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

11 сен 2011 в 11:19

Но я считаю, что настоящие ф-ции должны предшествовать операторам. Т.е. если необходисо сложение, то реализуйте ф-цию add(), а затем уже начинайте думать о перегрузке операторов. При этом перегруженный оператор + легко реализовать через уже написанную и, надеюсь, отлаженную ф-цию add().

11 сен 2011 в 12:40

@avp Так я и написал в некоторых случаях, перегрузкой не стоит сильно увлекаться и пихать её всюду, что, впрочем, верно и для любого действия. В подобных творениях не просили, но написать все через пень-колоду чтобы потом не найти маленькую ошибку можно и в самой простой процедурной программе без всякого ООП.

11 сен 2011 в 13:07
@Alerr например, вот: dmtsoft.ru/bn/370/as/oneaticleshablon
11 сен 2011 в 13:14

Зачем нужна? А затем, что удобно. На самом деле, честно говоря, перегрузка операторов только запутывает.

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

Касательно того когда перегружать и зачем. Это гораздо более интересный вопрос. Могу сказать следующее: выгодно перегружать операторы = (практически обязательно, если пользовательский класс содержит указатели куда-либо и нужно корректно отрабатывать ситуацию создания копии существующего или временнего объекта класса), оператор -> (если реализуете класс с семантикой классического указателя), оператор [] (если реализуете класс массива или коллекции и есть необходимость доступа к элементу этой коллекции поиндексу), оператор () ( если реализуете концепцию функтора). К остальным перегрузкам следует относиться с осторожностью. Во-первых, сутьопераций должна быть ясна. Например, оператор + должен складывать объекты, а не вычитать или, скажем, считать налоги. Т.е. его стоит реализовывать для пользовательских строковых типов или численных типов. А вот для объектов бизнес-логики или моделирующих физические процессы использование этого оператора нерационально — становится неясно, что хотел сказать автор кода. Поэтому вызовы функций с говорящими именами — наш метод, правильный и надежный. Во-вторых, нет возможности переопределить приоритеты операторов. В третьих, не стоит плодить кучу перегрузок одного и того же оператора для случая разных комбинаций типов. Это вкупе с операторами приведения типов очень быстро приводит к бардаку. И может в некоторых случаях удивить компилятор, ну, и программиста тоже.

Зачем нужна перегрузка функций?

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

type Reserve = < (from: Date, to: Date, destination: string): string (from: Date, destination: string) >const reserve: Reserve = ( from: Date, toOrDestination: Date | string, destination?: string ) => < if (toOrDestination instanceof Date && destination !== undefined) < return 'Round way ticket' >else if (typeof toOrDestination === 'string') < return 'One way ticket' >>

Это типа заказ билетов с возможностью не брать обратный билет. Но разве эта простыня лучше, скажем, такой записи?

type Reserve2 = (from: Date, destination: string, to?: Date) => string const reserve2: Reserve2 = (from, dest, to) => ( !to ? 'One way ticket' : 'Round way ticket' )

В другом месте видел такой пример, дескать, ‘a’ может быть как числом, так и строкой

type SumOrConcat = < (a: number, b: number): number (a: string, b: number): string >const result: SumOrConcat = (a: any, b: any): any => < if (typeof a === 'number') < return a + b >return `$ $` >

Но это же совсем дичь, да еще и с использованием any. Разве не лучше сделать вот так?

type SumOrConcat = (a: number | string, b: number) => number | string const result: SumOrConcat = (a, b) => ( typeof a === 'number' ? a + b : `$ $` )

Придумывал сам еще всякие примеры, но везде получается, что можно сделать без перегрузок. Какие могут быть ситуации, где перегрузка реально необходима?

  • Вопрос задан более двух лет назад
  • 355 просмотров

6 комментариев

Средний 6 комментариев

Lynn

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

Zhuroff

Евгений Журов @Zhuroff Автор вопроса
Lynn «Кофеман», о, вот это интересная мысль, про коллбэки я не подумал, спасибо

Lynn

А во втором примере у вас вообще не эквивалентный код.
В оригинале TS понимает, что если первый аргумент строка, то функция вернёт строку, а если число, то вернёт число. Ваш же код всегда возвращает строку-или-число.

Т.е. у вас выражение SumOrConcat(1, 2) * 3 не скомпилируется с ошибкой, что строки нельзя умножать.

Zhuroff

Евгений Журов @Zhuroff Автор вопроса

Lynn «Кофеман», возможно, перегрузку имеет смысл использовать, когда комбинаций действительно много, и/или типы данных какие-нибудь специфические? Но в этом случае, скорей всего, будет смысл вообще сделать разные функции, а то получится что функция слишком много всего делает. Хотя суть примеров с nodejs мне ясна, но полагаю, реальная необходимость использовать перегрузку возникает не так часто?

Zhuroff

Евгений Журов @Zhuroff Автор вопроса

всегда возвращает строку-или-число.

да, действительно, спасибо

Lynn

Нечасто. Обычно достаточно какого-нибудь перечисления типов.
Решения вопроса 1

bingo347

Дмитрий Беляев @bingo347 Куратор тега TypeScript
Crazy on performance.

Перегрузки нужны в следующих случаях:
1. Когда от типа некоторого аргумента зависит тип возвращаемого значения;
2. Когда от типа некоторого аргумента зависят типы последующих аргументов;
3. Комбинация из 1 и 2 вариантов.

Важно понимать, что в TS перегрузки существуют только на уровне типов, в отличии от других языков. Притом сами перегрузки реализованы как intersection от всех сигнатур, что порой вызывает проблемы с присвоением функции к типу с перегруженной сигнатурой. Все дело в вариантности, так как аргументы функции контравариантны сигнатуре самой функции, когда большинство отношений типов (и intersection в том числе) в TS ковариантны. В вашем примере с SumOrConcat эту проблему решили через тип any, который делает любую композицию типов с ним бивариантной.

На самом деле в примере с SumOrConcat вполне можно задать типы аргументам:

type SumOrConcat = < (a: number, b: number): number (a: string, b: number): string >const result: SumOrConcat = (a: number | string, b: number): any => < if (typeof a === 'number') < return a + b >return `$ $` >

Но надежнее все же так:

type SumOrConcat = < (a: number, b: number): number (a: string, b: number): string >const result = ((a: number | string, b: number): number | string => < if (typeof a === 'number') < return a + b >return `$ $` >) as SumOrConcat;

Ну и надо отметить, что синтаксис самих перегрузок таких проблем не имеет, хотя стрелочную функцию с ним не опишешь

function sumOrConcat(a: number, b: number): number; function sumOrConcat(a: string, b: number): string; function sumOrConcat(a: number | string, b: number): number | string < if (typeof a === 'number') < return a + b >return `$ $` > const result = sumOrConcat;

Так же, очень часто можно обойтись без перегрузки используя дженерики. Сам такой код будет выглядеть сложнее, но пользоваться им будет проще. Пример.

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

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