Скрытые возможности C#, о которых молчат в учебниках
Расширяем горизонты: от yield до контекстных ключевых слов
Отличный вопрос! C# действительно полон «пасхальных яиц», которые превращают рутинный код в элегантные решения. Давайте разберём несколько самых мощных фишек, которые стоит знать каждому разработчику.
1. yield return — ленивая магия
Вы правы, можно вернуть List<T>. Но yield return создаёт ленивую последовательность (IEnumerable), которая вычисляется «на лету». Это экономит память и время, когда данных много или они генерируются сложно.
// Пример: бесконечная последовательность чисел Фибоначчи
public static IEnumerable<int> Fibonacci(int count)
{
int prev = 0, current = 1;
for (int i = 0; i < count; i++)
{
yield return prev; // возвращаем и "засыпаем"
(prev, current) = (current, prev + current);
}
}
// Использование: вычисляем только первые 10 чисел
foreach (var num in Fibonacci(10))
Console.WriteLine(num); // 0,1,1,2,3,5,8,13,21,34
Без yield пришлось бы хранить весь список в памяти. А здесь — каждый элемент вычисляется при итерации. Плюс поддерживаются такие LINQ-операции как .Where(), .Select() — они тоже ленивые!
2. Extension Methods — методы-призраки
Выглядит как магия, но это просто статические методы в статическом классе, которые «прикрепляются» к типу через ключевое слово this в первом параметре.
// Создаём метод для строк
public static class StringExtensions
{
public static bool IsPalindrome(this string str)
{
if (string.IsNullOrEmpty(str)) return false;
var reversed = new string(str.Reverse().ToArray());
return str.Equals(reversed, StringComparison.OrdinalIgnoreCase);
}
}
// Теперь можно писать:
string word = "А роза упала на лапу Азора";
Console.WriteLine(word.IsPalindrome()); // True (если убрать пробелы)
Важно: extension-методы не могут переопределять существующие методы, они лишь «тень». Но это идеальный способ добавить функциональность к DateTime, string или даже к интерфейсам (например, IEnumerable<T>).
3. Выполнение кода до Main() — не миф, а статические конструкторы
Да, такое возможно! Статический конструктор класса выполняется автоматически при первом обращении к классу. Если поместить его в класс с точкой входа, он сработает до Main().
class Program
{
static Program()
{
Console.WriteLine("Я выполняюсь ДО Main()!");
Environment.Exit(0); // Можно даже завершить программу
}
static void Main(string[] args)
{
Console.WriteLine("Это не выполнится");
}
}
// Вывод: "Я выполняюсь ДО Main()!"
Более практичный пример — инициализация глобальных настроек или логирование старта. Но будьте осторожны: статические конструкторы могут замедлить первый запуск.
4. Контекстные ключевые слова (C# 6+)
Некоторые слова становятся ключевыми только в определённом контексте. Например:
- async/await — для асинхронного кода (но это уже база).
- var — неявная типизация (работает только для локальных переменных).
- from/where/select — в LINQ-запросах.
- partial — разделение класса на несколько файлов.
5. Оператор switch с кортежами (C# 8+)
// Сравнение двух значений без if-else
string result = (a, b) switch
{
(> 0, > 0) => "Оба положительные",
(< 0, < 0) => "Оба отрицательные",
(_, 0) => "Второй ноль",
_ => "Остальные случаи"
};
Совет: не пытайтесь использовать все фишки сразу. Начните с yield return и extension-методов — они реально упрощают код. А static constructors используйте только когда точно знаете, зачем.
Альтернативный взгляд: фишки, о которых молчат даже в документации
Добавлю несколько менее известных, но очень полезных возможностей:
1. Null-conditional operators (?.) и null-coalescing assignment (??=)
// Безопасный доступ: если obj == null, то result = null
int? length = obj?.Name?.Length;
// Присвоение по умолчанию, если null
list ??= new List<string>(); // C# 8+
2. Stackalloc в безопасном коде (C# 7.2+)
Можно выделять память на стеке без unsafe-блоков:
Span<int> numbers = stackalloc int[10]; // Быстрее, чем new int[10]
3. Именованные аргументы и кортежи с именами
// Именованные аргументы делают код самодокументируемым
var result = Calculate(salary: 50000, taxRate: 0.13);
// Кортежи с именами полей (C# 7+)
var person = (Name: "Анна", Age: 30);
Console.WriteLine(person.Name); // "Анна"
4. Атрибут [CallerMemberName] (C# 5+)
Автоматически подставляет имя вызывающего метода — идеально для INotifyPropertyChanged:
void SetProperty<T>(ref T field, T value, [CallerMemberName] string prop = "")
{
field = value;
OnPropertyChanged(prop);
}
Главное правило: используйте фишки, которые делают код читаемее, а не короче ради спортивного интереса. И всегда помните — «скрытые» возможности часто становятся явными в следующей версии C#!