Square c что это
Перейти к содержимому

Square c что это

sqrt , sqrtf , sqrtl

Поскольку C++ допускает перегрузку, можно вызывать перегрузки sqrt , которые принимают типы float или long double . В программе на языке C, если только вы не используете <tgmath.h> макрос для вызова этой функции, sqrt всегда принимает и возвращает double .

При использовании <tgmath.h> sqrt() макроса тип аргумента определяет, какая версия функции выбрана. Подробные сведения см. в разделе Type-Generic Math .

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

Возвращаемое значение

Функции sqrt возвращают квадратный корень x . По умолчанию, если x имеет отрицательное значение, sqrt возвращает неопределенное NaN значение.

How to square a number in c++ ?

In c++ logic when we require square of a number then there are multiple solutions are available for it.

  1. You can use Power function of c++ pow(Base,Power)
  2. Simple multiplication of number two time
  3. If we want to make square without multiplication then we can achieve it using for loop or while loop.

Create square of number in c++

Square of a number solution in c++

How to square a number in c++ ?

Here we can square number using Power function. You will need to include #include<cmath> for it. Inside function we need to pass Base value which we want to square and Power value (for square it will be 2). So example will be Pow(Power,Base). You can consider it as c++ square operator.

square function c++

We can create separate function for getting square of number. Here we just have to do multiplication of number two time.

Find square of a number without using multiplication or division operators

If there is requirement to create square of number without using multiplication/division then we have to create different logic using for loop or while loop.

  1. Using for loop, make addition of number
  2. Using while loop, we have to add Odd number so it will generate square at the end.

c++ program to find square of a number using for loop

Here we are adding 10+10…. up to 10 times. So after addition it will create square of 10 which is 100.

c++ program to find a square of a number using while loop

Here we are adding odd number using while loop and final result will create square of a number.

SEE MORE:

Reader Interactions

Leave a Reply Cancel reply

Primary Sidebar

Search here

Social Media

SEE MORE

Fibonacci sequence c++

Fibonacci Sequence c++ is a number sequence which created by sum of previous two numbers. First two number of series are 0 and 1. And then using these two number Fibonacci series is create like 0, 1, (0+1)=1, (1+1)=2, (2+1)=3, (2+3)=5 …etc Displaying Fibonacci Series in C++ ( without recursion) Output: From given output you […]

map c++

C++ Map [Learn by Example]

C++ map is part of Standard Template Library (STL). It is type of Associative container. Map in c++ is used to store unique key and it’s value in data structure. But if you want to store non-unique key value then you can use Multi Map in c++. Let us first understand in detail what is […]

how to copy paste in turbo c++ ?

There are many different C++ IDE are available but still many students are using Turbo c++ for learning c/c++ programming languages. During using Turbo c++ if you are beginner you will be confuse for how to copy and paste in turbo c++ or if you have already copy some content and you want to paste […]

C++

return 0 c++

There are two different scenario return statement is used inside c++ programming. We can use return 0 c++ inside main() function or other user defined functions also. But both have different meanings. return 0 c++ used inside Main function return 0 c++ used inside other user defined function What is meaning of return 0 and […]

C++

c++ expected a declaration [ SOLVED]

When any function or statement is not in scope or we have used wrong syntax then possibly you will get error for c++ expected a declaration in your code. Main reasons for errors are: Incorrect use/declaration inside namespace Statements are added out of scope Required statement need to add inside main/function Solution-1 | Expected a […]

Интерфейсы C# — Самый подробный разбор

Возможно, тебе уже приходилось слышать про механизм множественного наследования. Это когда есть возможность создать класс производный от двух и более базовых классов. Так вот, забудьте о нем, CLR его не поддерживает совсем. НО! За-то есть возможность реализовать ограниченное множественное наследование через реализацию интерфейсов. Так вот, что такое интерфейсы, чем они отличаются от классов и особенности их применения мы и будем подробно рассматривать в этой статье и прикрепленном видео.

Наследование классов и реализация интерфейсов. В чем разница?

В языке C# существует базовый класс, от которого наследуется любой другой. Это класс Object. В нем определены и реализованы 4 метода: Equals() , GetHashCode() , GetType() и ToString() . Это означает, что абсолютно любой класс наследует две вещи:

  1. Сигнатуры этих методов
  2. Реализацию этих методов

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

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

Интерфейс же, в отличии от класса, содержит в себе только определение сигнатур метода, без их непосредственной реализации.

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

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

За счет того, что класс непосредственно в себе содержит определения методов, CLR способна различать к какому интерфейсу относится какой метод и избегать конфликтов. Благодаря этому мы можем реализовывать несколько интерфейсов, в отличии от множественного наследования. Но вот чем похожи наследование и реализация интерфейсов, так это возможностью подставлять экземпляры производного типа на место базовых. То есть наш любимый полиморфизм. Мы можем объявить переменную интерфейсного типа и в нее поместить экземпляр любого класса, который реализует этот интерфейс. Данная схема применяется очень часто, например, в DI-контейнерах или в mock-тестах.

Определение интерфейсов

Интерфейс — это поименованный набор сигнатур методов (в том числе событий, свойств и индексаторов, т.к. всё это по сути синтаксический сахар для методов). В интерфейсе нельзя определить конструкторы и поля, а также статические методы и константы (кстати, это ограничение языка, сама CLR на это способна).

Для создания интерфейса используется ключевое слово interface кто бы мог подумать. Например, вот несколько часто используемых стандартных интерфейсов из FCL:

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

Для именования интерфейсов принято соглашение об использовании заглавной буквы I в начале имени. Как и всегда, это не является требованием системы, и программа сможет скомпилироваться как бы ты не назвал интерфейс. Но вот другие программисты могут и по голове настучать…

Интерфейсы могут наследоваться от других интерфейсов. Хотя Рихтер предпочитает называть это включением контрактов других интерфейсов. Например, рассмотрим приведенные ранее интерфейсы IEnumerable , IEnumerable<T> и ICollection<T> . Если какой-либо класс решит реализовать интерфейс ICollection<T> это будет означать:

  1. Этот класс обязан реализовать все методы определенные в интерфейсах IEnumerable , IEnumerable<T> и ICollection<T>
  2. Любой класс, который использует объект класса, реализующего интерфейс ICollection<T> также в праве ожидать и реализацию методов и IEnumerable , и IEnumerable<T>

Реализация интерфейсов

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

Для примера возьмем стандартный и широко используемый интерфейс IComparable<T> :

Данный интерфейс определяет механизм сравнения двух объектов. Теперь реализуем его в классе Point (весь исходный код доступен в GitHub):

InterfacesCSharp/ComparePoints

Для того, чтобы класс реализовывал интерфейс необходимо при объявлении класса через двоеточие указать название интерфейса (точно также как записывается наследование). А в теле класса реализовать все методы, которые были объявлены внутри интерфейса.

Для того, чтобы проверить работоспособность кода, просто создадим две точки в коллекции в неправильном порядке, а затем выполним сортировку по возрастанию. Метод CompareTo() выполнит сравнение точек и так как они не упорядочены мы выполним их обмен местами, после чего выведем на консоль координаты возрастающих точек.

Для методов, реализующих интерфейс есть несколько требований:

  1. Метод должен быть публичным ( public )
  2. Метод должен быть виртуальным ( virtual )

Но как ты мог заметить в предыдущем примере, мы не делали метод виртуальным. Но здесь, как всегда, начинает работать магия компилятора. Он самостоятельно добавляет к методу два модификатора, делая его виртуальным и запечатанным ( sealed ). Это сделано для того, чтобы наследник от реализующего интерфейс класса не мог переопределить этот метод. Но при желании мы можем самостоятельно добавить виртуальный модификатор к реализации метода. Тогда метод не будет запечатанным.

То есть, если при реализации метода мы не добавляем модификатор virtual , то метод будет и виртуальным, и запечатанным. Если добавляем модификатор virtual — виртуальным и незапечатанным.

Естественно, наследник не может переопределить запечатанные методы, но он может унаследовать этот же интерфейс, как у предка, и реализовать его самостоятельно. Давай разберем это на примере:

InterfacesCSharp/VirtualAndSealedImplementation

Создадим два базовых класса, которые оба будут реализовывать интерфейс IDisposable . Но для класса BaseSealed мы не будем использовать при реализации метода ключевое слово virtual , в отличии от класса BaseUnsealed , где этот модификатор используется. Теперь перейдем к классам, которые будут наследовать данные базовые классы.

Если мы наследуем класс, где модификатор virtual не использовался, то метод Dispose() является запечатанным, а следовательно мы не можем переопределить интерфейсный метод. Однако, мы можем немного модернизировать данный код следующим образом:

Мы можем заместить реализацию базового метода с помощью ключевого слова new , но тогда метод Dispose() не будет относиться к интерфейсу IDispoible , а будет работать как самостоятельный метод.

Если же мы будем использовать базовый класс, в котором применялось ключевое слово virtual, то результат будет совершенно другой:

Мы совершенно спокойно можем переопределить реализацию интерфейсного метода, так как он НЕ запечатан.

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

Для этого нам нужно будет унаследоваться от базового класса и самостоятельно реализовать интерфейс одновременно. А пре реализации метода использовать ключевое слово new . При этом потомок сможет использовать и собственную реализацию, и обращаться к родительской при необходимости.

Подробнее о вызовах интерфейсных методов

А теперь самое время посмотреть на то, как происходит вызов методов, определенных в интерфейсах. Для примера возьмем стандартный строковый тип string . Если посмотреть на его определение, то мы можем увидеть, что он реализует набор различных интерфейсов: IComparable , ICloneable , IConvertible , IEnumerable , IComparable<String> , IEnumerable<char> , IEquatable<String> .

А также неявно наследует класс object . Это означает, что по умолчанию для типа string уже доступны для использования методы определенные в object , а самому типу необходимо будет реализовать все методы в представленных интерфейсах. Ну и естественно, при необходимости тип может определять свои собственные члены. И если мы обратимся к помощнику IntelliSence то мы увидим весь этот набор методов.

Помощник IntelliSence отображает весь список доступных методов, как определенных в самом классе (например, EndsWith()), в интерфейсах (CompareTo()) и в object (GetHashCode())

Но в CLR у нас также есть замечательная возможность определять переменные интерфейсного типа. В таком случае, эта переменная может содержать в себе, ну а если точнее, то ссылаться на экземпляр любого типа, который реализует этот интерфейс. В данном случае для использования будут доступны только те методы, которые определены в самом интерфейсе, плюс методы из object, так как любой тип унаследован от object и CLR может быть на 100% уверена, что эти методы там будут.

InterfacesCSharp/CallingInterfaceMethod

GetMethods() возвращает имена доступных методов типа. Но так как перегрузка метода считается за отдельный метод, то на выходе получается повторение некоторых методов несколько раз. И с помощью Select и Distinct я просто избавляюсь от повторов перегруженных методов.

В данном примере все переменные типов IComparable , ICloneable и IEnumerable на самом деле ссылаются на один и тот же экземпляр переменной string в куче. Но когда мы работаем с этой переменной через интерфейсные переменные, нам доступен только определенный в интерфейсе набор методов и методов из object (хотя они и не отображаются во время вывода на консоль. Таким образом именно тип переменной определяет, какие действия могут быть выполнены с объектом.

Интерфейсы C#

Вывод всех доступных методов для переменных различных интерфейсных типов, ссылающихся на одну и ту же строковую переменную

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

Явная и неявная реализация интерфейсных методов

Что же на самом деле происходит в CLR, когда мы загружаем тип? Для него создаются таблицы метаданных, одна из которых — это таблица методов. В нее добавляются данные обо всех методах, определенных в самом классе, а также обо всех методах, получаемых по иерархии наследования и обо всех интерфейсных методах.

InterfacesCSharp/ExtinctInterfaceMethodImplementation

Соответственно, для типа SimpleType должны быть созданы в метаданных:

  • Все экземплярные методы из object , так как он его неявно наследуется
  • Методы для всех унаследованных интерфейсов (в данном случае только IDisposible.Dispose() )
  • Методы определенные в самом классе (в данном случае только Dispose() )

Но так как CLR достаточно умна, она понимает, что метод Dispose() в SimpleType является реализацией метода интерфейса IDisposible.Dispose() , так как они имеют одинаковую сигнатуру. И соответственно в таком случае интерфейсный метод IDisposible.Dispose() и SimpleType.Dispose() указывают на одну и ту же фактическую реализацию. Но при желании, мы можем реализовать интерфейсный метод явно.

Как отличить явную реализацию ( Explicit Interface Method Implementation , EIMI) от неявной? Нужно обратить внимание на два признака:

  • Перед именем метода указано имя самого интерфейса
  • У метода нельзя указывать модификатор доступа (он всегда private , чтобы к нему нельзя было случайно обратиться из вне)

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

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

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

Обобщенные интерфейсы

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

В первую очередь хочется заметить, что существуют обобщенные и необобщенные версии часто используемых интерфейсов ( IComparable , IEnumerable и т.д.). И самое важное помнить, что везде, где это возможно нужно использовать именно обобщенную версию. Они предоставляют несколько ключевых преимуществ, которые мы обсудим чуть позже. Но тогда возникает справедливый вопрос, зачем нужно было оставлять необобщенные версии? А ответ очень прост — для обратной совместимости. Поэтому, если придется столкнутся со старыми библиотеками, то в таком случае — да, нужно будет работать с необобщенными интерфейсами. Но если пишешь что-то новое — используй дженерики!

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

Ну а теперь к преимуществам. Если коротко — их три:

  • Более строгий контроль типов
  • Повышение производительности за счет избегания упаковки и распаковки
  • Многократная реализация обобщенных интерфейсов

Давай рассмотрим, данные преимущества обобщенных интерфейсов на примере:

InterfacesCSharp/GenericInterfaces

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

А теперь рассмотрим примеры улучшений, которые предоставляют нам обобщенные интерфейсы:

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

Обобщения и ограничения интерфейса

Как мы помним, применять обобщенные интерфейсы можно не только ко всему классу, но и к отдельным методам, и это тоже ведет к определенным преимуществам при использовании.

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

Ну и как всегда нужно рассмотреть все на примере. Кстати, не забывай, что вполне возможно указывать ограничение в качестве определенного типа и набора интерфейсов. В таком случае тип передаваемого аргумента должен быть этим классом или его наследником, а также реализовывать все другие интерфейсы.

InterfacesCSharp/MethodConstraints

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

Реализация нескольких интерфейсов с одинаковыми сигнатурами и именами методов

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

InterfacesCSharp/NameConflict

Объявим два интерфейса, у которых совпадают сигратуры методов (имя и принимаемые аргументы), но которые при этом имеют совершенно разную логику:

  • IRestaurant — список блюд доступных в ресторане
  • IWindow — контекстное меню в окне приложения

И потом получается так, что один и тот же класс реализует оба этих интерфейса:

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

Таким образом, если мы хотим вызывать явные реализации методов интерфейса, нам необходимо выполнить явное приведение экземпляра класса к этому интерфейсу и только тогда вызывать метод. Кстати, именно по такому принципу работают и новые реализации интерфейсов по умолчанию. Просто явная реализация интерфейса находится не в классе, реализующем интерфейс, а в самом файле интерфейса. А для вызова реализации по умолчанию точно также необходимо выполнять явное приведение к типу интерфейса. Обрати внимание, что эта возможность появилась только в C# 8.0.

Реализация интерфейсов по умолчанию

Interfaces.Csharp.DefaultInterfaceImplementation

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

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

Мы можем выполнить явную реализацию интерфейса в теле класса. При этом реализация по умолчанию становится недоступной, но метод все еще относится и объектной модели интерфейса, поэтому будет недоступен из экземпляра класса. Для вызова метода все также необходимо приведение к интерфейсному типу.

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

В свежих версиях C# появилась возможность реализовывать методы прямо внутри интерфейсов

Совершенствование контроля типов за счет явной реализации интерфейсных методов

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

InterfacesCSharp/EimiForNoneGenericInterfaces

Для примера возьмем стандартный необобщенный интерфейс IComparable

И реализуем его в тривиальном значимом типе:

При работе с этим типом мы можем столкнуться с нежелательным поведением системы — падение производительности за счет упаковки, а также возможность совершить ошибку при написании кода, так как не соответствие типов не будет определяться синтаксическим анализатором Visual Studio.

А теперь попробуем решить обе этих проблемы одним махом с помощью явной реализации интерфейсных методов:

Для этого в классе мы будем использовать сразу два метода CompareTo() . Собственная реализация будет принимать в качестве аргумента не object , а конкретный значимый тип EimiValueType , но это не соответствует контракту интерфейса IComparable . Поэтому нам и нужен второй метод CompareTo() , который будет соответствовать сигнатуре заявленной в интерфейсе, но реализовывать его мы будем явно.

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

Опасность явной реализации интерфейсных методов

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

Путаница в документации или еще отсутствие

Например, обратимся к официальной документации Microsoft, которая описывает тип int .

Информация о типе int

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

InterfacesCSharp/DangerousOfEimi

Для того, чтобы решить подобную проблему, нам необходимо выполнить приведение переменной к интерфейсному типу, так как тип int реализует интерфейс IConvertible явно. И разработчику, который не знаком с подобной особенностью приходится догадываться, как так получается, что тип реализует интерфейс, а вызвать методы, однако, не получается. И даже мой любимый IntelliSense — в данном случае не помогает.

Но даже подобный ход является далеко не оптимальным решением из-за второй проблемы

Упаковка

Если ты внимательно читал, то уже мог догадаться о другой проблеме, которая возникает, когда мы пытаемся решить первую. Так как int — это структура, а следовательно — значимый тип, при приведении к интерфейсу IConvertible будет выполняться упаковка, что негативно скажется на быстродействие и потребление ресурсов приложения.

EIMI нельзя вызывать из производных типов

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

Если попытаться обратиться к базовой реализации интерфейса base.CompareTo(o) , то данный метод просто будет не доступен от слова совсем.

Для вызова метода необходимо привести экземпляр к интерфейсному типу. Но если класс будет самостоятельно реализовывать данный интерфейс, то это приведет к бесконечной рекурсии.

Один из вариантов решения данной проблемы — для класса Derived не реализовывать интерфейс IComparable самостоятельно, но иногда это необходимо. В таком случае есть другой подход — нам понадобится изменить и базовый класс и наследник.

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

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

Базовый класс или интерфейс C#?

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

  1. Какой тип мы создаем? Если это структура, то ответ на вопрос однозначный — используйте интерфейс. Потому что значимые типы неявно наследуются от типа ValueType , а следовательно, не могут быть унаследованы от другого класса, ведь тогда получается множественное наследование, которое запрещено в C#. Для ссылочных типов уже следует обращать внимание и на остальные параметры.
  2. Связь потомка с предком. Здесь тоже все достаточно просто, если мы хотим получить множественное наследование — то необходимо использовать интерфейсы, ну или как максимум базовый тип и интерфейсы.
  3. Простота использования. Важно помнить, что при реализации интерфейса в каждом случае придется реализовывать методы с нуля или что еще хуже — дублировать код. Базовый класс позволяет определить поведение по умолчанию сразу для всех наследников без необходимости писать дополнительный код. А так как программисты люди ленивые — это очень сильный аргумент в пользу базовых классов.
  4. Управление версиями. Если вдруг возникла необходимость добавить метод в БАЗОВЫЙ контракт (не важно базовый класс или интерфейс), то это в любом случае повлияет на всех наследников, что логично. Но вот только степень влияния оказывается разной. Для интерфейса придется менять и добавлять реализацию в каждого наследника, а следовательно, и перекомпилировать. Особенно сильно это влияет, если меняется интерфейс в библиотеке, которая используется в различных проектах. Для базового класса — такой проблемы нет. Наследники просто принимают изменения базового типа и их даже не нужно перекомпилировать.

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

Как использовать базовый абстрактный класс C#

Базовые классы хорошо использовать для экономии времени при реализации большого количества схожих по поведению классов. Если несколько типов полностью совпадают в своем поведении или отличаются совсем немного, то нет смысла реализовывать их каждый раз с нуля. К тому же, если потребуется внести изменения, их достаточно будет сделать только в одном месте. Примерами подобных классов могут быть потоки, которые наследуются от базового класса Stream или элементы интерфейса Windows Forms , которые являются потомками класса Control .

InterfacesCSharp/BaseClassVsInterface

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

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

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

Как использовать интерфейсы C#

Интерфейсы же целесообразно использовать, если каждый из наследников будет содержать свою собственную логику, которая может совершенно отличаться или лишь слегка пересекаться с другими наследниками. Несмотря на то, что они предоставляют одинаковые возможности, но добираются результатов различными подходами и не имеют совместно используемого когда. Также если мы хотим большей независимости или боимся, что изменения в одном классе могут повлиять на другой. Примером могут служить коллекции. Несмотря на то, что каждая коллекция List , Queue , Dictionary , Stack предоставляет схожие функции для работы с наборами элементов, каждая из них уникально реализована и ведет себя по-своему, поэтому и базируются они на интерфейсах. При этом мы можем, например добавлять и удалять элементы, даже не зная, какая именно это коллекция.

Рассмотрим другой синтетический пример. У нас есть интерфейс для всех геометрических фигур, которые определяет метод для вычисления пощади.

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

Таким образом, если классы имеют одинаковые по задачам методы, но логика их работы кардинально различается, то лучше использовать интерфейсы. Это потребует чуть больше времени и внимательности во время разработки, но обеспечит большую свободу и независимость типов.

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

Interface ⇽ BaseClass ⇽ ParticularClass

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

Советую прочитать предыдущую статью — 7 обязательных навыков дата-сайентиста
А также подписывайтесь на группу ВКонтакте, Telegram, Инстаграм и YouTube-канал. Там еще больше полезного и интересного для программистов.

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

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