День 2385. #TipsAndTricks
5 Современных Возможностей C#, Которые Улучшат Ваш Код
C# стал гораздо более выразительным и мощным языком, чем был ещё несколько лет назад. Вот 5 новинок языка, которые отличают инженеров, просто «знающих C#», от тех, кто использует его на полную.
1. Структуры только для чтения для критически важных для производительности типов-значений
Обычная структура копируется без необходимости, что приводит к скрытым потерям производительности. Отметив её как только для чтения, вы сообщаете компилятору, что её поля никогда не изменятся, что позволяет среде выполнения не создавать защитные копии.
Раньше:
Сейчас:
Для высокопроизводительного кода, такого как графика, игры или API с большим количеством математических вычислений, это небольшое ключевое слово может означать значительную экономию.
2. Ключевое слово scoped для предотвращения использования ref-переменных вне области видимости
C# позволяет использовать ref-переменные для повышения производительности, но распространённой ошибкой является случайное их использование вне области видимости (возвращение ссылки на что-то, что больше не является безопасным). scoped гарантирует во время компиляции, что ссылка не проживёт дольше положенного.
Без scoped:
scoped:
3. Обязательные свойства для принудительной инициализации объекта
Сколько раз вы создавали объект и забывали установить одно из его критически важных свойств? При использовании обязательных свойств компилятор принуждает инициализировать объект.
Раньше:
Сейчас:
4. Возврат типа ref readonly, чтобы избежать защитного копирования
При возврате больших структур возврат по значению часто приводит к ненужному копированию. Возврат типа ref readonly даёт вызывающим функциям ссылку на объект, который они могут читать, но не могут изменять.
Без ref readonly:
С ref readonly:
Это особенно актуально в библиотеках, критичных к производительности, где структуры неизменяемы, но копирование требует больших затрат.
5. Статические абстрактные члены в интерфейсах для обобщённой математики
Это кажется узкоспециализированной функцией, но открывает доступ к настоящей обобщённой математике без рефлексии и упаковки. До появления этой функции нельзя было писать обобщённые алгоритмы, работающие с числовыми типами:
Благодаря статическим абстрактным членам, INumber<T> определяет операторы и константы обобщённо, обеспечивая чистые, независимые от типов математические библиотеки.
Итого
Современный C# — это не просто новый синтаксис; это достижение ясности, безопасности и производительности, которых просто не могли предложить старые версии. Начните внедрять эти функции в свой код, и вы не только начнёте писать лучшее ПО, но и по-новому взглянете на возможности языка.
Источник: https://blog.stackademic.com/5-modern-c-features-that-will-make-your-code-feel-like-magic-d1ef6a374d13
5 Современных Возможностей C#, Которые Улучшат Ваш Код
C# стал гораздо более выразительным и мощным языком, чем был ещё несколько лет назад. Вот 5 новинок языка, которые отличают инженеров, просто «знающих C#», от тех, кто использует его на полную.
1. Структуры только для чтения для критически важных для производительности типов-значений
Обычная структура копируется без необходимости, что приводит к скрытым потерям производительности. Отметив её как только для чтения, вы сообщаете компилятору, что её поля никогда не изменятся, что позволяет среде выполнения не создавать защитные копии.
Раньше:
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
Сейчас:
public readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
Для высокопроизводительного кода, такого как графика, игры или API с большим количеством математических вычислений, это небольшое ключевое слово может означать значительную экономию.
2. Ключевое слово scoped для предотвращения использования ref-переменных вне области видимости
C# позволяет использовать ref-переменные для повышения производительности, но распространённой ошибкой является случайное их использование вне области видимости (возвращение ссылки на что-то, что больше не является безопасным). scoped гарантирует во время компиляции, что ссылка не проживёт дольше положенного.
Без scoped:
ref int Dangerous(ref int number)
{
// Может быть использовано извне и привести к проблемам
return ref number;
}
scoped:
ref int Safe(scoped ref int number)
{
// Компилятор проверяет, что number может
// использоваться только внутри метода
// и выдаёт ошибку компиляции
return ref number;
}
3. Обязательные свойства для принудительной инициализации объекта
Сколько раз вы создавали объект и забывали установить одно из его критически важных свойств? При использовании обязательных свойств компилятор принуждает инициализировать объект.
Раньше:
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
// Можно забыть инициализировать свойства
var user = new User { Name = "John" };
Сейчас:
public class User
{
public required string Name { get; init; }
public required string Email { get; init; }
}
// Компилятор проверяет полноту инициализации
var user = new User {
Name = "John", Email = "[email protected]" };
4. Возврат типа ref readonly, чтобы избежать защитного копирования
При возврате больших структур возврат по значению часто приводит к ненужному копированию. Возврат типа ref readonly даёт вызывающим функциям ссылку на объект, который они могут читать, но не могут изменять.
Без ref readonly:
public BigStruct GetData() =>
_bigStruct; // Копирует структуру
С ref readonly:
public ref readonly BigStruct GetData() =>
ref _bigStruct; // Нет копирования
Это особенно актуально в библиотеках, критичных к производительности, где структуры неизменяемы, но копирование требует больших затрат.
5. Статические абстрактные члены в интерфейсах для обобщённой математики
Это кажется узкоспециализированной функцией, но открывает доступ к настоящей обобщённой математике без рефлексии и упаковки. До появления этой функции нельзя было писать обобщённые алгоритмы, работающие с числовыми типами:
T Add<T>(T a, T b) where T : INumber<T>
=> a + b;
Благодаря статическим абстрактным членам, INumber<T> определяет операторы и константы обобщённо, обеспечивая чистые, независимые от типов математические библиотеки.
Итого
Современный C# — это не просто новый синтаксис; это достижение ясности, безопасности и производительности, которых просто не могли предложить старые версии. Начните внедрять эти функции в свой код, и вы не только начнёте писать лучшее ПО, но и по-новому взглянете на возможности языка.
Источник: https://blog.stackademic.com/5-modern-c-features-that-will-make-your-code-feel-like-magic-d1ef6a374d13
👍25
День 2386. #ЗаметкиНаПолях
Неожиданная Несогласованность в Записях
На днях Джон Скит пытался найти ошибку в своём коде, и она оказалась следствием его непонимания принципов работы записей в C#. Как показывает вчерашний опрос, он не единственный, кто ожидал, что они будут работать именно так.
Когда записи появились в C#, одновременно появился и оператор «обратимого изменения» with. Идея заключается в том, что типы записей неизменяемы, но вы можете легко и эффективно создать новый экземпляр с теми же данными, что и существующий экземпляр, но с другими значениями некоторых свойств:
Это не изменяет данные первоначальной записи, т.е. entry.Score останется 5000.
Проблема
В качестве очень простого (и весьма надуманного) примера можно создать запись, которая определяет, является ли значение чётным или нечётным при инициализации:
На первый взгляд, всё нормально:
Но если мы изменим код на использование with:
Джон (и не только он) всегда предполагал, что оператор with вызывает конструктор с новыми значениями. На самом деле это не так. Оператор with выше преобразуется в примерно такой код:
Метод <Clone>$ (по крайней мере, в этом случае) вызывает сгенерированный конструктор копирования (Number(Number)), который копирует как Value, так и резервное поле для Even (т.е. значение Even). Всё это документировано, но пока компилятор не выдаёт никаких предупреждений о возможных несоответствиях, которые это может вызвать.
Конечно, вычисляемое свойство работает правильно. Значение вычисляется каждый раз при обращении к нему:
Что делать?
Пока просто знать об этом и жить дальше. Легко сказать, что можно просто использовать вычисляемые свойства. Проблема, если кто-то, не знающий об этом, добавит/изменит свойство на вычисляемое при инициализации. Либо если запись предоставлена в какой-то внешней библиотеке (не заглядывая в исходный код библиотеки, нет возможности определить какое свойство там использовано). Сложно представить, что это поведение будет изменено в языке. Но Джон попросил Майкрософт хотя бы добавить предупреждение в таких случаях, а также написал собственный анализатор.
Источник: https://codeblog.jonskeet.uk/2025/07/19/unexpected-inconsistency-in-records/
Неожиданная Несогласованность в Записях
На днях Джон Скит пытался найти ошибку в своём коде, и она оказалась следствием его непонимания принципов работы записей в C#. Как показывает вчерашний опрос, он не единственный, кто ожидал, что они будут работать именно так.
Когда записи появились в C#, одновременно появился и оператор «обратимого изменения» with. Идея заключается в том, что типы записей неизменяемы, но вы можете легко и эффективно создать новый экземпляр с теми же данными, что и существующий экземпляр, но с другими значениями некоторых свойств:
public record HighScoreEntry(
string PlayerName, int Score, int Level);
HighScoreEntry entry = new("Jon", 5000, 50);
var updatedEntry =
entry with { Score = 6000, Level = 55 };
Это не изменяет данные первоначальной записи, т.е. entry.Score останется 5000.
Проблема
В качестве очень простого (и весьма надуманного) примера можно создать запись, которая определяет, является ли значение чётным или нечётным при инициализации:
public record Number(int Value)
{
public bool Even { get; } =
(Value & 1) == 0;
}
На первый взгляд, всё нормально:
var n2 = new Number(2);
var n3 = new Number(3);
Console.WriteLine(n2);
// Number { Value = 2, Even = True }
Console.WriteLine(n3);
// Number { Value = 3, Even = False }
Но если мы изменим код на использование with:
var n3 = n2 with { Value = 3 };
Console.WriteLine(n3);
// Number { Value = 3, Even = True }
Джон (и не только он) всегда предполагал, что оператор with вызывает конструктор с новыми значениями. На самом деле это не так. Оператор with выше преобразуется в примерно такой код:
var n3 = n2.<Clone>$();
n3.Value = 3;
Метод <Clone>$ (по крайней мере, в этом случае) вызывает сгенерированный конструктор копирования (Number(Number)), который копирует как Value, так и резервное поле для Even (т.е. значение Even). Всё это документировано, но пока компилятор не выдаёт никаких предупреждений о возможных несоответствиях, которые это может вызвать.
Конечно, вычисляемое свойство работает правильно. Значение вычисляется каждый раз при обращении к нему:
public record Number(int Value)
{
public bool Even => (Value & 1) == 0;
}
Что делать?
Пока просто знать об этом и жить дальше. Легко сказать, что можно просто использовать вычисляемые свойства. Проблема, если кто-то, не знающий об этом, добавит/изменит свойство на вычисляемое при инициализации. Либо если запись предоставлена в какой-то внешней библиотеке (не заглядывая в исходный код библиотеки, нет возможности определить какое свойство там использовано). Сложно представить, что это поведение будет изменено в языке. Но Джон попросил Майкрософт хотя бы добавить предупреждение в таких случаях, а также написал собственный анализатор.
Источник: https://codeblog.jonskeet.uk/2025/07/19/unexpected-inconsistency-in-records/
👍21