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

Применение нескольких параметров типа

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

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

Для начала рассмотрим простейший, хотя и не очень практичный пример:


string text = "Hello";
text >>= null;

static class Operators{

    extension<T>(T self) where T: class?{

        public void operator >>= (T _) => Console.WriteLine(self);
    }
}

Здесь мы определяем для произвольного ссылочного типа оператор >>= (оператор сдвига вправо с последующим присвоением).

Прежде всего определяем блок расширений:


extension<T>(T self) where T: class?{

Этот блок типизирован параметром T, вместо которого будет передаваться некоторый тип данных. И как в случае с обобщенными типами и методами мы можем установить ограничения для обобщеного блока расширений с помощью оператора where. В данном случае с помощью выражения where T: class? мы говорим, что тип, который будет передаваться вместо T, будет представлять класс или null

Также стоит обратить внимание, что это нестатический блок, и в данном случае мы определяем составной оператор присвоения - а начиная с C# 14 эти операторы можно определить с помощью нестатических методов. И в данном случае все, что делает наш оператор - это просто выводит значение объекта (левого операнда) на консоль:

public void operator >>= (T _) => Console.WriteLine(self);

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

И применение оператора:


string text = "Hello";
text >>= null;

Здесь к переменной text, которая представляет ссылочный тип string, применяем наш оператор >>=. Правый операнд здесь не важен, за тем исключением, что он должен представлять тот же тип, что и левый операнд (так как параметр оператора имеет тип T) или null. Для примера я выбрал в качестве правого операнда null, хотя можно было бы использовать и строку:


string text = "Hello";
text >>= "world";

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

Но теперь пойдем дальше и рассмотрим более практический пример:


string text = "Hello METANIT.COM";

text >>= Console.WriteLine;
text >>= FramePrint;
text >>= mes => Console.WriteLine($"<{mes}>");

void FramePrint(string message) => Console.WriteLine($"***** {message} *****");

static class Operators{

    extension<T>(T self) where T: class{

        public void operator >>= (Action<T> op) => op(self);
    }
}

Здесь изменено определение оператора - теперь в качестве параметра он принимает делегат Action<T> - то есть некоторое действие, которое имеет один параметр типа T - типа, к которому и применяется данный оператор. А в коде оператора просто вызываем данный делегат.

Подобному делегату, например, соответствует метод Console.WriteLine, поэтому мы можем написать

text >>= Console.WriteLine;

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


text >>= FramePrint;
text >>= mes => Console.WriteLine($"<{mes}>");

void FramePrint(string message) => Console.WriteLine($"***** {message} *****");

Консольный вывод подобной программы:


Hello METANIT.COM
***** Hello METANIT.COM *****
<Hello METANIT.COM>

И поскольку мы определили блок расширений как обобщенный, то тот же самый оператор мы можем применять к любому ссылочному типу. Например:


Person person = new("Bob", 44);
person >>= Console.WriteLine;     // Bob (44)

person >>= (p) => p.Age = 25;
person >>= Console.WriteLine;     // Bob (25)

person >>= SetDefault;
person >>= Console.WriteLine;     // Tom (41)

void SetDefault(Person p)
{
    p.Age = 41;
    p.Name = "Tom";
}

class Person{

    public string Name {get;set;}
    public int Age {get;set;}

    public Person(string name, int age)
    {

        Name = name;
        Age = age;
    }

    public override string ToString() => $"{Name} ({Age})";
}

static class Operators{

    extension<T>(T self) where T: class{

        public void operator >>= (Action<T> op) => op(self);
    }
}

В данном случае применяем оператор к кастомному типу Person. Причем поскольку это ссылочный тип, то мы можем в делегате-правом операнде изменять его свойства и доступные поля. Консольный вывод программы:

Значимые типы

В примере выше в качестве параметра типа в блоке расширения можно было использовать ссылочные типов (where T: class). Если же нам потребуется определить подобный оператор, но для значимых типов, то кроме установки соответствующего ограничения (where T: struct) необходимо использовать модификатор ref для параметра расширения:


int n = 123;

n >>= Console.WriteLine;

static class Operators{

    extension<T>(ref T self) where T: struct{

        public void operator >>= (Action<T> op) => op(self);
    }
}

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


// проверяем строку и устанавливаем значение по умолчанию, если ввод отсутствует
var checkNum = (string? val) => val?.Length > 0 ? val : "11";

Console.Write("Введите число: ");

int num =  
    Console.ReadLine()  // считываем число в виде строки
    | checkNum          // проверяем введенную строку   (string -> string)
    | Int32.Parse       // парсим строку в число        (string -> int)
    | Square            // возводим в квадрат           (int -> int)
    ;

num >>= Console.WriteLine;

int Square(int n) => n * n;

static class Operators{

    extension<T1, T2>(T1) {

        public static T2 operator | (T1 self, Func<T1, T2> op) => op(self);
    }
    extension<T>(ref T self) where T: struct{

        public void operator >>= (Action<T> op) => op(self);
    }
}

Здесь блок расширения применяет два параметра типа - T1 и T2:


extension<T1, T2>(T1) {

    public static T2 operator | (T1 self, Func<T1, T2> op) => op(self);
}

Причем сам блок расширения формально определен для типа T1. Внутри расширения определяем оператор |, первый параметр которого представляет тип T1 - тип левого операнда. А вот правый операнд представляет делегат Func<T1, T2> - некоторое действие, которое принимает значение типа T1 и возвращает значение типа T2. Результат делегата и будет результатом всего оператора. Причем важно отметить, что T1 и T2 могут представлять как разные типы, так и один и тот же тип.

Посмотрим на применение этого оператора:


int num =  
    Console.ReadLine()  // считываем число в виде строки
    | checkNum          // проверяем введенную строку   (string -> string)
    | Int32.Parse       // парсим строку в число        (string -> int)
    | Square            // возводим в квадрат           (int -> int)
    ;

Здесь мы имеем следующий порядок выполнения:

  1. Console.ReadLine()

    Считываем число в виде строки

  2. | checkNum

    В качестве левого операнда оператору | передается ранее введенная строка. Правый операнд - лямбда-выражение checkNum, которое проверяет строку, и, если эта строка равна null или не содержит символов, возвращает значение по умолчанию - "11"

  3. | Int32.Parse

    В качестве левого операнда оператору | передается строка - результат checkNum. Правый операнд - метод Int32.Parse(), который принимает строку и преобразует ее в целое число. Поэтому здесь оператор возвращает значение типа int

    В данном случае не проверяется, если пользователь ввел не конвертируемую в число строку, но, я думаю, не составит труда добавить подобную логику по аналогии.

  4. | Square

    В качестве левого операнда оператору | передается полученное от Int32.Parse целое число. Правый операнд - метод Square, который возводит переданное число в квадрат.

Примеры работы программы:


eugene@Eugene:/dotnet/c#/testapp$ dotnet run
Введите число: 15
225
eugene@Eugene:/dotnet/c#/testapp$ dotnet run
Введите число: -3
9
eugene@Eugene:/dotnet/c#/testapp$ dotnet run
Введите число: 
121
eugene@Eugene:/dotnet/c#/testapp$ dotnet run

Проверь себя

📋 Тест по теме «Применение нескольких параметров типа» Тест по теме

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

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

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

string _____ = "Hello";
text >>= null;

static class Operators{

    extension<T>(T self) where T: class?{

        public void operator >>= (T _) => Console.WriteLine(self);
    }
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

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

extension<T>(T _____) where T: class?{
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

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

public void operator >>= (T _) => Console._____(self);
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

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

string _____ = "Hello";
text >>= null;
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

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

string _____ = "Hello";
text >>= "world";
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

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

string text = "Hello";
text >>= null;

static class Operators{

    extension<T>(T _____) _____ T: class?{

        public void operator >>= (T _) => Console.WriteLine(self);
    }
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

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

_____<T>(T _____) where T: class?{
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

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

}
static class Operators{
text >>= null;
    }
string text = "Hello";
    extension<T>(T self) where T: class?{
        public void operator >>= (T _) => Console.WriteLine(self);
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «Применение нескольких параметров типа»
+30 XP

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

string text = "Hello";
text >>= null;

static class Operators{

    extension<T>(T self) where T: class?{

        public void operator >>= (T _) => Console.WriteLine(self);
    }
}
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «Применение нескольких параметров типа»
+30 XP

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

extension<T>(T self) where T: class?{
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «Применение нескольких параметров типа»
+50 XP

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

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

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

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

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

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