C# 14: Extension Members и как они помогают polyfill-библиотекам
C# давно поддерживает extension-методы, которые позволяют добавлять новые методы к существующим типам, не трогая исходный код. Но раньше это касалось только инстанс-методов. В C# 14 появились extension members, и область расширения заметно выросла. Теперь можно добавлять extension-свойства и extension-статические методы к существующим типам. Такая фича особенно полезна для polyfill-библиотек, которые позволяют использовать новые API в старых версиях .NET.
Polyfill-библиотеки помогают писать код с использованием современных API, но при этом таргетировать разные версии .NET. В предыдущем посте про polyfills я объяснял, что смысл таких библиотек заключается в том, чтобы предоставить реализацию новых API для старых фреймворков и уменьшить количество
До выхода C# 14 polyfill-библиотеки могли добавлять только инстанс-методы через extension methods. Это означало, что статические методы и свойства из новых версий .NET нормально не покрывались polyfill'ами. C# 14 снимает это ограничение и позволяет polyfill-библиотекам выглядеть куда полнее и нативнее.
Хороший пример это статический метод ArgumentNullException.ThrowIfNull, который появился в .NET 6. Он дает удобный способ проверить аргументы на null:
До C# 14 polyfill-библиотеки не могли добавить ThrowIfNull как статический метод ArgumentNullException, потому что extension методы работали только с инстансами. В C# 14 можно определить extension static method, и polyfill-библиотеки могут предоставить идентичный API даже для старых версий .NET. Теперь можно использовать ArgumentNullException.ThrowIfNull независимо от target framework, и код будет выглядеть единообразно и чище.
Конечно, можно писать свои extension static-методы и свойства, но проще воспользоваться уже поддерживаемой polyfill-библиотекой.
Пакет Meziantou.Polyfill (GitHub) может покрывать более 350 типов, методов и свойств. Он использует возможности C# 14, чтобы дать максимально полное polyfill-решение. Пакет основан на source generators, которые автоматически добавляют только те polyfills, которые реально нужны под ваш target framework.
Установка:
После установки можно использовать новые API вроде ArgumentNullException.ThrowIfNull даже при таргете на старые платформы, вроде .NET Standard 2.0 или .NET Framework:
Source generator автоматически определяет target framework, версию C# и опции компиляции, и генерирует extension members только тогда, когда это возможно и реально требуется. Если вы не используете C# 14 или ваш target framework уже содержит нужный API, polyfill просто не будет создан.
👉 @KodBlog
C# давно поддерживает extension-методы, которые позволяют добавлять новые методы к существующим типам, не трогая исходный код. Но раньше это касалось только инстанс-методов. В C# 14 появились extension members, и область расширения заметно выросла. Теперь можно добавлять extension-свойства и extension-статические методы к существующим типам. Такая фича особенно полезна для polyfill-библиотек, которые позволяют использовать новые API в старых версиях .NET.
Polyfill-библиотеки помогают писать код с использованием современных API, но при этом таргетировать разные версии .NET. В предыдущем посте про polyfills я объяснял, что смысл таких библиотек заключается в том, чтобы предоставить реализацию новых API для старых фреймворков и уменьшить количество
#if в проекте.До выхода C# 14 polyfill-библиотеки могли добавлять только инстанс-методы через extension methods. Это означало, что статические методы и свойства из новых версий .NET нормально не покрывались polyfill'ами. C# 14 снимает это ограничение и позволяет polyfill-библиотекам выглядеть куда полнее и нативнее.
Хороший пример это статический метод ArgumentNullException.ThrowIfNull, который появился в .NET 6. Он дает удобный способ проверить аргументы на null:
public void ProcessData(string data)
{
ArgumentNullException.ThrowIfNull(data);
// Process the data...
}
До C# 14 polyfill-библиотеки не могли добавить ThrowIfNull как статический метод ArgumentNullException, потому что extension методы работали только с инстансами. В C# 14 можно определить extension static method, и polyfill-библиотеки могут предоставить идентичный API даже для старых версий .NET. Теперь можно использовать ArgumentNullException.ThrowIfNull независимо от target framework, и код будет выглядеть единообразно и чище.
static class PolyfillExtensions
{
extension(ArgumentNullException)
{
public static void ThrowIfNull([NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
{
if (argument is null)
throw new ArgumentNullException(paramName);
}
}
}
Конечно, можно писать свои extension static-методы и свойства, но проще воспользоваться уже поддерживаемой polyfill-библиотекой.
Пакет Meziantou.Polyfill (GitHub) может покрывать более 350 типов, методов и свойств. Он использует возможности C# 14, чтобы дать максимально полное polyfill-решение. Пакет основан на source generators, которые автоматически добавляют только те polyfills, которые реально нужны под ваш target framework.
Установка:
dotnet add package Meziantou.PolyfillПосле установки можно использовать новые API вроде ArgumentNullException.ThrowIfNull даже при таргете на старые платформы, вроде .NET Standard 2.0 или .NET Framework:
public class UserService
{
public void CreateUser(string username, string email)
{
// Работает в .NET Standard 2.0 благодаря extension static methods
ArgumentNullException.ThrowIfNull(username);
ArgumentNullException.ThrowIfNull(email);
// Create user logic...
}
}
Source generator автоматически определяет target framework, версию C# и опции компиляции, и генерирует extension members только тогда, когда это возможно и реально требуется. Если вы не используете C# 14 или ваш target framework уже содержит нужный API, polyfill просто не будет создан.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤3🔥3
На Хабре разобрали YARP — это быстрый reverse proxy на .NET, и его можно удобно конфигурировать прямо в
Под капотом есть всё, что нужно для продовой нагрузки:
• поддержка HTTP/2 и gRPC
• трансформации путей и заголовков
• балансировка трафика (RoundRobin, PowerOfTwoChoices и другие алгоритмы)
• health-checks и session affinity
• TLS-терминация
В статье есть разбор, примеры конфигурации и демо от OTUS
👉 @KodBlog
ASP.NET Core через appsettings.json.Под капотом есть всё, что нужно для продовой нагрузки:
• поддержка HTTP/2 и gRPC
• трансформации путей и заголовков
• балансировка трафика (RoundRobin, PowerOfTwoChoices и другие алгоритмы)
• health-checks и session affinity
• TLS-терминация
В статье есть разбор, примеры конфигурации и демо от OTUS
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥2
C# expression-bodied члены просто топ.
А теперь к делу. Вот способ использовать enum для сортировки строк, которые сами по себе ничего не значат.
Бывало, что нужно отсортировать данные, пришедшие откуда-то извне, например из веб-API, где нет нормального поля для сортировки? Enum может стать небольшим хаком и избавить от магических строк.
👉 @KodBlog
А теперь к делу. Вот способ использовать enum для сортировки строк, которые сами по себе ничего не значат.
Бывало, что нужно отсортировать данные, пришедшие откуда-то извне, например из веб-API, где нет нормального поля для сортировки? Enum может стать небольшим хаком и избавить от магических строк.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8
В Visual Studio 2026 появилась возможность отключать отображение символов под файлами C# и C++ в Solution Explorer, и эта функция скоро станет доступна.
👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14😁6👎2❤1👏1
Оптимизация производительности создания GUID в .NET приложениях
Во многих .NET проектах GUID используют как идентификаторы для активностей, interop-сценариев, ключей в базах данных и многого другого. Часто можно встретить код вида new Guid("01234567-8901-2345-6789-012345678901"). Такой способ читаемый и простой, но у него есть скрытая цена = он может замедлять старт приложения.
Создание GUID из строки требует парсинга строкового представления, а это уже дополнительные операции. Плюс .NET поддерживает несколько форматов строковых GUID (с фигурными скобками, без, с дефисами и т.д.), из-за чего логика парсинга тоже усложняется.
Если GUID зашит в коде, эту нагрузку можно убрать, используя конструктор Guid, который принимает числовые параметры.
Например, строковое создание GUID:
Можно заменить на:
Так GUID собирается напрямую из числовых компонентов, без парсинга строки. Минус в том, что читаемость страдает, и вручную конвертировать строку в numeric-формат легко ошибиться. Плюс такой код сложнее искать в проекте. Решением будет оставлять строковое значение как комментарий.
Автоматическое обнаружение с Meziantou.Analyzer
Чтобы находить такие места автоматически, Meziantou.Analyzer содержит правило MA0176, которое ищет создание GUID из строковых литералов и предлагает использовать numeric-конструктор.
Устанавливается через .NET CLI или добавлением в csproj.
CLI:
Или в .csproj:
MA0176 ловит такие случаи и предлагает оптимизацию:
И автоматически заменяет на:
// Оптимизированный вариант
new Guid(0x01234567, 0x8901, 0x2345, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01) /* 01234567-8901-2345-6789-012345678901 */;
Бенчмарк
Использовался BenchmarkDotNet для сравнения:
Разница огромная (×1000 в пользу числового конструктора), но в большинстве приложений создаётся мало фиксированных GUID, поэтому влияние на общую производительность минимальное. Но при старте приложения, а особенно в сценариях вроде Azure Functions или AWS Lambda, где важны cold start задержки, эта оптимизация может помочь.
👉 @KodBlog
Во многих .NET проектах GUID используют как идентификаторы для активностей, interop-сценариев, ключей в базах данных и многого другого. Часто можно встретить код вида new Guid("01234567-8901-2345-6789-012345678901"). Такой способ читаемый и простой, но у него есть скрытая цена = он может замедлять старт приложения.
Важно отметить, что это микрооптимизация, которая в основном полезна в случаях, когда GUID создаются во время запуска приложения. Для обычного кода разница практически незаметна (смотри бенчмарк ниже).
Создание GUID из строки требует парсинга строкового представления, а это уже дополнительные операции. Плюс .NET поддерживает несколько форматов строковых GUID (с фигурными скобками, без, с дефисами и т.д.), из-за чего логика парсинга тоже усложняется.
Если GUID зашит в коде, эту нагрузку можно убрать, используя конструктор Guid, который принимает числовые параметры.
Например, строковое создание GUID:
var guid = new Guid("01234567-8901-2345-6789-012345678901");Можно заменить на:
var guid = new Guid(0x01234567, 0x8901, 0x2345, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01);
Так GUID собирается напрямую из числовых компонентов, без парсинга строки. Минус в том, что читаемость страдает, и вручную конвертировать строку в numeric-формат легко ошибиться. Плюс такой код сложнее искать в проекте. Решением будет оставлять строковое значение как комментарий.
Автоматическое обнаружение с Meziantou.Analyzer
Чтобы находить такие места автоматически, Meziantou.Analyzer содержит правило MA0176, которое ищет создание GUID из строковых литералов и предлагает использовать numeric-конструктор.
Устанавливается через .NET CLI или добавлением в csproj.
CLI:
dotnet add package Meziantou.AnalyzerИли в .csproj:
<PackageReference Include="Meziantou.Analyzer" Version="2.0.224">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
MA0176 ловит такие случаи и предлагает оптимизацию:
// Строковое создание (будет замечено анализатором)
_ = new Guid("01234567-8901-2345-6789-012345678901");
_ = Guid.Parse("01234567-8901-2345-6789-012345678901");
И автоматически заменяет на:
// Оптимизированный вариант
new Guid(0x01234567, 0x8901, 0x2345, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01) /* 01234567-8901-2345-6789-012345678901 */;
Бенчмарк
Использовался BenchmarkDotNet для сравнения:
public class NewGuid
{
[Benchmark(Baseline = true)]
public Guid GuidParse() => Guid.Parse("01234567-8901-2345-6789-012345678901");
[Benchmark]
public Guid NewGuidString() => new Guid("01234567-8901-2345-6789-012345678901");
[Benchmark]
public Guid NewGuidComponents() => new Guid(0x01234567, 0x8901, 0x2345, 0x67, 0x89, 0x01, 0x23, 0x45, 0x67, 0x89, 0x01);
}
GuidParse:
Mean 23.4168 ns
Error 0.4823 ns
StdDev 0.4511 ns
Median 23.3622 ns
NewGuidString:
Mean 22.6531 ns
Error 0.3757 ns
StdDev 0.3514 ns
Median 22.7011 ns
NewGuidComponents:
Mean 0.0215 ns
Error 0.0170 ns
StdDev 0.0366 ns
Median 0.0000 ns
Разница огромная (×1000 в пользу числового конструктора), но в большинстве приложений создаётся мало фиксированных GUID, поэтому влияние на общую производительность минимальное. Но при старте приложения, а особенно в сценариях вроде Azure Functions или AWS Lambda, где важны cold start задержки, эта оптимизация может помочь.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔6❤5🥴2👍1
Polly остаётся основным выбором для реализации отказоустойчивости в .NET, особенно теперь, когда он быстрее в V8+, и это важно, потому что HTTP-запросы могут падать из-за сетевых или серверных проблем, создавая риск каскадных отказов, а официальная библиотека resilience в .NET всего лишь обёртка над Polly с OpenTelemetry.
Вот как сделать приложение устойчивым: читать
👉 @KodBlog
Вот как сделать приложение устойчивым: читать
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3
Перегрузка операторов в C# 14 выходит на новый уровень и теперь вопрос только в том, стоит ли принимать такие замороченные варианты в кодовой базе, потому что подобные штуки могут взорвать мозг многим разработчикам
👉 @KodBlog
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯32👍9❤7👏1
Не злоупотребляй select * в запросах к базе.
Почему это плохо:
Схема может измениться, появятся новые поля, например огромная JSONB колонка, и ты внезапно начнешь таскать лишний трафик и грузить память.
Нельзя нормально использовать covering indexes, потому что запрос требует все колонки, а не только нужные.
Запрашивай только те поля, которые реально нужны в проде.
👉 @KodBlog
Почему это плохо:
Схема может измениться, появятся новые поля, например огромная JSONB колонка, и ты внезапно начнешь таскать лишний трафик и грузить память.
Нельзя нормально использовать covering indexes, потому что запрос требует все колонки, а не только нужные.
Запрашивай только те поля, которые реально нужны в проде.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🥴1