115.72K
Категория: ПрограммированиеПрограммирование

Generic (обобщенные типы, универсальные шаблоны)

1.

Generic
(обобщенные типы,
универсальные шаблоны)
Обобщенные типы,
Зачем нужны generic,
Обобщенные методы,
Ограничения обобщений

2.

2
Общая информация
Виды занятий и контроля по дисциплине
в 3 семестре:
Лекции – 1 пара в 2 недели
Практика – 1 пара в 2 недели
Лабораторные работы – 1 пара в неделю
(~10 лаб. работ)
Тесты на контрольных неделях – 2
Курсовая работа
Экзамен

3.

3
Общая информация
Рекомендуемая литература
Павловская Т.А. С#. Программирование на
языке высокого уровня, 2020
Троелсен Э. Язык программирования С# 7 и
платформы .NET и .NET Core, 2020
Полное руководство по языку
программирования С# 10 и платформе .NET 6,
https://metanit.com/sharp/tutorial
Руководство по WPF,
https://metanit.com/sharp/wpf/

4.

4
Generics
Generics – это классы, структуры,
интерфейсы и методы, в которых
некоторые типы сами являются
параметрами
Универсальные шаблоны позволяют точно
настроить метод, класс, структуру или
интерфейс в соответствии с типом
обрабатываемых данных
Преимущества - улучшенная возможность
многократного использования кода и
безопасность типов

5.

5
Зачем нужны generic? Пример
Класс для хранения данных пользователя:
class Person {
public int Id { get;}
public string Name { get;}
public Person(int id, string name) {
Id = id;
Name = name;
}
}
Id - уникальный идентификатор пользователя

6.

6
Определение Id
Сейчас Id – число (1, 2, 232 и пр.)
А может быть строковым (номер паспорта)
И у числовых, и у строковых значений есть
свои плюсы и минусы
На момент написания класса можем точно
не знать, что лучше выбрать (int, string)
А возможно, класс Person будет
использоваться другими разработчиками,
которые для представления
идентификатора создадут специальный
класс
Какой тип указать для Id?

7.

7
Какой тип указать для Id?
Тип object - Id может быть чем-угодно
class Person {
public object Id { get;}
public string Name { get;}
public Person(object id, string name)
{
Id = id;
Name = name;
}
}

8.

8
Пример использования
Person tom = new Person(546, "Tom");
Person bob = new Person("abc123", "Bob");
int tomId = (int)tom.Id;
string bobId = (string)bob.Id;
Console.WriteLine(tomId);
//546
Console.WriteLine(bobId);
//abc123
Недостатки:
упаковка (boxing) и распаковка (unboxing)
проблема безопасности типов

9.

9
Упаковка (boxing)
При передаче в конструктор значения типа
int, происходит упаковка этого значения в
тип Object:
Person tom = new Person(546, "Tom");
Упаковка (boxing) - преобразование
объекта значимого типа (тут - int) к типу
object. При упаковке общеязыковая среда
CLR обертывает значение в объект типа
System.Object и сохраняет его в
управляемой куче

10.

10
Распаковка (unboxing)
Чтобы обратно получить данные в
переменную типов int, необходимо
выполнить распаковку:
int tomId = (int)tom.Id;
Распаковка (unboxing) - преобразование
объекта типа object к значимому типу
Упаковка и распаковка ведут к снижению
производительности, т.к. системе надо
осуществить необходимые
преобразования

11.

11
Проблема безопасности типов
Пример использования класса Person:
Person tom = new Person(546, "Tom");
string tomId = (string)tom.Id; //!Ошибка
Console.WriteLine(tomId);
Мы можем не знать, какой именно объект
представляет Id, и при попытке его получить
возникнет исключение InvalidCastException
Причем, с исключением мы столкнемся на
этапе выполнения программы!!!

12.

12
Зачем нужны generic?
class Person {
public object Id { get;}
public string Name { get;}
public Person(object id, string name) {
Id = id;
Name = name;
}
}
Недостатки:
упаковка (boxing) и распаковка (unboxing)
проблема безопасности типов
Решение проблем – использовать generic-типы

13.

13
Обобщенный класс Person
Обобщенные типы позволяют указать
конкретный тип, который будет использоваться
class Person<T> {
public T Id { get; set; }
public string Name { get; set; }
public Person(T id, string name) {
Id = id;
Name = name;
}
}
Т – тип-параметр, не известный на момент
написания кода, может быть любым типом

14.

14
Пример использования
Person<int> tom = new Person(546, "Tom");
Person<string> bob = new Person("abc123","Bob");
int tomId = tom.Id;
string bobId = bob.Id;
Console.WriteLine(tomId);
// 546
Console.WriteLine(bobId);
// abc123
При определении переменной после
Person в угловых скобках надо указать
тот тип, который будет использоваться
вместо универсального параметра T

15.

15
Прежние недостатки
1.
Нет упаковки/распаковки
Person<int> tom = new Person(546, "Tom");
int tomId = tom.Id; //распаковка не нужна
2.
При попытке указать id не того типа –
получим ошибку компиляции
Person<int> tom = new Person("546", "Tom");
Обобщенный вариант класса - снижает
время на выполнение и количество
потенциальных ошибок!

16.

16
Пример
При этом универсальный параметр также
может представлять обобщенный тип
class Person<T> {
public T Id { get;}
public string Name { get;}
public Person(T id, string name)
{
Id//пример
= id; использования
Person<int>
Name
= name; tom = new Person<int>(546, "Tom");
}
Company<Person<int>> microsoft =
}
new Company<Person<int>>(tom);
class Company<P> { //класс
компании
public P CEO { get; set; } //президент компании
//546
publicConsole.WriteLine(microsoft.CEO.Id);
Company(P ceo) { CEO = ceo; }
Console.WriteLine(microsoft.CEO.Name); //Tom
}

17.

17
Примеры Generic
Типичный пример для generic – коллекции
элементов из System.Collections.Generic:
List<T>
SortedList<T>
Dictionary<TKey, TValue>
ICollection<T>
IComparer<T>
Активно используются в технологии LINQ

18.

18
Использование нескольких
универсальных параметров
class Person<T, K>
{
public T Id { get;}
public K Password { get; set; }
public string Name { get;}
public Person(T id, K password, string name)
{
Id = id;
Name
= name;
//пример
использования
Password
= password;
Person<int,
string>
tom =
} new Person<int, string>(546, "qwerty", "Tom");
} Console.WriteLine(tom.Id);
//546
Console.WriteLine(tom.Password); //qwerty

19.

19
Как работают generic?
При создании экземпляра универсального
класса указываем фактические типы для
замены параметров типа
Person<int> tom = new Person<int>(546, "Tom");
При этом компилятор создает новый класс, в
котором все параметры типа заменены на
указанный
class PersonInt {
public int Id { get;}
// ...
}

20.

20
Обобщенные методы
Обобщенный метод - это метод со своим списком
обобщенных параметров
Может использовать тип-параметр как тип
возвращаемого значения или как тип одного из
формальных параметров
Обобщенный метод можно объявить как в обычном
классе (структуре), так и в обобщенном
class MyClass {
T G<T>(T arg) {...} //обобщенный метод
}
class MyGenericClass<T> {
T M(T arg) {...}
//НЕобобщенный метод
}

21.

21
Обобщенные методы
//обмен значениями двух переменных
void Swap<T>(ref T x, ref T y)
{
T temp = x;
//пример использования
x = y;
int x = 7;
y = temp;
int y = 25;
}
Swap<int>(ref x, ref y); // или Swap(ref x, ref y);
Console.WriteLine($"x={x} y={y}"); // x=25 y=7
string s1 = "hello";
string s2 = "bye";
Swap<string>(ref s1,ref s2);//или Swap(ref s1,ref s2);
Console.WriteLine($"s1={s1} s2={s2}");
//s1=bye s2=hello

22.

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

23.

23
Ограничения (constraints)
Можно указать ограничения для типов, которые
могут быть значениями обобщенных типовпараметров
Ограничения задаются с помощью ключевого
слова where
Типы ограничений:
тип должен реализовывать определенные
интерфейсы
тип должен иметь определенный базовый класс
тип должен иметь конструктор без параметров
(new())
тип должен быть ссылочным типом (class) или
типом-значением (struct)

24.

24
Пример 2
Класс – некоторое сообщение
class Message {
public string Text {get;} //текст сообщения
public Message(string text)
{
Text = text;
}
}
И пусть, хотим определить метод для
отправки сообщения

25.

25
Пример 2
На первый взгляд - определим метод так:
void SendMessage(Message message)
{
Console.WriteLine($"Отправляется
сообщение: {message.Text}");
}
SendMessage(new Message("Hello World"));
Всё ли нормально?

26.

26
Проблемы с методом отправки
У класса Message могут быть классы-наследники,
например, EmailMessage, SmsMessage:
class EmailMessage : Message {
public EmailMessage(string text): base(text) { }
}
class SmsMessage : Message {
public SmsMessage(string text) : base(text) { }
}
И хотим также отправлять сообщения, которые
представляют эти классы:
SendMessage(new EmailMessage("Hello World"));

27.

27
Проблемы с методом отправки
SendMessage(new EmailMessage("Hello World"));
void SendMessage(Message message) {
Console.WriteLine($"Отправляется
сообщение: {message.Text}");
}
Проблемы вроде нет, т.к. параметр метода
SendMessage – Message, а значит он может
принимать и объекты производных классов

28.

28
Проблемы с методом отправки
SendMessage(new EmailMessage("Hello World"));
Но здесь будет преобразование типов: от
EmailMessage к Message. Кроме того,
возможна проблема типобезопасности,
если мы захотим внутри метода
преобразовать объект message в объект
производных классов
Избежать преобразований можно,
используя обобщения

29.

29
Обобщенный метод SendMessage
void SendMessage<T>(T message)
{
Console.WriteLine($"Отправляется сообщение:
{message.Text}");
//Ошибка! - свойство Text
}
Обобщения позволяют избежать преобразований
Но теперь - другая проблема: универсальный
параметр T подразумевает любой тип
Но не любой тип имеет свойство Text. И поэтому
нельзя явно использовать это свойство.
Для объекта T доступны только методы типа object

30.

30
Ограничение обобщения
Тут помогут ограничения для универсального
параметра:
void SendMessage<T>(T message) where T: Message
{
Console.WriteLine($"Отправляется сообщение:
{message.Text}");
}
Выражение «where T: Message» говорит, что через T
будут передаваться объекты класса Message и
производных классов
Компилятор будет знать, что T будет иметь
функционал класса Message, и соответственно
можно обращаться к методам и свойствам класса
Message внутри метода

31.

31
Применение метода SendMessage
//альтернатива – обобщенный класс
class Messenger<T> where T : Message {
void SendMessage(T message) {
Console.WriteLine($"Отправляется сообщение:{message.Text}");
}
}
//примеры использования
Messenger<Message> telegram = new Messenger<Message>();
telegram.SendMessage(new Message("Hello World"));
Messenger<EmailMessage> outlook = new Messenger<EmailMessage>();
outlook.SendMessage(new EmailMessage("Bye World"));
Messenger<SMSMessage> mobile = new Messenger<SMSMessage>();
mobile.SendMessage(new SMSMessage("SMS to World"));

32.

32
Примеры ограничений
Параметр Т - только структура или
другие типы-значений:
class Messenger<T> where T : struct
{ }
Т - только ссылочные типы:
class Messenger<T> where T : class
{ }

33.

33
Примеры ограничений
Параметр Т - класс или структура, которые
имеют общедоступный конструктор без
параметров:
class Messenger<T> where T : new()
{ }
Несколько ограничений:
class MyGenericClass<K, T>
where K : MyBaseClass, IComparable<K>
where T : struct, ICloneable
{ }

34.

34
Оператор default
Если в методе обобщенного типа надо вернуть
новый элемент универсального типа:
class Instantiator<T>
{
public T instance;
public Instantiator()
{
instance = new T(); //Ошибка!
}
}

35.

35
Оператор default
Тут надо использовать оператор default(T),
возвращающий значение по умолчанию для
типа (null - для ссылочных типов; 0 - для
численных типов-значений; для структур (struct)
всем полям присваиваются значения 0 или null)
class Instantiator<T> {
public T instance;
public Instantiator() {
instance = default(T);
}
}
English     Русский Правила