Главная страница
Навигация по странице:

  • Add -> New Project...

  • Dependencies

  • Доступ к членам базового класса из класса-наследника

  • Конструкторы в производных классах Конструкторы не передаются производному классу при наследовании. И если в базовом классе не определен

  • Порядок вызова конструкторов

  • Определение интерфейса Для определения интерфейса используется ключевое слово interface

  • Методические рекомендации по выполнению практических работ по междисциплинарному курсу


    Скачать 2.6 Mb.
    НазваниеМетодические рекомендации по выполнению практических работ по междисциплинарному курсу
    Дата05.09.2022
    Размер2.6 Mb.
    Формат файлаpdf
    Имя файлаMU_PR_MDK_01_01.pdf
    ТипМетодические рекомендации
    #663423
    страница3 из 13
    1   2   3   4   5   6   7   8   9   ...   13
    Инициализаторы объектов
    Для инициализации объектов классов можно применять инициализаторы.
    Инициализаторы представляют передачу в фигурных скобках значений доступным полям и свойствам объекта:
    1 2
    Person tom = new Person { name = "Tom", age=31 }; tom.GetInfo(); // Имя: Tom Возраст: 31
    С помощью инициализатора объектов можно присваивать значения всем доступным полям и свойствам объекта в момент создания без явного вызова конструктора.
    При использовании инициализаторов следует учитывать следующие моменты:

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

    Инициализатор выполняется после конструктора, поэтому если и в конструкторе, и в инициализаторе устанавливаются значения одних и тех же полей и свойств, то значения, устанавливаемые в конструкторе, заменяются значениями из инициализатора.
    Практическая работа №6 Перегрузка методов
    Иногда возникает необходимость создать один и тот же метод, но с разным набором параметров. И в зависимости от имеющихся параметров применять определенную версию метода. Такая возможность еще называется перегрузкой методов (method overloading).
    И в языке C# мы можем создавать в классе несколько методов с одним и тем же именем, но разной сигнатурой. Что такое сигнатура? Сигнатура складывается из следующих аспектов:

    Имя метода

    Количество параметров

    Типы параметров

    Порядок параметров

    Модификаторы параметров
    Но названия параметров в сигнатуру НЕ входят. Например, возьмем следующий метод:
    1 2
    3 4 public int Sum(int x, int y)
    { return x + y;
    }
    У данного метода сигнатура будет выглядеть так: Sum(int, int)
    И перегрузка метода как раз заключается в том, что методы имеют разную сигнатуру, в которой совпадает только название метода. То есть методы должны отличаться по:

    Количеству параметров

    Типу параметров

    Порядку параметров

    Модификаторам параметров
    Например, пусть у нас есть следующий класс:
    1 2 class Calculator
    {

    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public void Add(int a, int b)
    { int result = a + b;
    Console.WriteLine($"Result is {result}");
    } public void Add(int a, int b, int c)
    { int result = a + b + c;
    Console.WriteLine($"Result is {result}");
    } public int Add(int a, int b, int c, int d)
    { int result = a + b + c + d;
    Console.WriteLine($"Result is {result}"); return result;
    } public void Add(double a, double b)
    { double result = a + b;
    Console.WriteLine($"Result is {result}");
    }
    }
    Здесь представлены четыре разных версии метода Add, то есть определены четыре перегрузки данного метода.
    Первые три версии метода отличаются по количеству параметров. Четвертая версия совпадает с первой по количеству параметров, но отличается по их типу. При этом достаточно, чтобы хотя бы один параметр отличался по типу. Поэтому это тоже допустимая перегрузка метода Add.
    То есть мы можем представить сигнатуры данных методов следующим образом:
    1 2
    3 4
    Add(int, int)
    Add(int, int, int)
    Add(int, int, int, int)
    Add(double, double)
    После определения перегруженных версий мы можем использовать их в программе:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 class Program
    { static void Main(string[] args)
    {
    Calculator calc = new Calculator(); calc.Add(1, 2); // 3 calc.Add(1, 2, 3); // 6 calc.Add(1, 2, 3, 4); // 10 calc.Add(1.4, 2.5); // 3.9
    Console.ReadKey();
    }
    }
    Консольный вывод:
    Result is 3
    Result is 6
    Result is 10
    Result is 3.9

    Также перегружаемые методы могут отличаться по используемым модификаторам.
    Например:
    1 2
    3 4
    5 6
    7 8
    9 10 11 void Increment(ref int val)
    { val++;
    Console.WriteLine(val);
    } void Increment(int val)
    { val++;
    Console.WriteLine(val);
    }
    В данном случае обе версии метода Increment имеют одинаковый набор параметров одинакового типа, однако в первом случае параметр имеет модификатор ref. Поэтому обе версии метода будут корректными перегрузками метода Increment.
    А отличие методов по возвращаемому типу или по имени параметров не является основанием для перегрузки. Например, возьмем следующий набор методов:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 int Sum(int x, int y)
    { return x + y;
    } int Sum(int number1, int number2)
    { return x + y;
    } void Sum(int x, int y)
    {
    Console.WriteLine(x + y);
    }
    Сигнатура у всех этих методов будет совпадать:
    1
    Sum(int, int)
    Поэтому данный набор методов не представляет корректные перегрузки метода Sum и работать не будет.
    Практическая работа №7 Определение операций в классе
    Нередко различные классы и структуры оформляются в виде отдельных библиотек, которые компилируются в файлы dll и затем могут подключать в другие проекты.
    Благодаря этому мы можем определить один и тот же функционал в виде библиотеки классов и подключать в различные проекты или передавать на использование другим разработчикам.
    Создадим и подключим библиотеку классов.
    Возьмем имеющийся проект консольного приложения .NET Core, например, созданный в прошлых темах. В структуре проекта нажмем правой кнопкой на название решения и далее в появившемся контекстном меню выберем Add -> New Project... (Добавить новый проект):
    Далее в списке шаблонов проекта найдем пункт Class Library (.NET Core):
    Затем дадим новому проекту какое-нибудь название, например, MyLib:

    После этого в решение будет добавлен новый проект, в моем случае с названием MyLib:
    По умолчанию новый проект имеет один пустой класс Class1 в файле Class1.cs. Мы можем этот файл удалить или переименовать, как нам больше нравится.
    Например, переименуем файл Class1.cs в Person.cs, а класс Class1 в Person. Определим в классе Person простейший код:
    1 2
    3 4
    5 public class Person
    { public string name; public int age;
    }
    Теперь скомпилируем библиотеку классов. Для этого нажмем правой кнопкой на проект библиотеки классов и в контекстном меню выберем пункт Rebuild:
    После компиляции библиотеки классов в папке проекта в каталоге bin/Debug/netcoreapp3.0 мы сможем найти скомпилированный файл dll
    (MyLib.dll). Подключим его в основной проект. Для этого в основном проекте нажмем правой кнопкой на узел Dependencies и в контекстном меню выберем пункт Add
    Reference:
    Далее нам откроется окно для добавления библиотек. В этом окне выберем пункт
    Solution,который позволяет увидеть все библиотеки классов из проектов текущего решения, поставим отметку рядом с нашей библиотекой и нажмем на кнопку OK:
    Если наша библиотека вдруг представляет файл dll, который не связан ни с каким проектом в нашем решении, то с помощью кнопки Browse мы можем найти местоположение файла dll и также его подключить.
    После успешного подключения библиотеки в главном проекте изменим класс Program, чтобы он использовал класс Person из библиотеки классов:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 using System; using MyLib; // подключение пространства имен из библиотеки классов namespace HelloApp
    { class Program
    { static void Main(string[] args)
    {
    Person tom = new Person { name = "Tom", age = 35 };
    Console.WriteLine(tom.name);
    }
    }
    }
    Практическая работа №8 Создание наследованных классов

    Наследование (inheritance) является одним из ключевых моментов ООП. Благодаря наследованию один класс может унаследовать функциональность другого класса.
    Пусть у нас есть следующий класс Person, который описывает отдельного человека:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 class Person
    { private string _name; public string Name
    { get { return _name; } set { _name = value; }
    } public void Display()
    {
    Console.WriteLine(Name);
    }
    }
    Но вдруг нам потребовался класс, описывающий сотрудника предприятия - класс
    Employee. Поскольку этот класс будет реализовывать тот же функционал, что и класс
    Person, так как сотрудник - это также и человек, то было бы рационально сделать класс
    Employee производным (или наследником, или подклассом) от класса Person, который, в свою очередь, называется базовым классом или родителем (или суперклассом):
    1 2
    3 4 class Employee : Person
    {
    }
    После двоеточия мы указываем базовый класс для данного класса. Для класса Employee базовым является Person, и поэтому класс Employee наследует все те же свойства, методы, поля, которые есть в классе Person. Единственное, что не передается при наследовании, это конструкторы базового класса.
    Таким образом, наследование реализует отношение is-a (является), объект класса
    Employee также является объектом класса Person:
    1 2
    3 4
    5 6
    7 8 static void Main(string[] args)
    {
    Person p = new Person { Name = "Tom"}; p.Display(); p = new Employee { Name = "Sam" }; p.Display();
    Console.Read();
    }
    И поскольку объект Employee является также и объектом Person, то мы можем так определить переменную: Person p = new Employee().
    По умолчанию все классы наследуются от базового класса Object, даже если мы явным образом не устанавливаем наследование. Поэтому выше определенные классы Person и
    Employee кроме своих собственных методов, также будут иметь и методы класса Object:
    ToString(), Equals(), GetHashCode() и GetType().
    Все классы по умолчанию могут наследоваться. Однако здесь есть ряд ограничений:

    Не поддерживается множественное наследование, класс может наследоваться только от одного класса.

    При создании производного класса надо учитывать тип доступа к базовому классу - тип доступа к производному классу должен быть таким же, как и у базового класса, или
    более строгим. То есть, если базовый класс у нас имеет тип доступа internal, то производный класс может иметь тип доступа internal или private, но не public.
    Однако следует также учитывать, что если базовый и производный класс находятся в разных сборках (проектах), то в этом случае производый класс может наследовать только от класса, который имеет модификатор public.

    Если класс объявлен с модификатором sealed, то от этого класса нельзя наследовать и создавать производные классы. Например, следующий класс не допускает создание наследников:
    1 2
    3 sealed class Admin
    {
    }

    Нельзя унаследовать класс от статического класса.
    Доступ к членам базового класса из класса-наследника
    Вернемся к нашим классам Person и Employee. Хотя Employee наследует весь функционал от класса Person, посмотрим, что будет в следующем случае:
    1 2
    3 4
    5 6
    7 class Employee : Person
    { public void Display()
    {
    Console.WriteLine(_name);
    }
    }
    Этот код не сработает и выдаст ошибку, так как переменная _name объявлена с модификатором private и поэтому к ней доступ имеет только класс Person. Но зато в классе Person определено общедоступное свойство Name, которое мы можем использовать, поэтому следующий код у нас будет работать нормально:
    1 2
    3 4
    5 6
    7 class Employee : Person
    { public void Display()
    {
    Console.WriteLine(Name);
    }
    }
    Таким образом, производный класс может иметь доступ только к тем членам базового класса, которые определены с модификаторами private protected (если базовый и производный класс находятся в одной сборке), public, internal (если базовый и производный класс находятся в одной сборке), protected и protected internal.
    Ключевое слово base
    Теперь добавим в наши классы конструкторы:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 class Person
    { public string Name { get; set; } public Person(string name)
    {
    Name = name;
    } public void Display()
    {
    Console.WriteLine(Name);
    }

    14 15 16 17 18 19 20 21 22 23 24 25
    } class Employee : Person
    { public string Company { get; set; } public Employee(string name, string company)
    : base(name)
    {
    Company = company;
    }
    }
    Класс Person имеет конструктор, который устанавливает свойство Name. Поскольку класс
    Employee наследует и устанавливает то же свойство Name, то логично было бы не писать по сто раз код установки, а как-то вызвать соответствующий код класса Person. К тому же свойств, которые надо установить в конструкторе базового класса, и параметров может быть гораздо больше.
    С помощью ключевого слова base мы можем обратиться к базовому классу. В нашем случае в конструкторе класса Employee нам надо установить имя и компанию. Но имя мы передаем на установку в конструктор базового класса, то есть в конструктор класса
    Person, с помощью выражения base(name).
    1 2
    3 4
    5 6
    7 8 static void Main(string[] args)
    {
    Person p = new Person("Bill"); p.Display();
    Employee emp = new Employee ("Tom", "Microsoft"); emp.Display();
    Console.Read();
    }
    Конструкторы в производных классах
    Конструкторы не передаются производному классу при наследовании. И если в базовом классе не определен конструктор по умолчанию без параметров, а только конструкторы с параметрами (как в случае с базовым классом Person), то в производном классе мы обязательно должны вызвать один из этих конструкторов через ключевое слово base.
    Например, из класса Employee уберем определение конструктора:
    1 2
    3 4 class Employee : Person
    { public string Company { get; set; }
    }
    В данном случае мы получим ошибку, так как класс Employee не соответствует классу
    Person, а именно не вызывает конструктор базового класса. Даже если бы мы добавили какой-нибудь конструктор, который бы устанавливал все те же свойства, то мы все равно бы получили ошибку:
    1 2
    3 4
    5 public Employee(string name, string company)
    {
    Name = name;
    Company = company;
    }
    То есть в классе Employee через ключевое слово base надо явным образом вызвать конструктор класса Person:
    1 2 public Employee(string name, string company)
    : base(name)

    3 4
    5
    {
    Company = company;
    }
    Либо в качестве альтернативы мы могли бы определить в базовом классе конструктор без параметров:
    1 2
    3 4
    5 6
    7 8
    9 10 class Person
    {
    // остальной код класса
    // конструктор по умолчанию public Person()
    {
    FirstName = "Tom";
    Console.WriteLine("Вызов конструктора без параметров");
    }
    }
    Тогда в любом конструкторе производного класса, где нет обращения конструктору базового класса, все равно неявно вызывался бы этот конструктор по умолчанию.
    Например, следующий конструктор
    1 2
    3 4 public Employee(string company)
    {
    Company = company;
    }
    Фактически был бы эквивалентен следующему конструктору:
    1 2
    3 4
    5 public Employee(string company)
    :base()
    {
    Company = company;
    }
    Порядок вызова конструкторов
    При вызове конструктора класса сначала отрабатывают конструкторы базовых классов и только затем конструкторы производных. Например, возьмем следующие классы:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 16 17 18 19 20 21 class Person
    { string name; int age; public Person(string name)
    { this.name = name;
    Console.WriteLine("Person(string name)");
    } public Person(string name, int age) : this(name)
    { this.age = age;
    Console.WriteLine("Person(string name, int age)");
    }
    } class Employee : Person
    { string company; public Employee(string name, int age, string company) : base(name, age)

    22 23 24 25 26
    { this.company = company;
    Console.WriteLine("Employee(string name, int age, string company)");
    }
    }
    При создании объекта Employee:
    1
    Employee tom = new Employee("Tom", 22, "Microsoft");
    Мы получим следующий консольный вывод:
    Person(string name)
    Person(string name, int age)
    Employee(string name, int age, string company)
    В итоге мы получаем следующую цепь выполнений.
    1.
    Вначале вызывается конструктор Employee(string name, int age, string company). Он делегирует выполнение конструктору Person(string name, int age)
    2.
    Вызывается конструктор Person(string name, int age), который сам пока не выполняется и передает выполнение конструктору Person(string name)
    3.
    Вызывается конструктор Person(string name), который передает выполнение конструктору класса System.Object, так как это базовый по умолчанию класс для Person.
    4.
    Выполняется конструктор System.Object.Object(), затем выполнение возвращается конструктору Person(string name)
    5.
    Выполняется тело конструктора Person(string name), затем выполнение возвращается конструктору Person(string name, int age)
    6.
    Выполняется тело конструктора Person(string name, int age), затем выполнение возвращается конструктору Employee(string name, int age, string company)
    7.
    Выполняется тело конструктора Employee(string name, int age, string company). В итоге создается объект Employee

    Практическая работа №9 Работа с объектами через интерфейсы
    Интерфейс представляет ссылочный тип, который может определять некоторый функционал - набор методов и свойств без реализации. Затем этот функционал реализуют классы и структуры, которые применяют данные интерфейсы.
    Определение интерфейса
    Для определения интерфейса используется ключевое слово interface. Как правило, названия интерфейсов в C# начинаются с заглавной буквы I, например, IComparable,
    IEnumerable (так называемая венгерская нотация), однако это не обязательное требование, а больше стиль программирования.
    Что может определять интерфейс? В целом интерфейсы могут определять следующие сущности:

    Методы

    Свойства

    Индексаторы

    События

    Статические поля и константы (начиная с версии C# 8.0)
    Однако интерфейсы не могут определять нестатические переменные. Например, простейший интерфейс, который определяет все эти компоненты:
    1 2
    3 4
    5 6
    7 8
    9 10 11 12 13 14 15 interface IMovable
    {
    // константа const int minSpeed = 0; // минимальная скорость
    // статическая переменная static int maxSpeed = 60; // максимальная скорость
    // метод void Move(); // движение
    // свойство string Name { get; set; } // название delegate void MoveHandler(string message); // определение делегата для события
    // событие event MoveHandler MoveEvent; // событие движения
    }
    В данном случае определен интерфейс IMovable, который представляет некоторый движущийся объект. Данный интерфейс содержит различные компоненты, которые описывают возможности движущегося объекта. То есть интерфейс описывает некоторый функционал, который должен быть у движущегося объекта.
    Методы и свойства интерфейса могут не иметь реализации, в этом они сближаются с абстрактными методами и свойствами абстрактных классов. В данном случае интерфейс определяет метод Move, который будет представлять некоторое передвижение. Он не имеет реализации, не принимает никаких параметров и ничего не возвращает.
    То же самое в данном случае касается свойства Name. На первый взгляд оно похоже на автоматическое свойство. Но в реальности это определение свойства в интерфейсе, которое не имеет реализации, а не автосвойство.
    Еще один момент в объявлении интерфейса: если его члены - методы и свойства не имеют модификаторов доступа, но фактически по умолчанию доступ
    1   2   3   4   5   6   7   8   9   ...   13


    написать администратору сайта