👨🎓️ Самоучитель по C# для начинающих за 30 минут. Часть 2: ООП и коллекции
В этой статье рассмотрим основные принципы объектно-ориентированного программирования, коллекции и другие аспекты на языке C#, без которых программировать будет не совсем комфортно.
ООП
Процедурное программирование строится на написании процедур или методов, которые выполняют операции над данными, а объектно-ориентированное программирование на создании объектов, содержащих как данные, так и методы.
ООП основано на использовании трёх основных принципов:
- Инкапсуляция – это механизм, позволяющий скрыть код класса (поля, методы, функции) и ограничить доступ к коду и данным из других участков кода.
- Наследование – это механизм, позволяющий классу наследовать свойства другого класса, принимая все поля и функции членов базового типа.
- Полиморфизм – это свойство, которое позволяет использовать один интерфейс для общего класса действий. Эта концепция часто выражается как "один интерфейс, несколько действий".
Объектно-ориентированное программирование имеет ряд преимуществ перед процедурным программированием, основными из них являются: быстродействие и многократное использование кода.
Классы и объекты
Язык C# является объектно-ориентированным языком программирования, это позволяет разрабатывать на нём большие программные системы, с возможностью модульной заменой многократно используемого кода.
В C# классы и объекты, а также их атрибуты и методы, являются базовыми инструментами для реализации ООП. Например, смартфон – это объект, имеющий атрибуты, такие как диагональ экрана, установленный процессор, цвет корпуса и методы, такие как вывод на экран, отправка/принятие звонка и другие.
C# поддерживает описание, реализацию и использование классов в одном файле кода и раздельно. Будем использовать именно раздельный подход.
Члены класса: поля и методы
Для добавления класса в проект, в его контекстном меню выбираем пункт Add new item/Добавить новый элемент
и в появившемся окне выбираем – Class/Класс
и вводим его имя.
В результате будет создан файл .cs
с именем класса Phone
. В нём будет объявлен класс с помощью ключевого слова class
и объявление класса может иметь следующий вид:
Класс может включать в себя поля (fields
) – переменные и методы, например, у класса Phone
есть два поля и один метод. Рассмотрим простой пример использования класса.
Поля и методы являются членами класса, в нашем случае они объявлены с модификатором public
, чтобы их можно было использовать в коде программы (класс Program
). Для создания объектов используется конструктор по умолчанию new Phone()
.
В консоль будет выведено следующее:
В результате выполнения выведены значения полей и отработаны методы класса. Но для более эффективного программирования классов используются конструкторы.
Конструкторы
Конструктор – метод, имеющий имя класса, который используется для создания и инициализации объектов. Конструктор вызывается при создании объекта класса и его можно использовать для инициализации начальных значений полей.
Существуют следующие типы конструкторов:
- конструктор без параметров;
- параметризованный конструктор;
- конструктор по умолчанию.
Конструктор по умолчанию создается в языке C#, если в классе не реализованы конструкторы.
Имя конструктора должно совпадать с именем класса и не может иметь тип возвращаемого значения.
Модифицируем класс Phone
, добавив в него конструктор без параметров и с параметрами:
Из примера видно, что класс Phone
содержит два конструктора, поэтому у объектов будет разная инициализация, apple
будет иметь значения из конструктора. Для инициализации объекта samsung
используется конструктор с параметрами, которые задаются при его вызове. Ниже приведён результат работы примера.
Конструкторы, как и другие методы можно перегружать, используя разные параметры.
Модификаторы доступа
Модификаторы доступа используется для установки уровня доступа/видимости для классов, полей, методов и свойств. К модификаторам доступа относятся такие ключевые слова, как public
, private
, protected
и internal
, ранее использовался нами только public
.
Модификатор | Описание |
public | Код доступен для всех классов |
private | Код доступен только в пределах одного класса |
Protected | Код доступен в пределах одного класса или в классе, который наследуется от этого класса |
Internal | Код доступен только в пределах собственной сборки, но не из другой сборки |
Если мы объявим поля нашего класса Phone
или методы как private
, то они станут недоступны для использования в других классах, например в Program
. Ограничение доступа и является основой принципа ООП – инкапсуляции.
Инкапсуляция и свойства
Инкапсуляция позволяет скрыть конфиденциальные данные от пользователей. Чтобы достичь этого, необходимо:
- объявить поля/переменные как
private
; - обеспечить
public get
иset
методы через свойства для доступа и изменения значенияprivate
поля.
Доступ к private
переменным возможен только внутри одного и того же класса. Иногда нужно получить к ним доступ вне класса – и это можно сделать с помощью свойств.
Свойство похоже на комбинацию переменной и метода и имеет два метода: get()
и set()
.
Свойства, Manufacturer
и Model
связаны с полями manufacturer
и model
соответственно.
Рекомендуется использовать одно и то же имя как для свойства, так и для частного поля, но с первой буквой в верхнем регистре.
Метод get()
возвращает значение приватной переменной. Метод set()
присваивает значение переменной. Ключевое слово value
представляет значение, которое мы присваиваем свойству.
Теперь можно получать и задавать значения свойств вне класса их содержащим. Результат выполнения примера, следующий:
Поля можно сделать доступными только для чтения, используя get()
или только для записи, используя set()
.
C# позволяет использовать автоматические свойства, где не нужно определять поле для свойства, а достаточно написать get;
и set;
внутри свойства. Изменив класс Phone
, следующим образом:
Получим тот же результат в консоли программы.
Инкапсуляция улучшает контроль над членами класса и снижает вероятность повреждения кода.
Наследование
В C# поля и методы могут наследоваться от одного класса к другому. В наследовании используются два понятия:
- Базовый класс – класс, от которого наследуется.
- Производный класс – класс, который наследуется от базового класса.
На примере ниже класс Smartphone
наследует поля и методы от класса Phone
:
Как видно из кода, класс Smartphone
наследует от класса Phone
поля и метод, что позволяет использовать их в программе, инициализировав только объект Smartphone()
. Результат работы примера приведён ниже:
Результат показывает, что класс Smartphone
успешно унаследовал от класса Phone
поля и метод.
Наследование позволяет существенно сократить объем кода, так как не требуется повторная реализация участков кода в других классах.
Полиморфизм и перегрузка методов
Принцип полиморфизма позволяет методам классов иметь не одну, а несколько форм, и он необходим, когда у нас есть много классов, связанных друг с другом путем наследования.
Как было сказано выше, наследование позволяет наследовать поля и методы от другого класса. Полиморфизм использует эти методы для выполнения различных задач. Это позволяет нам выполнять одно и то же действие разными способами.
Например, базовый класс Device
, у которого есть метод enabledScreen()
. Производными классами устройств могут быть смартфоны, планшеты и у них также есть собственная реализация вывода информации на экран:
В базовом классе объявлен метод screenEnabled()
, как virtual
. В классах наследниках, используя ключевое слово override
, перегружаем метод для каждого класса по-своему.
Все объекты успешно выполнили свои методы, благодаря их перегрузке.
Абстрактные классы и методы
Абстракция данных – это процесс сокрытия определенных деталей и показа пользователю только важной информации. Абстракция может быть достигнута либо с помощью абстрактных классов, либо с помощью интерфейсов.
Ключевое слово abstract
используется для классов и методов:
- абстрактный класс – это ограниченный класс, который нельзя использовать для создания объектов (для доступа к нему он должен быть унаследован от другого класса);
- абстрактный метод: может использоваться только в абстрактном классе и не имеет тела. Тело предоставляется производным классом.
Абстрактный класс может иметь как абстрактные, так и обычные методы. Создавать объекты абстрактных классов нельзя. Для создания объектов должен использоваться класс наследник.
Рассмотрим на примере:
Пример выше показывает, что класс Device
теперь абстрактный и его нельзя использовать для создания объектов напрямую. Но унаследованные от него классы Phone
и Tablet
без проблем используют его методы. Большей абстракции можно добиться, используя интерфейсы.
Интерфейсы
В C# можно добиться абстракции, используя интерфейсы. Interface
– это полностью абстрактный класс, который может содержать только абстрактные методы и свойства (без реализации). Правильнее писать имя интерфейса с буквы I
, так легче распознать, что это интерфейс, а не класс. По умолчанию члены интерфейса являются abstract
и public
.
Интерфейсы могут содержать свойства и методы, но не поля.
Чтобы получить доступ к методам интерфейса, интерфейс должен быть реализован в другом классе. Чтобы реализовать интерфейс, используйте :
символ (так же, как с наследованием). Тело метода интерфейса предоставляется классом «реализовать». Обратите внимание, что вам не нужно использовать ключевое слово override
при реализации интерфейса:
Использование интерфейса в примере показывает, что с их помощью можно добиться большего уровня абстракции.
В этом разделе статьи мы рассмотрели основные принципы ООП, необходимые для понимания работы готовых библиотечных классов и коллекций.
Коллекции
В первой части самоучителя были рассмотрены одномерные и многомерные массивы и их реализация в C#, но массивы используют фиксированную размерность и не позволяют динамически изменять размер.
Для работы с динамическими данными в C# используются коллекции. Они позволяют динамически изменять размерность данных и без каких либо проблем добавлять/изменять наборы данных.
Пространство имён System.Collections.Generic
включает в себя набор коллекций для широкого спектра задач. Рассмотрим некоторые из них: List<T>
и Dictoniary<T,V>
.
Список
List<T>
– список.
Список по структуре напоминает массив, который имеет возможность динамического расширения. При объявлении списка можно указывать встроенный или пользовательский тип данных.
Синтаксис объявления пустого списка, следующий:
List<Type> list = new List<Type>();
Если необходимо, то можно указать в скобках начальный размер списка:
List<Type> list = new List<Type> (25);
Можно сразу при объявлении проинициализировать его, добавив, например, строковые элементы:
List<string> names = new List<string> {“Michael”, “Stefan”, “Mary”};
У класса List
имеются свойства, необходимые для работы со списком:
Count
– получает количество элементов списка;Capacity
– возвращает или задаёт количество элементов, которое список может вместить без изменения размера;Item[int32]
– возвращает или задает элемент по указанному индексу.
Имеются и методы, организующие выполнение различных операций над списком, перечислим некоторые из них:
Add(T)
– добавляет элемент к списку;Clear()
– очистка списка;IndexOf(T)
– возвращает индекс переданного элемента;ElementAt(Int32)
– возвращает элемент по индексу;Insert(Int32, T)
– вставляет элемент в указанную позицию;Remove(T)
– удаляет указанный элемент из списка;RemoveAt(Int32)
– удаляет элемент из заданной позиции;Sort()
– сортирует список;Reverse()
– меняет порядок расположения элементов на противоположный.
Ниже на примере рассмотрим использование методов класса List
:
Рассмотрены некоторые методы List
, результат работы ниже:
Создали список с элементами, добавили новый элемент, удалили его по индексу, перевернули список в обратном порядке. С остальными методами предлагаю поэкспериментировать самостоятельно.
Словарь
Dictoniary<T, V>
– словарь.
Класс Dictionary реализует структуру данных Отображение, которую иногда называют Словарь или Ассоциативный массив. Идея довольно проста: в обычном массиве доступ к данным мы получаем через целочисленный индекс, а в словаре используется ключ, который может быть числом, строкой или любым другим типом данных, который реализует метод GetHashCode()
. При добавлении нового объекта в такую коллекцию для него указывается уникальный ключ, который используется для последующего доступа к нему.
Пустой словарь можно объявить так:
Словарь с набором элементов можно объявить так:
Рассмотрим некоторые свойства и методы класса Dictionary<TKey, TValue>. Более подробно ознакомиться со всеми возможностями класса можно на официальном сайте Microsoft.
Свойства класса Dictionary:
Count
– количество объектов в словаре;Keys
– ключи словаря;Values
– значения элементов словаря.
Методы класса Dictoniary:
Add(TKey, TValue)
– добавляет в словарь элемент с заданным ключом и значением;Clear()
– удаляет из словаря все ключи и значения;ContainsValue(TValue)
– проверяет наличие в словаре указанного значения;ContainsKey(TKey)
– проверяет наличие в словаре указанного ключа;GetEnumerator()
– возвращает перечислитель для перебора элементов словаря;Remove(TKey)
– удаляет элемент с указанным ключом;TryAdd(TKey, TValue)
– метод, реализующий попытку добавить в словарь элемент с заданным ключом и значением;TryGetValue(TKey, TValue)
– метод, реализующий попытку получить значение по заданному ключу.
На примере ниже рассмотрим работу со словарём:
В результате выполнения в консоль будет выведено следующее:
Продемонстрировано использование основных свойств и методов класса Dictoniary
.
На этом мы завершаем вторую часть статьи, важно понять принципы ООП и в дальнейшем их корректно применять на практике.