Наследование метода c#

Рассмотрим основные принципы наследование классов в C#.

Вспомним из предыдущей статьи синтаксис класса:

[атрибуты]
[модификаторы] class Имя_класса : [родитель]
<
>
Под родителем понимается класс, от которого наследуется данный. Существует несколько понятий, имеющих одно и то же значение, обозначающие наследуемый класс — базовый класс (base class) , родительский класс (parent class), предок. Класс, который наследует, называется производным, потомком или наследником. Потомок наследует все поля, свойства, методы, индексаторы, операторы от предка, помеченные соответствующим модификатором доступа. Кроме этого он может иметь собственные элементы или переопределить существующие.
Наследование можно рассмотреть на примере класса Point2D и класса Point3D, который от него наследуется.

class Point2D
<
protected double x; // поле доступно только в производных классах
protected double y; // поле доступно только в производных классах
public Point2D(double x, double y)
<
this.x = x;
this.y = y;
>
protected Point2D()
:this(0,0)
< >
>

class Point3D : Point2D
<
private double z;
Point3D(double x, double y, double z)
:base(x,y)
<
this.z = z;
>
>

В данном случае класс Point3D наследует переменные x, y, а также конструктор базового класса. Параметр base действует аналогично this, только используется он всегда для доступа к членам класса-предка, в то время как this — ссылается на текущий экземпляр класса. В этом случае часть объекта, соответствующая базовому классу, создается через вызов его конструктора. А часть, соответствующая производному, — с помощью конструктора потомка. Через параметр base можно вызвать любой конструктор базового класса с соответствующими параметрами.
Добавим по методу GetRadius() в оба класса:

// в Point2D
public double GetRadius()
<
return Math.Sqrt(x * x + y * y);
>
// в Point3D
public new double GetRadius()
<
return Math.Sqrt(x * x + y * y + z * z);
>

Это называется перекрытием метода, а точнее скрытием. В данном случае модификатор new явно скрывает базовый метод GetRadius() и заменяет его методом в производном классе.
Класс, помеченный модификатором sealed не может быть унаследован.

0. В «синтаксисе класса» навскидку пропущено два пункта. Если пропустили сознательно — почему бы не написать «в упрощенном синтаксисе класса» или что-то вроде этого?

1. В предыдущей статье ошибка — у класса может быть только один родитель.

2. Зачем?
protected Point2D()
:this(0,0)

3. protected double x; // поле доступно только в производных классах
В комментарии оговорка, еще и в объекте этого класса.

4. В данном случае класс Point3D наследует переменные x, y, а также конструктор базового класса
Point3D наследует конструктор базового класса? Почему же пример ниже не работает? ?
Point3D point = new Point3D (3, 4);

5. Последний пример (с сокрытием метода) явно вредный. Люди потом удивляются, почему же код вроде этого работает через ..опу?

Point2D point = new Point3D(3, 4, 5);
Console.WriteLine(point.GetRadius);

6. Про всякие «оговорки» вроде «this ссылается на этот класс» (к которым придираются на собеседованиях/экзаменах) даже говорить не буду.

Вот честное слово, так люблю читать твои комментарии. Они всегда конструктивные. Автор статьи в ближайшее время ответит на них.

0. Как угодно, «упрощенный синтаксис». Тут цель — просто показать запись класса-предка в объявлении.
1. Конечно, исправил.
2. Если честно, не понял вопроса. Не нравится, что нулевые значения передаются?
3. Согласен.
4. Я фигню написал.) Конструкторы классов наследников вызывают конструкторы класса-предка, например, непараметризованный конструктрор Point3D() будет вызывать Point2D(). Мы можем только явно указать, какой из конструкторов будет вызываться.
5. Ну я не написал, что это приветствуется. Просто надо знать, что такая вещь есть.
6. Ссылается на текущий экземпляр класса.

Спасибо Вам за обучающую информацию. Очень много полезного для начинающих. Есть пара вопросов:
1. Будет ли продолжение статей по С#?
2. Есть ли у Вас какой-нибудь сборник задач по программированию? Для разных уровней мастерства)
А в остальном еще раз спасибо за сайт)

cybern.ru

Основы наследования

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

В языке C# класс, который наследуется, называется базовым, а класс, который наследует, — производным. Следовательно, производный класс представляет собой специализированный вариант базового класса. Он наследует все переменные, методы, свойства и индексаторы, определяемые в базовом классе, добавляя к ним свои собственные элементы.

Поддержка наследования в C# состоит в том, что в объявление одного класса разрешается вводить другой класс. Для этого при объявлении производного класса указывается базовый класс. При установке между классами отношения «является» строится зависимость между двумя или более типами классов. Базовая идея, лежащая в основе классического наследования, заключается в том, что новые классы могут создаваться с использованием существующих классов в качестве отправной точки:

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

Как видите класс CSharp получает доступ к полям и методам класса ProfessorWeb. Всегда помните, что наследование предохраняет инкапсуляцию, а потому приватные члены никогда не могут быть доступны через ссылку на объект. Т.е. поле inf из примера не может быть доступно для вызова с помощью экземпляра класса obj.

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

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

professorweb.ru

Существует определенный набор возможностей в любом языке программирования для понимания которых нужно просто знать, как они реализованы. Вот, например, замыкания; это не сверх сложная концепция, но знание того, как этот зверь устроен позволяет делать определенные выводы относительно поведения замыканий с переменными цикла. Тоже самое касается вызова виртуальных методов в конструкторе базового класса: здесь нет одного правильного решения и нужно просто знать, что именно решили разработчики языка и будет ли вызываться метод наследника (как в Java или C#), или же «полиморфное» поведение в конструкторе не работает и будет вызываться метод базового класса (как в С++).

Еще одним типом проблемы у которой нет идеального решения, является совмещение перегрузки методов (overloading) и переопределения (overriding) метода. Давайте рассмотрим следующий пример на языке C#. Предположим, у нас есть пара классов, Base и Derived, с виртуальным методом Foo(int) и невиртуальным методом Foo(object) в классе Derived:

Вопрос заключается в том, какой метод вызовется в следующем случае:

Первым, и вполне разумным предположением является то, что вызовется метод Derived.Foo(int), ведь 42 – это int, а класс Derived содержит метод Foo(int). Однако на самом деле это не так и будет вызван метода Derived.Foo(object).

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

Однако в данном случае интересен не просто факт того, что объявление и переопределение методов трактуется по разному и что методы базового класса являются «методами» второго сорта, и компилятор анализирует их во вторую очередь, сколько причины того, что компилятор (точнее его разработчики) решили реализовать именно такое поведение.
Чтобы ответить на вопрос о том, насколько текущее поведение логично давайте сделаем шаг назад и рассмотрим такой случай. Предположим, что в нашей иерархии классов есть лишь один метод Foo(object), и расположен он в классе Derived:

Да, не сильно полезная иерархия классов, но тем не менее. Самое главное в ней то, что ни у кого не вызовет вопросов, какой вызов Foo будет вызван в следующем случае (вариант-то всего один): new Derived().Foo(42).
Но давайте предположим, что разработкой классов Base и Derived занимаются разные организации или хотя бы разные разработчики. Поскольку разработчик класса Base не очень-то знает о том, что именно делает разработчик класса Derived, то в одни прекрасный момент он может добавить метод Foo в базовый класс без ведома разработчиков класса наследника:

Если следовать здравому смыслу, который говорил в нас при ответе на исходный вопрос, то у нас появляется более подходящая перегрузка метода Foo и следующий код: new Derived().Foo(42) теперь должен приводить к вызову метода базового класса и выводить Base.Foo(int). Однако насколько логично, что без ведома разработчика класса Derived после изменений в базовом классе хорошо протестированный код вдруг перестанет работать? Конечно, можно было бы сказать, что давайте в этом случае не будем вызывать метод базового класса и будем вызывать его только при наличии перегрузки в классе Derived. Но это поведение будет еще более странным.
Данная проблема известна в широких кругах читателей спецификации языка C# и блога Эрика Липперта, как проблема «хрупких базовых классов» (brittle base classes syndrome), которую большинство разработчиков языков программирования стараются как-то решить. В данном конкретном случае она решается тем, что компилятор вначале анализирует методы непосредственно объявленные в классе используемой переменной и лишь при отсутствии подходящего метода рассматривает методы, объявленные в базовых классах.

А как насчет других языков программирования?

Да, было бы очень интересным узнать о том, как эта проблема решается в других языках программирования, например, в C++, Java или, может быть, в Eiffel-е (по мнению многих самом навороченном ОО языке программирования).

Давайте я начну с конца, поскольку так будет немного проще. В Eiffel-е проблема решается очень просто: несмотря на множество тру ОО-шных фишек в Eiffel-е просто нет перегрузки методов и вы не можете объявить в наследнике метод с тем же именем, что и метод базового класса. Это значит, что диагностика этой проблемы переносится на время компиляции и просто не существует во время исполнения. (Кстати, хотя это звучит смешно, но это весьма эффективный способ борьбы со многими проблемами; тот же Eiffel успешно решает ряд нетривиальных проблем просто тем, что он их не допускает. И хотя такой подход далеко не идеален, иногда он вполне может применяться к решению многих проблем доменной области: иногда проще запретить некоторую возможность для пользователя нежели убить полгода на ее решение).

ПРИМЕЧАНИЕ
На самом деле подобный трюк используется не только в языке Eiffel; так, например, в языке C# существует некоторая проблема с виртуальными событиями, которая решается в VB.NET весьма элегантно – виртуальные события в нем просто запрещены.

В Java и С++ дела обстоят несколько иначе и связано это, прежде всего с тем, каким образом в этих языках декларируется переопределение метода в классе наследнике. В этих языках применяется разный подход к виртуальности по умолчанию (в Java все методы по умолчанию виртуальные, а в С++ виртуальность метода нужно явно декларировать явно), но изначально в них применялся один и тот же подход к переопределению виртуальных методов в классе наследнике:

Для переопределения метода в языках Java и C++ не требуется использования каких-либо дополнительных ключевых слов: достаточно в классе наследнике реализовать метод с той же самой сигнатурой. А поскольку с точки зрения синтаксиса переопределяемый метод никак не отличается от объявления нового метода (сравните два метода класса Derived), то и поведение здесь будет не таким, как в языке C#:

В данном случае, как и ожидали мы изначально, будет вызван метод Foo(Integer&).
В языке Java и в языке C++ позднее появилась возможность у программиста более точно передавать свои намерения с точки зрения переопределения методов в наследнике. В Java, начиная с 5-й версии появилась специальная аннотация — Override, а в С++11 появилось новое ключевое слово “override”. Однако, по понятным причинам, поведение в этих языках осталось неизменным.

ПРИМЕЧАНИЕ
Кстати, за подробностями о том, что нового появилось в С++11 по сравнению с предыдущим стандартом, можно найти в переводе FAQ-а Бьярне Страуструпа: C++11 FAQ.
Правда на этом схожесть языков Java и С++ заканчиваются. Если закомментировать метод Foo(Integer&) в классе Derived, то в С++ будет вызван Derived::Foo(Object&) (т.е. более подходящий метод базового класса не будет рассматриваться в качестве кандидата), а в Java – Base.Foo(Integer).

m.habr.com

Наследование в C#

Всем доброго времени суток. На связи Алексей Гулынин. В прошлой статье вы узнали немного о том, что такое делегаты в C#. В данной статье я бы хотел рассказать про наследование в C#. Простыми словами, «Наследование» означает то, что мы создаём класс на основе другого. Получается, у нас есть родительский класс и дочерний класс, который наследует все поля и методы родительского класса. Для того, чтобы понять, что такое наследование и для чего его нужно использовать, вернёмся к понятию объекта.

Объект представляет собой некую абстрактную сущность. Допустим мы хотим создать класс «Animal» (Животное). Но животных же существует великое множество и вряд ли мы одним классом сможем описать их всех. В классе «Animal» мы можем создать поля и методы, присущие всем животным. Например, полями общими для всех животных могут быть «Вес», «Средняя продолжительность жизни», «Имеется хвост или нет». Методом может быть «eat()» (кушать), ведь все же животные питаются. От такого общего класса мы можем создать дочерний класс, который расширяет родительский класс. Например, класс «Dog» (собака) может расширять класс «Animal» уже конкретными полями и методами, которые соответствуют именно собакам.

Отношение наследования — это отношение перехода от более общей абстракции к более конкретной.

В C# наследование является одиночным, то есть нельзя наследоваться от двух и более классов. Наследование определяется через «:».

Интересный момент: если вы пишете обычный класс, который не имеет родителя, то, по умолчанию, этот класс является наследником класса «Object». Класс «Object» является родителем абсолютно для всех классов в .NET.

Немного поговорим про понятия, так или иначе связанные с наследованием.

Абстрактный класс — это класс, объекты которого нельзя создавать, т.е. нельзя будет использовать ключевое слово «new». Абстрактные классы используются при наследовании и используются как прародители к другим классам (реальным, не абстрактным). Т.е. в нашем примере можно класс «Animal» пометить как «abstract». Абстрактным может быть также и метод (это метод без реализации). Если в классе присутствует хотя бы один абстрактный метод, то и сам класс обязан быть абстрактным. В обратную сторону правило не действует.

С помощью ключевого слова «sealed» можно запретить создавать наследников от класса. Пример: класс «String». От этого класса мы не сможем создать наследников. Применение ключевого слова «sealed» к методу означает, что мы запрещаем переопределение этого метода в классах-наследниках.

Ключевое слово «virtual» применяется только к методам, и используется для того, чтобы превратить метод в виртуальный. Это делается для того, чтобы метод можно было переопределить в классах-наследниках. Переопределение метода означает, что мы внутри класса-наследника создаём метод, у которого заголовок полностью совпадает с заголовком метода класса-родителя. При этом в классе-наследнике нужно указать ключевое слово «override», чтобы явно указать, что мы переопределяем метод.

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

Конструкторы при наследовании.

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

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

С помощью «base» также можно вызывать родительский метод.

Разберем на эту тему вот такой пример: пусть имеется родительский класс «Degree», имеющий одной поле «degrees» и один метод, который возвращает значение данного поля. Создадим дочерний класс «Radiance», который, используя метод родительского класса, возвращает градусы, переведенные в радианы:

Напоследок, обобщу особенности наследования:

  • Ключевые слова «sealed» и «static» (статический класс, про него поговорим в отдельной статье) запрещают наследование
  • Если в базовом классе определен какой-то метод «abstract», то базовый класс тоже должен быть абстрактным. В классе-наследнике такой абстрактный метод нужно переопределить. Абстрактный метод, по умолчанию, является виртуальным.
  • При проектировании программы важным является понимание того, что от чего можно унаследовать, а что нельзя. Для проверки условия наследования используется слово «является». В нашем примере: «Собака является животным? — является», «Питбуль является собакой? — является». А вот наоборот лучше не делать (технически конечно можно, но программы лучше сразу проектировать правильно), «Животное является собакой? — не является». Поэтому класс «Animal» от класса «Dog» наследовать нельзя.

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

На связи был Алексей Гулынин, оставляйте свои комментарии, увидимся в следующих статьях.

alekseygulynin.ru

Наследование метода c#

C#. Переопределение и скрытие методов

Переопределение и скрытие методов

В этой статье, я расскажу об использовании ключевых слов new и override применительно к методам классов в C#. Я не буду затрагивать все тонкости использования этих ключевых слов в данном контексте, а наоборот, постараюсь донести самые основные моменты в доступной форме. Скажу сразу, что тема настоящей статьи актуальна при условии, что Вы знакомы с основами ООП (объектно-ориентированного программирования). И так, к делу!

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

Представьте, что в определенный момент разработки, Вы понимаете, что метод базового класса (а точнее его реализация) в производном (в производный класс, он попал «по наследству»), работает не так как Вы хотите! И тогда, вы решаете создать подобный метод в производном классе, но с доработанной реализацией. Причем, Вы не хотите придумывать для этого метода какое-то новое название. По двум причинам, во-первых, у метода уже отличное название, а во-вторых Вы не хотите, чтобы пользователю класса был доступен «неподходящий» метод, пришедший из базового класса (замену которому Вы и собираетесь разрабатывать). На практике, это может выглядеть примерно так:

Обратите внимание на выделенную строку. Как видите, в производном классе мы тоже объявили метод «DoSomthing», с таким же прототипом, что и в базовом классе. Но есть одна важная деталь, так как мы решили скрыть метод базового класса, заменив его новым методом, мы указали ключевое слово new сразу после модификатора доступа public.

А теперь, давайте разберемся с переопределением методов базового класса в производном. Тут ситуация немного другая, переопределение методов применяется тогда, когда метод базового класса является виртуальным (для чего нужны виртуальные методы, я рассказывал в уроке № 22 базового курса C#). Давайте модифицируем наш базовый класс, сделав его главный метод виртуальным:

Теперь метод «DoSomthing» стал виртуальным. И тогда, чтобы не скрыть, а переопределить этот метод в производном классе, нужно использовать ключевое слово override, вместо new, как показано в примере ниже:

Как видите, всё довольно просто! Да это только основные варианты использования ключевых слов new и override по отношению к методам класса, есть еще ряд нюансов, но на начальном уровне изучения, достаточно и основных моментов. Главное запомнить, что ключевое слово new скрывает метод базового класса (в том числе и виртуальный), а override переопределяет виртуальный метод базового класса.

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

Для отправки комментария вам необходимо авторизоваться.

plssite.ru