Как запретить наследование от класса c

от admin

Как запретить наследование от класса c

Наследование (inheritance) представляет один из ключевых аспектов объектно-ориентированного программирования, который позволяет наследовать функциональность одного класса или базового класса (base class) в другом — производном классе (derived class).

Зачем нужно наследование? Рассмотрим небольшую ситуацию, допустим, у нас есть классы, которые представляют человека и работника предприятия:

В данном случае класс Employee фактически содержит функционал класса Person: свойства name и age и функцию display. И было бы не совсем правильно повторять функциональность одного класса в другом классе, тем более что по сути сотрудник предприятия в любом случае является человеком. Поэтому в этом случае лучше использовать механизм наследования. Унаследуем класс Employee от класса Person:

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

Перед названием базового класса также можно указать спецификатор доступа, как в данном случае используется спецификатор public , который позволяет использовать в производном классе все открытые члены базового класса. Если мы не используем модификатор доступа, то класс Employee ничего не будет знать о переменных name и age и функции display.

После установки наследования мы можем убрать из класса Employee те переменные, которые уже определены в классе Person. Используем оба класса:

Таким образом, через переменную класса Employee мы можем обращаться ко всем открытым членам класса Person.

Конструкторы

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

После списка параметров конструктора производного класса через двоеточие идет вызов конструктора базового класса, в который передаются значения параметров n и a.

Если бы мы не вызвали конструктор базового класса, то это было бы ошибкой.

Консольный вывод программы:

Таким образом, в строке

Вначале будет вызываться конструктор базового класса Person, в который будут передаваться значения «Bob» и 31. И таким образом будут установлены имя и возраст. Затем будет выполняться собственно конструктор Employee, который установит компанию.

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

Также в примере выше стои отметить, что переменные в обоих классах стали закрытыми, то есть они объявлены со спецификатором private. Производный класс не может обращаться к закрытым членам базового класса. Поэтому, если бы мы попробовали обратиться к закрытым переменным класса Person через переменную класса Employee, то мы бы получили ошибку:

Спецификатор protected

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

Например, определим переменную name со спецификатором protected:

Таким образом, мы можем использовать переменную name в производном классе, например, в методе showEmployeeName, но извне мы к ней обратиться по-прежнему не можем.

Запрет наследования

Иногда наследование от класса может быть нежелательно. И с помощью спецификатора final мы можем запретить наследование:

После этого мы не сможем унаследовать другие классы от класса User. И, например, если мы попробуем написать, как в случае ниже, то мы столкнемся с ошибкой:

14.7 Запрет наследования

В С# есть ключевое слово sealed, позволяющее описать класс, от которого запрещено наследование. Например:

sealed class Demo

class newDemo: Demo < … >// ошибка

Задание. Подумайте:

для чего может создаваться класс, от которого нельзя наследовать?

можно ли использовать сочетание спецификаторов sealed и abstract при описании класса, и почему?

Самостоятельная работа

Используя дополнительную литературу и Интернет, рассмотрите следующие темы:

Идентификация типов во время выполнения программы: использование операторов is, as, typeof.

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

Лекция 15. Интерфейсы и структуры

15.1. Пользовательские и стандартные интерфейсы

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

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

[атрибуты] [спецификаторы] interface имя_интерфейса : [предки]

//объявление функциональных членов интерфейса без реализации

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

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

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

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

В качестве примера рассмотрим интерфейс IDemo и его реализацию для классов DemoPoint и DemoLine из предыдущей лекции:

void Show(); //объявление метода

double Dlina(); //объявление метода

int X //объявление свойства, доступного только для чтения

int this [int i] //объявление индексатора, доступного для чтения-записи

//класс DemoPoint наследует интерфейс IDemo

protected int x;

protected int y;

public DemoPoint ( int x, int y)

public void Show() //реализация метода, объявленного в интерфейсе

Console.WriteLine(«точка на плоскости: (<0>, <1>)»,x, y);

Читать:
Как сделать ходьбу в unity 2d

public double Dlina() //реализация метода, объявленного в интерфейсе

public int X //реализация свойства, объявленного в интерфейсе

public int this [int i] //реализация индексатора, объявленного в интерфейсе

else if (i==1) return y;

else throw new Exception («недопустимое значение индекса»);

else if (i==1) y=value;

else throw new Exception («недопустимое значение индекса»);

//класс DemoShape наследует класс DemoPoint и интерфейс IDemo

class DemoShape : DemoPoint, IDemo

protected int z;

public DemoShape(int x, int y, int z):base(x, y)

// реализация метода, объявленного в интерфейсе, с сокрытием одноименного метода из

public new void Show()

Console.WriteLine(«точка в пространстве: (<0>, <1>, <2>)», x, y, z);

// реализация метода, объявленного в интерфейсе, с сокрытием одноименного метода из

public new double Dlina()

// реализация индексатора, объявленного в интерфейсе, с сокрытием одноименного

// индексатора из базового класса

public new int this [int i]

else if (i==1) return y;

else if (i==2) return z;

else throw new Exception («недопустимое значение индекса»);

else if (i==1) y=value;

else if (i==2) z=value;

else throw new Exception («недопустимое значение индекса»);

static void Main()

//создание массива интерфейсных ссылок

IDemo []a =new IDemo[4];

a[1]=new DemoPoint(-3, 0);

a[3]= new DemoShape(0,5, 6);

foreach (IDemo x in a)

Console.WriteLine(«x новые координаты — «);

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

Добавьте в интерфейс IDemo свойство Y, которое позволит обращаться для чтения к значению поля у. Реализуйте работу с данным свойством в классах DemoPoint и DemoShape.

Добавьте свойство Z для обращения к полю z класса DemoShape. Подумайте, куда именно нужно добавить определение данного свойства и почему.

Защищенный доступ и исключение наследования

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

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

Использование ключевого слова private

В данном примере класс MyClass наследуется классом ClassA, а его члены x, y и z объявлены как protected, и поэтому они доступны для метода LongVector(). Если бы члены x, y и z класса MyClass были объявлены как private, то они оказались бы недоступными для класса ClassA, и приведенный выше код нельзя было бы скомпилировать.

Аналогично состоянию public и private, состояние protected сохраняется за членом класса независимо от количества уровней наследования. Поэтому когда производный класс используется в качестве базового для другого производного класса, любой защищенный член исходного базового класса, наследуемый первым производным классом, наследуется как защищенный и вторым производным классом.

Хотя protected-поля данных могут нарушить инкапсуляцию, объявлять protected-методы достаточно безопасно (и полезно). При построении иерархий классов очень часто приходится определять набор методов, которые используются только производными типами.

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

Ключевое слово sealed

В C# поддерживается еще одно ключевое слово — sealed, которое исключает наследование. Если класс помечен как sealed (запечатанный), компилятор не позволяет наследовать от него. Ниже приведен пример объявления класса типа sealed:

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

Чаще всего запечатывание имеет смысл при проектировании служебного класса. Например, в пространстве имен System определено множество запечатанных классов. В этом легко убедиться, открыв окно Object Browser в Visual Studio 2010 (через меню View (Вид)) и выбрав класс String, определенный в пространстве имен System внутри сборки mscorlib.dll:

Множество запечатанных типов

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

Диаграммы классов Visual Studio

Среда Visual Studio 2010 позволяет устанавливать между классами отношения «базовый-производный» визуально во время проектирования. Чтобы использовать этот аспект IDE-среды, первый шаг состоит во включении нового файла диаграммы классов в текущий проект. Для этого выберите в меню пункт Project — Add New Item (Проект — Добавить новый элемент) и затем пиктограмму Class Diagram (Схема классов):

Создание диаграммы классов

После щелчка на кнопке Add (Добавить) появится пустая поверхность проектирования. Для добавления классов к диаграмме просто перетаскивайте каждый файл из окна Solution Explorer на эту поверхность. Также помните, что удаление элемента в визуальном конструкторе (за счет его выбора и нажатия клавиши <Delete>). не приводит к удалению ассоциированного исходного кода, а просто убирает элемент из поверхности проектирования:

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

Nicolas Chabanovsky's user avatar

Для предотвращения наследования от класса необходимо объявить его с модификатором sealed. Например, если мы не хотим чтобы от класса SomeClass можно было наследоваться, то объявляем его так:

Nicolas Chabanovsky's user avatar

В качестве альтернативы, можно запретить и создание экземпляров этого класса с помощью ключевого слова static (под капотом, будет использована пара модификаторов: abstract sealed ).

Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.3.11.43300

Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

Похожие статьи