C# — Создание объекта формы в обобщенном классе


Содержание

Обобщения

Глава из книги “Язык программирования C# 2005 (Си Шарп) и платформа .NET 2.0”


Автор: Эндрю Троелсен
Источник: Язык программирования C# 2005 (Си Шарп) и платформа .NET 2.0
Материал предоставил: Издательство »Вильямс»

Опубликовано: 30.09.2006
Версия текста: 1.0

С появлением .NET 2.0 язык программирования C# стал поддерживать новую возможность CTS (Common Type System — общая система типов), названную обобщениями (generics). Упрощенно говоря, обобщения обеспечивают программисту возможность определения “заполнителей” (формально называемых параметрами типа ) для аргументов методов и определений типов, которые будут конкретизированы во время вызова обобщенного метода или при создании обобщенного типа.

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

Снова о создании объектных образов, восстановлении значений и System.Object

Чтобы понять, в чем заключаются преимущества использования обобщений, следует выяснить, какие проблемы возникают у программиста без их использования. Вы должны помнить из главы 3, что платформа .NET поддерживает автоматическое преобразование объектов стека и динамической памяти с помощью операций создания объектного образа и восстановления из объектного образа. На первый взгляд, эта возможность кажется не слишком полезной и имеющей, скорее, теоретический, чем практический интерес. Но на самом деле возможность создания объектного образа и восстановления из объектного образа очень полезна тем, что она позволяет интерпретировать все, как System.Object, оставив все заботы о распределении памяти на усмотрение CLR.

Чтобы рассмотреть особенности процесса создания объектного образа, предположим, что мы создали System.Collections.ArrayList для хранения числовых (т.е. размещаемых в стеке) данных. Напомним, что все члены ArrayList обладают прототипами для получения и возвращения типов System.Object. Но вместо того, чтобы заставлять программиста вручную вкладывать размещенное в стеке целое число в соответствующую объектную оболочку, среда выполнения делает это автоматически с помощью операции создания объектного образа.

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

Для представления операции создания объектного образа в терминах CIL компилятор C# использует блок box. Точно так же операция восстановления из объектного образа преобразуется в CIL-блок unbox. Вот соответствующий CIL-код для показанного выше метода Main() (этот код можно увидеть с помощью ildasm.exe).

Обратите внимание на то, что перед обращением к ArrayList.Add() размещенное в стеке значение System.Int32 преобразуется в объект, чтобы передать требуемый System.Object. Также заметьте, что при чтении из ArrayList с помощью индексатора типа (что отображается в скрытый метод get_Item()) объект System.Object восстанавливается в System.Int32 только для того, чтобы снова стать объектным образом при передаче методу Console.WriteLine().

Проблемы создания объектных образов и восстановления значений

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

  1. Новый объект нужно разместить в управляемой динамической памяти.
  2. Значение размещенных в стеке данных нужно записать в соответствующее место в памяти.
  3. При восстановлении значения, сохраненного в объекте, размещенном в динамической памяти, это значение нужно снова вернуть в стек.
  4. Неиспользуемый объект в управляемой динамической памяти (в конце концов) должен быть уничтожен сборщиком мусора.

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

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

В идеальной ситуации компилятор C# должен решать проблемы некорректных операций восстановления из объектного образа во время компиляции, а не в среде выполнения. В связи с этим, в действительно идеальной ситуации, можно было бы сохранить типы, характеризуемые значениями, в контейнере, который не требовал бы преобразования в объект. В .NET 2.0 обобщения дают решение именно этих проблем. Однако перед тем как углубиться в детали использования обобщений, давайте посмотрим, как программисты пытались бороться с этими проблемами в .NET 1.x. с помощью строго типизованных коллекций.

Типовая безопасность и строго типизованные коллекции

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

Чтобы построить коллекцию персон, можно определить член-переменную System.Collections.ArrayList в рамках класса PeopleCollection и настроить все члены на работу со строго типизованными объектами Person, а не с общими объектами System.Object.

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

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

Вы, наверное, знаете из своего собственного опыта, что процесс создания множества строго типизованных коллекций для учета различных типов является не только трудоемким, но просто кошмарным для последующего обслуживания. Обобщенные коллекции позволяют отложить указание спецификации содержащегося типа до времени создания. Пока что не слишком беспокойтесь о синтаксических деталях. Рассмотрите следующий программный код, в котором используется обобщенный класс с именем System.Collections.Generic.List<> для создания двух контейнерных объектов, обеспечивающих типовую безопасность.

Проблемы создания объектных образов и строго типизованные коллекции

Строго типизованные коллекции можно найти в библиотеках базовых классов .NET, и это очень полезные программные конструкции. Однако эти пользовательские контейнеры мало помогают в решении проблем создания объектных образов. Даже если вы создадите пользовательскую коллекцию с именем IntCollection, предназначенную для работы только с типами данных System.Int32, вам придется создать объект некоторого типа для хранения самих данных (System.Array, System.Collections.ArrayList и т.п.).

Вне зависимости от того, какой тип вы выберете для хранения целых чисел (System.Array, System.Collections.ArrayList и т.п.), вы не сможете избавиться от проблемы .NET 1.1, связанной с созданием объектных образов. Нетрудно догадаться, что здесь снова на помощь приходят обобщения. В следующем фрагменте программного кода тип System.Collections.Generic.List<> используется для создания контейнера целых чисел, не имеющего проблем создания объектных образов и восстановления значений при вставке и получении типов характеризуемых значений.

Просто в качестве подтверждения рассмотрите следующий CIL-код для этого метода Main() (обратите внимание на отсутствие в нем каких бы то ни было блоков box и unbox).

Теперь, когда вы имеете лучшее представление о роли обобщений в .NET 2.0, мы с вами готовы углубиться в детали. Для начала мы формально рассмотрим пространство имен System.Collections.Generic.

Пространство имен System.Collections.Generic

Обобщенные типы присутствуют во многих библиотеках базовых классов .NET 2.0, но пространство имен System.Collections.Generic буквально наполнено ими (что вполне соответствует его названию). Подобно своему “родственнику” без обобщений (System.Collections), пространство имен System.Collections.Generic содержит множество типов класса и интерфейса, что позволяет вкладывать элементы в самые разные контейнеры. Совсем не удивительно, что обобщенные интерфейсы имитируют соответствующие необобщенные типы из пространства имен System.Collections.

  • ICollection
  • IComparer
  • IDictionary
  • IEnumerable
  • IEnumerator
  • IList

ПРИМЕЧАНИЕ

По соглашению для обобщенных типов их замещаемые параметры обозначаются буквами верхнего регистра. И хотя здесь допустимо использовать любые буквы (или слова), обычно используют T для обозначения типов, K — для ключей, а V — для значений.

В пространстве имен System.Collections.Generic определяется и ряд классов, реализующих многие из этих ключевых интерфейсов. В табл. 10.1 представлены описания базовых типов класса из этого пространства имен, реализуемые ими интерфейсы и соответствующие типы из пространства имен System.Collections.

Обобщенный класс Необобщенный аналог в System.Collections Описание
Collection CollectionBase База для обобщенной коллекции
Comparer Comparer Выполняет сравнение двух обобщенных объектов
Dictionary Hashtable Обобщенная коллекция пар имен и значений
List ArrayList Список элементов с динамически изменяемыми размерами
Queue Queue Обобщенная реализация списка FIFO (дисциплина обслуживания типа “очередь”)
SortedDictionary SortedList Обобщенная реализация сортированного набора пар имен и значений
Stack Stack Обобщенная реализация списка LIFO (дисциплина обслуживания типа “стек”)
LinkedList Обобщенная реализация двусвязного списка
ReadOnlyCollection ReadOnlyCollectionBase Обобщенная реализация набора элементов только для чтения
Таблица 10.1. Классы System.Collections.Generic

В пространстве имен System.Collections.Generic также определяется целый ряд “вспомогательных” классов и структур для работы с конкретными контейнерами. Например, тип LinkedListNode представляет узел в обобщенном LinkedList , исключение KeyNotFoundException возникает при попытке доступа к элементу контейнера с несуществующим ключом и т.д.

Как видно из табл. 10.1, многие обобщенные классы коллекции имеют необобщенные аналоги в пространстве имен System.Collections (иногда даже с одинаковыми именами). В главе 7 было показано, как работать с такими необобщенными типами, поэтому дальше не предполагается рассматривать все их обобщенные “дубликаты”. Мы рассмотрим только List , чтобы проиллюстрировать приемы использования обобщений. Если вам нужны подробности о других элементах пространства имен System.Collections.Generic, обратитесь к документации .NET Framework 2.0.

Тип List

Подобно необобщенным классам, обобщенные классы являются объектами, размещаемыми в динамической памяти, поэтому для них следует использовать new со всеми необходимыми аргументами конструктора. Кроме того, вы должны указать типы, замещающие параметры, определенные обобщенным типом. Так, для System.Collections.Generic.List требуется указать одно значение, задающее вид элемента, с которым будет функционировать List . Например, чтобы создать три объекта List<> для хранения целых чисел, объектов SportsCar и объектов Person, вы должны записать следующее.

В этот момент вы можете поинтересоваться, что же на самом деле становится значением заполнителя. Открыв окно определения программного кода в Visual Studio 2005 (см. главу 2), вы увидите, что везде в определении типа List используется заполнитель T. Ниже показана часть соответствующего листинга (обратите внимание на элементы, выделенные полужирным шрифтом).

Когда вы создаете тип List и указываете для него SportsCar, это эквивалентно следующему определению типа List .

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

Если с помощью ildasm.exe проверить генерируемый CIL-код, обнаружатся следующие подстановки.

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

Создание обобщенных методов

Чтобы научиться интегрировать обобщения в проекты, мы начнем с простого примера обычной подпрограммы свопинга. Целью этого примера является построение метода обмена, который сможет работать с любыми типами данных (характеризуемыми значениями или ссылками), используя для этого один параметр типа. В силу самой природы алгоритмов свопинга входные параметры будут посылаться по ссылке (с помощью ключевого слова C# ref). Вот соответствующая полная реализация.

Обратите внимание на то, что обобщенный метод определяется с помощью указания параметра типа, размещаемого после имени метода, но перед списком параметров. Здесь вы заявляете, что метод Swap() может работать с любыми двумя параметрами типа . Просто для информации вы выводите имя типа соответствующего заменителя на консоль с помощью оператора C# typeof(). Теперь рассмотрите следующий метод Main(), в котором происходит обмен между целочисленными и строковыми типами.

Пропуск параметров типа

При вызове обобщенных методов, подобных Swap , у вас есть возможность не указывать параметр типа, но только в том случае, когда обобщенный метод требует указания аргументов, поскольку тогда компилятор может “выяснить” тип этих аргументов на основе вводимых параметров. Например, можно переставить два типа System.Boolean так.

Но если, например, у вас есть обобщенный метод с именем DisplayBaseClass , не имеющий входных параметров, как показано ниже:

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

Рис. 10.1. Обобщенные методы в действии

В данном случае обобщенные методы Swap и DisplayBaseClass были определены в рамках объекта приложения (т.е. в рамках типа, определяющего метод Main()). Если вы предпочтете определить эти члены в новом типе класса (MyHelperClass), то должны записать следующее.

Обратите внимание на то, что тип MyHelperClass сам по себе не является обобщенным, но определяет два обобщенных метода. Так или иначе, теперь, когда методы Swap и DisplayBaseClass находятся в контексте нового типа класса, при вызове их членов придется указать имя типа.

Наконец, обобщенные методы не обязаны быть статическими. Если бы Swap и DisplayBaseClass были методами уровня экземпляра, нужно было бы просто создать экземпляр MyHelperClass и вызвать их из объектной переменной.

Создание обобщенных структур (и классов)



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

Вот полное определение Point , необходимое нам для дальнейшего анализа.

Ключевое слово default в обобщенном программном коде

Как видите, Point использует параметр типа в определении полей данных, аргументов конструктора и в определениях свойств. Обратите внимание на то, что вдобавок к переопределению ToString() обобщенный тип Point определяет метод ResetPoint(), в котором используется новый синтаксис.

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

  • Для числовых значений значением по умолчанию является 0.
  • Для ссылочных типов значением по умолчанию является null.
  • Поля структуры устанавливаются равными 0 (для типов, характеризуемых значениями) или null (для ссылочных типов).

Для Point вы можете непосредственно установить xPos и yPos равными 0, поскольку вполне безопасно предполагать, что вызывающая сторона будет поставлять только числовые данные. Однако с помощью синтаксиса default(T) вы можете сделать обобщенный тип более гибким. В любом случае вы теперь можете использовать методы Point так.

Соответствующий вывод показан на рис. 10.2.

Рис. 10.2. Использование обобщенного типа Point

Создание пользовательских обобщенных коллекций

Итак, пространство имен System.Collections.Generic предлагает множество типов, позволяющих создавать эффективные контейнеры, удовлетворяющие требованиям типовой безопасности. С учетом множества доступных вариантов очень велика вероятность того, что в .NET 2.0 у вас вообще не возникнет необходимости в построении пользовательских типов коллекции. Тем не менее, чтобы показать, как строится обобщенный контейнер, нашей следующей задачей будет создание обобщенного класса коллекции, который мы назовем CarCollection .

Подобно созданному выше необобщенному типу CarCollection, наш новый вариант будет использовать уже существующий тип коллекции для хранения своих элементов (в данном случае это List<>). Будет реализована и поддержка цикла foreach путем реализации обобщенного интерфейса IEnumerable<>. Обратите внимание на то, что IEnumerable<> расширяет необобщенный интерфейс IEnumerable, поэтому компилятор ожидает, что вы реализуете две версии метода GetEnumerator(). Вот как может выглядеть соответствующая модификация.

Этот обновленный тип CarCollection можно использовать так.

Здесь создается тип CarCollection , который должен содержать только типы Car. Снова заметим, что того же результата можно достичь и с помощью непосредственного использования типа List . Главным преимуществом данного подхода является то, что теперь вы можете добавлять в CarCollection уникальные методы, делегирующие запросы к внутреннему типу List .

Установка ограничений для параметров типа с помощью where

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

Чтобы проиллюстрировать другую форму типичного непредусмотренного использования объекта, предположим, что вы создали два новых класса — SportsCar (спортивная машина) и MiniVan (минивэн), — которые являются производными от Car.

В соответствии с законами наследования, в коллекцию CarCollection , созданную с параметром типа Car, можно добавлять и типы MiniVan и SportsCar.

Это синтаксически корректно, но что делать, если вдруг понадобится добавить в CarCollection новый открытый метод, например, с именем PrintPetName()? Такая задача кажется простой — достаточно получить доступ к подходящему элементу из List и вызвать свойство PetName.

Однако в таком виде программный код скомпилирован не будет, поскольку истинная суть еще не известна, и вы не можете с уверенностью утверждать, что какой-то элемент типа List будет иметь свойство PetName. Когда параметр типа не имеет никаких ограничений (как в данном случае), обобщенный тип называется свободным (unbound). По идее параметры свободного типа должны иметь только члены System.Object (которые, очевидно, не имеют свойства PetName).

Вы можете попытаться “обмануть” компилятор путем преобразования элемента, возвращенного из метода индексатора List , в строго типизованный объект Car, чтобы затем вызвать PetName возвращенного объекта.

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

Для решения именно таких проблем обобщения .NET могут опционально определяться с ограничениями, для чего используется ключевое слово where. В .NET 2.0 обобщения могут иметь ограничения, описанные в табл. 10.2.

Ограничение обобщения Описание
where T : struct Параметр типа должен иметь в цепочке наследования System.ValueType
where T : class Параметр типа не должен иметь в цепочке наследования System.ValueType (т.е. должен быть ссылочным типом)
where T : new() Параметр типа должен иметь конструктор, заданный по умолчанию. Это полезно тогда, когда обобщенный тип должен создать экземпляр параметра типа, а вы не имеете ясных предположений о формате пользовательских конструкторов. Заметьте, что это ограничение должно быть последним в списке ограничений, если у типа их несколько
where T : БазовыйКласс Параметр типа должен быть производным класса, указанного параметром БазовыйКласс
where T : Интерфейс Параметр типа должен реализовывать интерфейс, указанный параметром Интерфейс
Таблица 10.2. Возможные ограничения обобщений для параметров типа

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

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

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

При таких ограничениях на CarCollection реализация PrintPetName() становится очень простой, поскольку теперь компилятор может предполагать, что является производным от Car. Более того, если указанный пользователем параметр типа не совместим с Car, будет сгенерирована ошибка компиляции.

Вы должны понимать, что обобщенные методы тоже могут использовать ключевое слово where. Например, если нужно гарантировать, чтобы методу Swap(), созданному в этой главе выше, передавались только типы, производные от System.ValueType, измените свой программный код так.

Следует также понимать то, что при таком ограничении метод Swap() уже не сможет переставлять строковые типы (поскольку они являются ссылочными).

Отсутствие поддержки ограничений при использовании операций

При создании обобщенных методов для вас может оказаться сюрпризом появление ошибок компилятора, когда с параметрами типа используются операции C# (+, -, *, == и т.д.). Например, я уверен, вы сочли бы полезными классы Add(), Subtract(), Multiply() и Divide(), способные работать с обобщенными типами.

Как ни печально, этот класс BasicMath не компилируется. Это может показаться большим ограничением, но не следует забывать, что обобщения являются обобщениями . Конечно, тип System.Int32 может прекрасно работать с бинарными операциями C#. Однако, если, например, будет пользовательским классом или типом структуры, компилятор не сможет сделать никаких предположений о характере перегруженных операций +, -, * и /. В идеале C# должен был бы позволять обобщенному типу ограничения с использованием операций, например, так.

Увы, ограничения обобщенных типов при использовании операций в C# 2005 не поддерживаются.

Создание обобщенных базовых классов

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

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

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

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

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

Вы уже видели при рассмотрении пространства имен System.Collections.Generic, что обобщенные интерфейсы в C# также допустимы (например, IEnumerable ). Вы, конечно, можете определить свои собственные обобщенные интерфейсы (как с ограничениями, так и без ограничений). Предположим, что нужно определить интерфейс, который сможет выполнять бинарные операции с параметрами обобщенного типа.

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

После этого вы можете использовать BasicMath, как и ожидали.

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

Создание обобщенных делегатов

Наконец, что не менее важно, .NET 2.0 позволяет определять обобщенные типы делегата. Предположим, например, что требуется определить делегат, который сможет вызывать любой метод, возвращающий void и принимающий один аргумент. Если аргумент может меняться, это можно учесть с помощью параметра типа. Для примера рассмотрим следующий программный код (обратите внимание на то, что целевые объекты делегата регистрируются как с помощью “традиционного” синтаксиса делегата, так и с помощью группового преобразования метода).

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

С учетом формата объекта strTarget метод StringTarget() должен теперь получить в качестве параметра одну строку.

Имитация обобщенных делегатов в .NET 1.1

Как видите, обобщенные делегаты предлагают более гибкий подход для указания вызываемых методов. В рамках .NET 1.1 аналогичного результата можно достичь с помощью базового System.Object.

Это позволяет посылать любой тип данных целевому объекту делегата, но при этом не гарантируется типовая безопасность, а кроме того, остаются нерешенными проблемы создания объектных образов. Предположим, например, что мы создали два экземпляра MyDelegate, которые указывают на один и тот же метод MyTarget. Обратите внимание на проблемы создания объектных образов и восстановления значений, а также на отсутствие гарантий типовой безопасности.

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

Несколько слов о вложенных делегатах

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

Резюме

Обобщения можно обоснованно считать главным из усовершенствований, предложенных в C# 2005. Как вы могли убедиться, обобщенный элемент позволяет указать “заполнители” (т.е. параметры типа), которые конкретизируются в момент создания типа (или вызова, в случае обобщенных методов). По сути, обобщения дают решение проблем объектных образов и типовой безопасности, усложняющих разработку программ в среде .NET 1.1.

Чаще всего вы будете просто использовать обобщенные типы, предлагаемые библиотеками базовых классов .NET, но можно создавать и свои собственные обобщенные типы. При создании обобщенных типов можно указать любое число ограничений, чтобы повысить уровень типовой безопасности и гарантировать, что соответствующие операции будут выполняться с “известными величинами”.


C# — Создание объекта формы в обобщенном классе

В книге Павла Агурова «C# Сборник рецептов» приводится очень интересный пример по созданию объекта используя его имя. Для того, чтобы создать объект по имени типа используются методы отражения (reflection). Если класс создаваемого объекта находится в том же простанстве имен, что и создающий код, то можно использовать простое создание экземпляра объекта по его типу. Такой способ удобн, если описание набора объектов находится в некотором конфигурационном файле (например, XML). Создание объектов по имени типа позволяет избавиться от оператора switch, создающего нужный объект в зависимости от имени.

Если класс имеет несколько конструкторов или конструктор с параметрами, то необходимо указать, какой именно контсруктор нужно найти:

Вот так просто все это делается.

Reflection1.rar (19,6 KiB, 784 закачек)

Обобщенные классы в С#

Введение

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

Запустите программу Visual Studio. Выберите тип проекта и строчку console application.

Нажмите на ярлык файла -> Новое -> Программа.

Выберите строчку Visual C# в левой части окна. Кликните Console Application в правом окне. Назовите программу «GenericClass». Задайте, где Вы хотите сохранить программу. Нажмите Ok.

Введите следующий код в дополнение:

public class Myclass

public void Compareme(T v1, T v2)

Console.Write( «The value is matching» );

Console.Write( «The value is not matching» );

static void Main(string[] args)

My > new Myclass();

objmyint.Compareme( «Amit» , «Amit» );

После введения кода, Вы получите такие исходные данные.

Измените строчку на что-либо другое, проверьте исходные данные. В примере создан второй строчный параметр «amit».

Урок C# №14: «Пишем свой класс»

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

свойства;

методы; [деструктор] //такая же ситуация, как и с конструктором >

Класс относится к типам данных, разрабатываемом самим программистом. Чтобы сообщить компилятору, что это класс, мы объявляем его ключевым словом class, после которого уже даем имя класса. Затем в фигурных скобках мы описываем поля, свойства, методы, конструкторы (если они нужны) и деструкторы. Кроме того, в C#, равно как и в его предшественнике C++, нужно задавать модификаторы доступа. Подробнее о них поговорим в других уроках.

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

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

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

Давайте создадим наш первый класс. Пусть это будет класс Human. Именно на базе этого класса мы и будем разбирать все ООП моего курса по C#. В этом уроке мы создадим каркас класса. Итак, запустите вашу Visual Studio и создайте новый проект, назовите его к примеру OOP. Теперь нам нужно создать новый класс Human. Для этого зайдите в Обозреватель решений вашего проекта, нажмите правую кнопку мышки и выберите Добавить ->Класс. После этого выберите шаблон класса, дайте ему имя Human и нажмите Ок. Среда создаст нам готовый скелет будущего класса. Физически у вас в папке проекта создастся файл Human.cs. Его потом можно будет подключать к нашей программе (по факту, классу) или к другому. Фактически, сейчас я несколько вас ввел в заблуждение тем фактом, что не создал новое пространство имен. Часто нужно это делать, потому что потом проще делать сборки и группировать классы. Но об этом поговорим в других уроках. Как видите, студия любезно предоставила нам шаблон класса, где уже объявлен Human. Мы можем в нашем проекте теперь его использовать в даже таком примитивном виде. Делается достаточно просто:

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

BestProg

Класс. Описание класса. Объект класса. Поля класса. Примеры. Ключевое слово this . Массив объектов в классе

Содержание

1. Что такое класс и объект класса?

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

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

2. Какое отличие между описанием класса и объектом класса?

При объявлении класса память не выделяется. Формируется только описание элементов класса.

Описание класса еще называют объявлением класса. В описании класса указываются данные и программный код. Как правило, программный код выполняет какую-то работу над данными в классе (оперирует данными в классе). При описании класса оперативная память для данных и программного кода не выделяется. Описание класса – это своего рода информация о том, какие данные есть в этом классе и какую работу (методы) выполняются над этими данными.

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

3. Что объявляется в классе?

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

Также класс может содержать только данные или только программный код. Но при работе с классами большинство классов содержат и данные и программный код.

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

4. Что называется полями класса?

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

5. Какие элементы языка C# могут относиться к функциям-членам класса?

К функциям-членам класса могут относиться:

  • методы;
  • конструкторы;
  • деструкторы;
  • индексаторы;
  • события;
  • операторы;
  • свойства.
6. Какая общая форма описания класса?

Класс создается с помощью ключевого слова class . Общая форма описания класса имеет вид:

В вышеприведенном описании:

  • имя_класса – имя, которое дается классу. Имя класса определяет новый тип данных. Имя класса может отвечать сущности класса. В свою очередь, класс должен определять одну (и только одну) логическую сущность;
  • доступ – это тип доступа. В языке C# предусмотрены четырех модификаторы доступа: public (общедоступный член класса), private (скрытый член), protected (защищенный) и internal . Модификатор internal определяет доступность члена класса во всех членах сборки и его недоступность за пределами сборки.
  • тип – тип переменной, которая есть членом класса;
  • переменная1 , переменная2 , …, переменнаяN – переменные, которые есть членами класса (данными в классе);
  • метод1 , метод2 , …, методN – методы, которые есть членами класса;
  • возвращаемый_тип – тип, который возвращается методом класса;
  • параметры – параметры методов, которые определяются в классе.
7. Какие существуют типы (модификаторы) доступа к членам класса?

Доступ к членам класса может быть пяти типов:


  • private – закрытый доступ. В этом случае члены (данные или методы) класса доступны только в границах класса. Это означает, что доступ к private членам класса имеют только переменные и методы этого класса;
  • public – открытый доступ. В этом случае члены класса есть доступными за пределами класса. Это означает, что public -члены класса можно использовать во всех других фрагментах кода – даже тех, что определенны за пределами класса;
  • protected – защищенный доступ. Данный тип используется в случае наследственности при построении иерархии классов. В этом случае protected -члены класса есть защищенными. Такие члены класса есть открытыми в границах иерархии классов, но закрытыми за пределами иерархии классов;
  • internal – определяет доступность члена класса во всех файлах сборки и его недоступность за пределами сборки. Это означает, что internal -члены класса известны только в самой программе, но не за ее пределами. Данный модификатор полезен при создании программных компонент;
  • protected internal — это есть комбинация модификаторов protected и internal с использованием логической операции «ИЛИ» . Такой член класса видим в пределах класса а также в унаследованном классе. Унаследованный класс может размещаться в той же самой сборке или даже в другой сборке.
8. Какие возможности доступа дает использование спаренного модификатора доступа protected internal ?

Спаренный идентификатор доступа protected internal состоит из двух отдельных модификаторов доступа protected и internal .

Такой уровень доступа может быть задан только для членов класса. Член класса, который объявлен как protected internal , доступен только в границах собственной сборки ( internal ) или для производных классов или типов ( protected ).

9. Может ли отсутствовать тип (модификатор) доступа при описании членов класса?

Ответ: да, может быть. В этом случае объявляемый член класса считается закрытым по умолчанию – private .

Пример.

10. Примеры описания простейших классов, содержащих только данные.

Пример 1. Класс, содержащий только данные. В этом примере создается новый тип MyPoint . Описывается общедоступный класс ( public ) MyPoint , описывающий точку на координатной плоскости.

Пример 2. Описание класса с именем MyBook , содержащего данные о книге.

11. Как создать объект (экземпляр класса)?

После описания класса создается новый тип. В предшествующем пункте создается тип с именем MyPoint . Объект класса описывается как обычная переменная некоторого типа.

Пример 1. Создание экземпляра объекта типа MyPoint (смотрите предыдущий пункт).

Способ 1. Создание экземпляра объекта типа MyPoint с именем p .

Пример 2. Создание экземпляра класса типа MyBook (смотрите предыдущий пункт) с именем b .

12. К каким типам данных принадлежат классы: к типам значения или к ссылочным типам?

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

Пример.

В данном примере в переменную p помещается адрес созданного экземпляра класса в оперативной памяти. Следующий рисунок схематично объясняет отличие между ссылочными типами и типами-значений .

13. Пример описания класса, который содержит данные и методы (функции).

Над данными в классе можно выполнять некоторую работу. Это осуществляется функциями-членами.

В следующих фрагментах кода описываются классы с собственными функциями.

Пример 1. Модификация класса MyPoint , содержащего координаты точек. В класс добавлены четыре новые функции-члены (методы):

  • функция GetX() , возвращающая координату x ;
  • функция GetY() , возвращающая координату y ;
  • функция SetX() , устанавливающая координату x в новое значение;
  • функция SetY() , устанавливающая координату y в новое значение.

Внутренние переменные x , y класса объявлены как private .

14. Пример использования массива объектов класса и описания деструктора в классе

В приведенном примере создается два класса. Первый типа « КНИГА » ( MyBook ), второй – массив книг MyBooks . В классе MyBooks описывается деструктор.

15. Какие особенности использования ключевого слова this в классе?

При объявлении объекта на класс, все члены этого объекта получают неявно ссылку this , которая есть ссылкой на этот же объект.

Пример. Объявление класса DemoThis . В классе есть два поля. Одно поле скрытое ( private ) типа int с именем i . Второе поле общедоступно ( public ) типа double с именем d .

В классе описаны два метода с именами GetI1() и GetI2() . Эти методы возвращают значение поля i . Эти методы выполняют одинаковую работу, но доступ к полю i осуществляется по разному. То же касается и методов GetD1() и GetD2() .

В конструкторе класса DemoThis ( int , double ), получющего два параметра, ключевое слово this служит для доступа к полям (членам данных) i и d класса. Это обусловлено тем, что локальные переменные-параметры конструктора с такими же именами ( i и d ) скрывают объявленные члены данных класса.

Создание обобщенных базовых классов

Создание обобщенных базовых классов

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

// Предположим, что создан пользовательский

// обобщенный класс списка.

public class MyList‹T› <

private List‹T› listOfData = new List‹T›();

// Конкретные типы должны указать параметр типа,

// если они получаются из обобщенного базового класса.

public class MyStringList: MyList‹string› <>

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

// Обобщенный класс с виртуальным методом.

public class MyList‹T› <

private List‹T› listOfData = new List‹T›();

public virtual void PrintList(T data) <>

public class MyStringList: MyList‹string› <

// В производных методах нужно заменить параметр типа,

// используемый а родительском классе.

public override void PrintList(string data) <>

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

// Обратите внимание, теперь здесь имеется ограничение,

// требующее конструктор по умолчанию.

public class MyList‹T› where T: new() <

private List‹T› listOfData = new List‹T›();


public virtual void PrintList(T data) <>

// Производный тип должен учитывать ограничения базового.

public class MyReadOnlyList‹T›: MyList‹T› where T: new() <

public override void PrintList(T data) <>

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

C# — Создание объекта формы в обобщенном классе

уже замучился, но так и не смогу найти ответ.

Хочется разделить проект.
Создаю клсс который будет рисовать.

Смысл такой.
Создаю класс(отдельным файлом) который будет рисовать кнопки на MainPage.
Вопрос:
Как обратиться из клсса (Class1) в класс MainPage(MainPage.xaml.cs) и нарисовать кнопку?

Сейчас делаю так.
В классе (Class1) создаю

Спасибо за ответ.
Думаю что ещё что то нужно, я обращаюсь к классу MainPage, а не к форме.
То есть, даже если я смогу поменять чтото в классе(MainPage) то мне это нечего не даст, ибо придётесь разрушить объект mainPage(я не уверен но возможно объект формы будет с маленькой буквы) и создать его заново, что естественно не входит в мои планы.
На данный момент обратиться к mainPage(с мал. буквы) скорее всего я не смогу, ибо такого обекта не существует.

Чёт я это ООП не могу понять
Вот есть класс «public partial class MainPage : PhoneApplicationPage»
внутри есть public MainPage() который инициализирует форму(InitializeComponent();).
А как узнать имя этй самой созданной формы, а точнее обратиться чтобы создать там кнопку?

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

C#: классы, структуры, наследование

Классы и структуры в C# относятся к пользовательским типам (Custom types), т.е. типам, введенным разработчиком.

Классы

Класс — самый общий ссылочный тип. Простейшее объявление класса выглядит следующим образом:

Перед ключевым словом class могут присутствовать атрибуты (attributes) и модификаторы класса (class modifiers): public , internal , abstract , sealed , static , unsafe и partial . После названия класса могут указываться параметры обобщенного типа (generic type parameters), базовый класс (base class) и интерфейсы (interfaces). Внутри фигурных скобок могут располагаться члены класса (сlass members): методы (methods), свойства (properties), индексаторы (indexers), события (events), поля (fields), конструкторы (constructors), перегруженные операторы (overloaded operators), вложенные типы (nested types) и файналазер (finalizer).

Поля (Fields)

Поле — это переменная, являющаяся членом класса или структуры.

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

Инициализировать поля не обязательно. Поля, которым не присвоено значение, принимают значение по умолчанию ( 0 , \0 , null , false ). Инициализация полей запускается до выполнения конструктора, в том порядке, в котором следуют поля.

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

Методы (Methods)

Метод представляет собой действие, состоящие из ряда инструкций. При вызове метод может принимать входные данные в виде параметров и возвращать данные в точку вызова в виде возвращаемого типа. Метод может возвращать тип void , т.е. не возвращать в точку вызова никакого значения. Метод также может возвращать данные в точку вызова с помощью параметров ref и out .

Сигнатура метода должна быть уникальна в пределах типа. Сигнатура метода включает его имя и типы передаваемых параметров (но не имена параметров и не возвращаемый тип).

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

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

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

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

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

Автоматический конструктор. В том случае (и только в том случае) если для класса не задан ни один конструктор компилятор C# автоматически генерирует публичный (public) конструктор без параметров.

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

Непубличный конструктор. Конструктор необязательно должен быть публичным (public). Самый распространенный пример, когда может понадобиться непубличный конструктор — необходимость контролировать создание экземпляров класса через вызов статического метода. Такой класс сначала проверяет наличие уже созданного объекта и если он уже создан — возвращает его, если нет — создает новый. Также такой метод может возвращать определенный подкласс исходя из входных аргументов.

Инициализаторы объекта

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

Ссылка this

Ссылка this указывает на сам экземпляр объекта.

Ссылка this также разрешает неоднозначность между локальными переменными или параметрами и полями объекта.

Ссылку this допустимо использовать только с нестатичными членами класса или структуры.

Свойства (Properties)

Внешне свойства ничем не отличаются от полей, но внутри самого класса они отличаются тем, что подобно методам содержат логику. Свойства определяются также как поля, но с добавлением блока get/set .

get и set — это средства доступа к свойствам (accessors). get выполняется при чтении свойства. Он должен возвращать значение того же типа, что и само свойство. set выполняется когда свойству присваивается значение. Оно имеет скрытый параметр value того же типа, что и само свойство.

Хотя доступ к полям и свойствам осуществляется одинаково, свойства в отличие от полей предоставляют полный контроль над процессом присвоения и получения значений (например, метод set может выбрасывать исключения и т.д.).

Если из средств доступа установлен только get , свойство будет доступно только для чтения (read-only), а если только set — только для записи (write-only). Обычно для свойства создается частное поле, в котором сохраняются данные, но это не обязательно: свойство может возвращать результаты расчета других данных.

Как уже отмечалось, самое распространенное назначение свойств — получение и присвоение значения частным полям того же типа что и свойство. Это можно сделать автоматически, используя объявление автоматических свойств (automatic property).

C# — Создание объекта формы в обобщенном классе

69 просмотра

1 ответ

4 Репутация автора

Я новичок в окнах форм. Я пытаюсь создать экземпляр объекта публичного класса и вызываю метод drawBoard() при нажатии кнопки 1. Метод, с drawBoard() помощью которого я хочу установить свойства pictureBox2. Но код не работал.

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

Оба работали. Непосредственно загружает изображение при запуске кода. А также когда кнопка1 нажата вторым способом. Интересно, как вызвать свойства pictureBox, когда они определены в методе пользовательского класса.

Ответы (1)

4 плюса

65138 Репутация автора

Если вы внимательно наблюдаете, то ваш gameBoard определяется как вложенный класс внутри, Form1 и он также наследуется от Form контроля, что не имеет смысла. Вы, вероятно, хотите, чтобы класс был определен снаружи, как (вероятно, в отдельном файле)

Введение в ООП с примерами на C#. Часть четвёртая. Абстрактные классы


В прошлых статьях серии “Введение в ООП” мы рассматривали полиморфизм (а также его нюансы на практике) и наследование. В этой мы поговорим о самой захватывающей части ООП-парадигмы — об абстрактных классах. В целом концепция абстрактных классов в C# ничем не отличается от таковой в других языках, но в C# работать с ней приходится несколько иначе.

Что такое абстрактные классы

В плане терминологии давайте доверимся MSDN:

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

Абстрактные классы в действии

Итак, попробуем создать абстрактный класс:

Попытаемся скомпилировать этот код:

Compile time error: Cannot create an instance of the abstract class or interface ‘InheritanceAndPolymorphism.ClassA’

Что нужно запомнить: Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова new .

Описание методов в абстрактном классе

Попробуем добавить в наш абстрактный класс немного кода:

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

Использование абстрактного класса в качестве базового

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

Вау. Теперь всё спокойно компилируется.

Что нужно запомнить: Мы можем унаследовать обычный класс от абстрактного.

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

Декларация методов в абстрактном классе

Теперь попробуем сделать вот так:

И… мы получаем ошибку компиляции:

Compile time error: ‘InheritanceAndPolymorphism.ClassA.YYY()’
must declare a body because it is not marked abstract, extern, or partial

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

…и снова получим ошибку компиляции:

Compiler error: ‘InheritanceAndPolymorphism.ClassB’ does not implement
inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’

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

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

Реализация абстрактного метода в производном классе

Так, давайте тогда попробуем реализовать метод YYY() в классе ClassB :

На первый взгляд всё отлично, правда? Но на этот раз мы получим сразу две ошибки компиляции:

Compile time error: ‘InheritanceAndPolymorphism.ClassB’ does not implement
inherited abstract member ‘InheritanceAndPolymorphism.ClassA.YYY()’

Compile time warning: ‘InheritanceAndPolymorphism.ClassB.YYY()’ hides
inherited member ‘InheritanceAndPolymorphism.ClassA.YYY()’.

Дело в том, что в C# нужно явно объявить, что мы реализуем абстрактный метод класса-родителя с помощью ключевого слова override :

Ура! ^_^ У нас наконец-то нет никаких ошибок!

Абстрактный метод базового класса и метод с override класса-наследника должны быть одинаковы

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

То в консоли увидим следующую ошибку:

Compile time error: ‘InheritanceAndPolymorphism.ClassB.YYY()’: return type must be ‘void’
to match overridden member ‘InheritanceAndPolymorphism.ClassA.YYY()’

Инициализация переменных в абстрактных классах

В примерах выше, переменная int a будет обладать значением по умолчанию (0). Мы можем изменить его на нужное нам прямо в абстрактном классе — с этим не связано никаких особенностей.

Абстрактные методы в неабстрактных классах

Такой код не скомпилируется:

Compiler error: ‘InheritanceAndPolymorphism.ClassA.YYY()’ is abstract
but it is contained in non-abstract class ‘InheritanceAndPolymorphism.ClassA’

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

Вызов абстрактного метода родителя

Compile time error : Cannot call an abstract base member:
‘InheritanceAndPolymorphism.ClassA.YYY()’

Разумеется, мы не можем исполнить код, которого не существует.

Абстрактный класс, который наследуется от другого абстрактного класса

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

Может ли абстрактный класс быть sealed

И получим ошибку:

Compile time error: ‘InheritanceAndPolymorphism.ClassA’:
an abstract class cannot be sealed or static

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

Что нужно запомнить: Абстрактный класс не может иметь модификатор sealed .

Что нужно запомнить: Абстрактный класс не может иметь модификатор static .

Что мы узнали сегодня:

Что нужно запомнить

  • Мы не можем создать экземпляр абстрактного класса с помощью ключевого слова new ;
  • Мы можем унаследовать обычный класс от абстрактного;
  • Мы можем создать экземпляр обычного класса, унаследованного от абстрактного, с помощью ключевого слова new ;
  • Если мы хотим объявить метод в абстрактном классе, но не реализовывать его, к методу нужно добавить ключевое слово abstract ;
  • Если мы объявляем абстрактный метод в абстрактном классе, то этот метод должен реализовываться в неабстрактных наследниках этого класса;
  • Абстрактные методы могут быть объявлены только в абстрактных классах;
  • Абстрактный класс не может иметь модификатор sealed ;
  • Абстрактный класс не может иметь модификатор static .
Цукерберг рекомендует:  Новый факультет GeekUniversity искусственный интеллект!
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих