Полное руководство по языку программирования С# 14 и платформе .NET 10 / Объектно-ориентированное программирование

Ограничения обобщений

Начальный ⏱ 5 мин чтения 👁 2 🕓 обновлено 11.08.2026

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


class Message
{
    public string Text { get; } // текст сообщения
    public Message(string text)
    {
        Text = text;
    }
}

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


SendMessage(new Message("Hello World"));

void SendMessage(Message message)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");
}

Метод SendMessage в качестве параметра message принимает объект Message и эмулирует его отправку. Вроде все нормально и что-то лучше вряд ли можно придумать. Но у класса Message могут быть классы-наследники. Например, класс EmailMessage для email-сообщений, SmsMessage - для sms-сообщений и так далее


class EmailMessage : Message
{
    public EmailMessage(string text) : base(text) { }
}
class SmsMessage : Message
{
    public SmsMessage(string text) : base(text) { }
}

Что если мы хотим также отправлять сообщения, которые представляют эти классы? Проблемы вроде нет, поскольку метод SendMessage принимает объект Message и соответственно также и объекты производных классов:


SendMessage(new EmailMessage("Hello World"));

void SendMessage(Message message)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");
}

Но здесь мы сталкиваемся с преобразованием типов: от EmailMessage к Message. Кроме того, опять же возможна проблема типобезопасности, если мы захотим преобразовать объект message в объект производных классов. И в этом случае чтобы избежать преобразований, мы можем применить обобщения:


void SendMessage<T>(T message)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");	// ! Ошибка - свойство Text
}

Обобщения позволяют избежать преобразований, но теперь мы сталкиваемся с другой проблемой - универсальный параметр T подразумевает любой тип. Но не любой тип имеет свойство Text. Соответственно свойство Text для объекта типа T не определено и мы не можем это свойство использоваться. Более того для объекта T по умолчанию нам достуны только методы типа object.

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

Ограничения методов

Ограничения методов указываются после списка параметров после оператора where:

имя_метода<T>(параметры) where T: тип_ограничения

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

Например, применим ограничения к методу SendMessage, который отправляет объекты Message


SendMessage(new Message("Hello World"));
SendMessage(new EmailMessage("Bye World"));

void SendMessage<T>(T message) where T: Message
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");
}
class Message
{
    public string Text { get; } // текст сообщения
    public Message(string text)
    {
        Text = text;
    }
}
class EmailMessage : Message
{
    public EmailMessage(string text) : base(text) { }
}

Выражение where T: Message в определении метода SendMessage говорит, что через универсальный параметр T будут передаваться объекты класса Message и производных классов. Благодаря этому компилятор будет знать, что T будет иметь функционал класса Message, и соответственно мы сможем обратиться к методам и свойствам класса Message внутри метода без проблем.

При вызове метода нам необязательно указывать тип в угловых скобках - компилятор на основании переданного значения сам определит каким типом типизиуется метод:

SendMessage(new EmailMessage("Bye World"));

Однако это можно сделать и явно

SendMessage<EmailMessage>(new EmailMessage("Bye World"));

Ограничения обобщений в типах

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

class имя_класса<T> where T: тип_ограничения

В качестве примера определим класс мессенджера, который будет отправлять сообшения в виде объектов Message:


class Messenger<T> where T : Message
{
    public void SendMessage(T message)
    {
        Console.WriteLine($"Отправляется сообщение: {message.Text}");
    }
}

class Message
{
    public string Text { get; } // текст сообщения
    public Message(string text)
    {
        Text = text;
    }
}
class EmailMessage : Message
{
    public EmailMessage(string text) : base(text) { }
}

Здесь для класса Messenger опять же установлено ограничение where T : Message. То есть внутри класса Messenger все объекты типа T можно использовать как объекты Message. И в данном случае в классе Messenger в методе SendMessage опять эмулируется отправка сообщений.

Применим класс для отправки сообщений:


Messenger<Message> telegram = new Messenger<Message>(); 
telegram.SendMessage(new Message("Hello World"));

Messenger<EmailMessage> outlook = new Messenger<EmailMessage>();
outlook.SendMessage(new EmailMessage("Bye World"));

Типы ограничений и стандартные ограничения

В качестве ограничений мы можем использовать следующие типы:

  • Классы

  • Интерфейсы

  • class - универсальный параметр должен представлять класс

  • struct - универсальный параметр должен представлять структуру

  • new() - универсальный параметр должен представлять тип, который имеет общедоступный (public) конструктор без параметров

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


class Messenger<T> where T : struct
{}

При этом использовать в качестве ограничения конкретные структуры в отличие от классов нельзя.

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


class Messenger<T> where T : class
{}

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


class Messenger<T> where T : new()
{}

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

  1. Название класса, class, struct. Причем мы можем одновременно определить только одно из этих ограничений

  2. Название интерфейса

  3. new()


class Smartphone<T> where T: Messenger, new()
{
 
}

Использование нескольких универсальных параметров

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


class Messenger<T, P> 
    where T : Message
    where P: Person
{
    public void SendMessage(P sender, P receiver, T message)
    {
        Console.WriteLine($"Отправитель: {sender.Name}");
        Console.WriteLine($"Получатель: {receiver.Name}");
        Console.WriteLine($"Сообщение: {message.Text}");
    }
}
class Person
{
    public string Name { get;}
    public Person(string name) => Name = name;
}

class Message
{
    public string Text { get; } // текст сообщения
    public Message(string text) =>  Text = text;
}

В данном случае для параметра P будут передаваться объекты типа Person, а для параметра T - объекты Message.

Применим классы:


Messenger<Message, Person> telegram = new Messenger<Message, Person>();
Person tom = new Person("Tom");
Person bob = new Person("Bob");
Message hello = new Message("Hello, Bob!");
telegram.SendMessage(tom, bob, hello);

Консольный вывод:

Отправитель: Tom
Получатель: Bob
Сообщение: Hello, Bob!

Проверь себя

📋 Тест по теме «Ограничения обобщений» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

class _____
{
    public string Text { get; } // текст сообщения
    public Message(string text)
    {
        Text = text;
    }
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

SendMessage(_____ Message("Hello World"));

void SendMessage(Message message)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

class _____ : Message
{
    public EmailMessage(string text) : base(text) { }
}
class SmsMessage : Message
{
    public SmsMessage(string text) : base(text) { }
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

SendMessage(new EmailMessage("_____ World"));

void SendMessage(Message message)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

void SendMessage<T>(T _____)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");	// ! Ошибка - свойство Text
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

class _____
{
    public string Text { get; } // текст сообщения
    public Message(string text)
    {
        Text = text;
    }
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

SendMessage(new Message("_____ World"));

void SendMessage(Message _____)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

Строки кода из примера этого урока перемешаны. Впишите их в правильном порядке, каждую строку — с новой строки.

class Message
    public string Text { get; } // текст сообщения
    public Message(string text)
    }
    {
}
        Text = text;
{
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «Ограничения обобщений»
+30 XP

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

class Message
{
    public string Text { get; } // текст сообщения
    public Message(string text)
    {
        Text = text;
    }
}
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «Ограничения обобщений»
+30 XP

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

SendMessage(new Message("Hello World"));

void SendMessage(Message message)
{
    Console.WriteLine($"Отправляется сообщение: {message.Text}");
}
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «Ограничения обобщений»
+50 XP

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

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «Ограничения обобщений»
+25 XP

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

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.