День 2460. #ЗаметкиНаПолях
Встраивание и Структуры в C#
Сегодня рассмотрим встраивание структур в C#. И как оно может оптимизировать производительность несколькими интересными способами.
Встраивание
Встраивание — это оптимизация компилятора, которая заменяет вызов метода его телом. Например:
Компилятор может оптимизировать его до:
Очевидное преимущество здесь в том, что мы избегаем накладных расходов на вызов метода, но это также может увеличить размер кода и негативно сказаться на производительности (поскольку мы копируем тело метода во все места его вызова). Существует атрибут
Структуры
Неотъемлемой частью структур является то, что они, как правило, передаются по значению. Это означает, что при передаче структуры методу создаётся её копия. Т.е.:
В идеале структуры лучше сохранять неизменяемыми именно по этой причине. А также делать небольшими, чтобы избежать накладных расходов на копирование. И вот в чём «прелесть»: встраивая функцию, мы «стираем» необходимость копирования структуры в новый стековый фрейм. Т.е. встраивание может фактически удешевить передачу структур.
Посмотрите вот этот пример на sharplab.io. Вам не нужно понимать JIT-код ASM. Но сама разница в объёме кода между
PS: на самом деле JIT совершает множество гораздо более хитрых оптимизаций вашего кода. На последнем DotNext об этом был хороший доклад Дмитрия Егорова "JIT не волшебство: как он работает и как не мешать". У кого есть доступ, обязательно посмотрите. Остальные - подождите выхода на Youtube.
Источник: https://steven-giesel.com/blogPost/e89d7156-f3fd-4152-b78a-cb908bc43226/inlining-and-structs-in-c
Встраивание и Структуры в C#
Сегодня рассмотрим встраивание структур в C#. И как оно может оптимизировать производительность несколькими интересными способами.
Встраивание
Встраивание — это оптимизация компилятора, которая заменяет вызов метода его телом. Например:
public int Add(int a, int b) => a + b;
public int CalculateSum(int x, int y)
=> Add(x, y);
Компилятор может оптимизировать его до:
public int CalculateSum(int x, int y)
=> x + y;
Очевидное преимущество здесь в том, что мы избегаем накладных расходов на вызов метода, но это также может увеличить размер кода и негативно сказаться на производительности (поскольку мы копируем тело метода во все места его вызова). Существует атрибут
[MethodImpl(MethodImplOptions.AggressiveInlining)], который можно использовать, чтобы подсказать компилятору, что метод следует встраивать, даже если по умолчанию он этого не делает. Это всего лишь подсказка, и JIT всё равно может её проигнорировать. Мы также можем использовать [MethodImpl(MethodImplOptions.NoInlining)], чтобы предотвратить встраивание метода (подсказать JIT, что встраивать его не следует).Структуры
Неотъемлемой частью структур является то, что они, как правило, передаются по значению. Это означает, что при передаче структуры методу создаётся её копия. Т.е.:
public struct Point
{
public int X;
public int Y;
}
public void MovePoint(Point p)
{
p.X += 10;
p.Y += 10;
}
Point myPoint = new Point { X = 0, Y = 0 };
MovePoint(myPoint);
// myPoint всё ещё { X = 0, Y = 0 }
В идеале структуры лучше сохранять неизменяемыми именно по этой причине. А также делать небольшими, чтобы избежать накладных расходов на копирование. И вот в чём «прелесть»: встраивая функцию, мы «стираем» необходимость копирования структуры в новый стековый фрейм. Т.е. встраивание может фактически удешевить передачу структур.
Посмотрите вот этот пример на sharplab.io. Вам не нужно понимать JIT-код ASM. Но сама разница в объёме кода между
InlineVsNonInlineBenchmark.NonInline() и InlineVsNonInlineBenchmark.Inline() показывает, что Inline скорее всего будет работать быстрее. По сути, NonInline приходится много копировать (инструкции vmovdqu и vmovq), в то время как Inline просто считывает свойство, добавляет что-то и возвращает результат.PS: на самом деле JIT совершает множество гораздо более хитрых оптимизаций вашего кода. На последнем DotNext об этом был хороший доклад Дмитрия Егорова "JIT не волшебство: как он работает и как не мешать". У кого есть доступ, обязательно посмотрите. Остальные - подождите выхода на Youtube.
Источник: https://steven-giesel.com/blogPost/e89d7156-f3fd-4152-b78a-cb908bc43226/inlining-and-structs-in-c
👍8
День 2461. #ЧтоНовенького
Спонсорство на NuGet.org
Свершилось то, о чём так долго говорили большевики. Похоже, реагируя на волну коммерциализации популярных бесплатных NuGet-пакетов, NuGet.org предложили реализовать спонсорство, которое упрощает для пользователей возможность поддерживать авторов своих любимых пакетов.
NuGet.org теперь позволяет авторам пакетов добавлять URL спонсорства. Эта ссылка отображается в виде значка ❤️ или кнопки Sponsor this package (Спонсировать этот пакет) на странице пакета, направляя пользователей к безопасным и популярным платформам.
Пользователи пакетов теперь смогут поддерживать любимые пакеты. Найдите значок
Для владельцев пакетов
Чтобы установить спонсорство для пакета:
1. вы должны быть владельцем или совладельцем пакета на NuGet.org;
2. спонсорская ссылка должна быть на одну из одобренных платформ:
- GitHub Sponsors
- Patreon
- Open Collective
- Ko-fi
- Tidelift
- Liberapay
Зайдите в панель управления пакетами и раскройте блок Sponsorship Links (Ссылки Спонсорства). Добавьте ссылку, система автоматически проверит URL. После добавления ссылок значок
На данный момент поддерживаются только перечисленные платформы, и NuGet.org не собирает никакой статистики или финансовой информации о спонсорстве. Всё это отдано на откуп этим платформам.
Источник: https://devblogs.microsoft.com/dotnet/announcing-sponsorship-on-nugetdotorg-for-maintainer-appreciation/
Спонсорство на NuGet.org
NuGet.org теперь позволяет авторам пакетов добавлять URL спонсорства. Эта ссылка отображается в виде значка ❤️ или кнопки Sponsor this package (Спонсировать этот пакет) на странице пакета, направляя пользователей к безопасным и популярным платформам.
Пользователи пакетов теперь смогут поддерживать любимые пакеты. Найдите значок
❤️ Sponsor на NuGet.org (см. картинку выше). Нажмите на него, чтобы посетить страницу спонсорства и внести свой вклад.Для владельцев пакетов
Чтобы установить спонсорство для пакета:
1. вы должны быть владельцем или совладельцем пакета на NuGet.org;
2. спонсорская ссылка должна быть на одну из одобренных платформ:
- GitHub Sponsors
- Patreon
- Open Collective
- Ko-fi
- Tidelift
- Liberapay
Зайдите в панель управления пакетами и раскройте блок Sponsorship Links (Ссылки Спонсорства). Добавьте ссылку, система автоматически проверит URL. После добавления ссылок значок
❤️ Sponsor появится на странице пакета.На данный момент поддерживаются только перечисленные платформы, и NuGet.org не собирает никакой статистики или финансовой информации о спонсорстве. Всё это отдано на откуп этим платформам.
Источник: https://devblogs.microsoft.com/dotnet/announcing-sponsorship-on-nugetdotorg-for-maintainer-appreciation/
👍7
День 2462. #ЗаметкиНаПолях
Динамический LINQ. Начало
Вы реализуете простую конечную точку поиска… а затем приходит таска: «А можно отфильтровать по статусу? По последнему входу в систему? Сортировать по любому столбцу?» и т.п. И вот ваш красивый LINQ-запрос превращается в лес if, а каждое изменение означает повторное развёртывание. Знакомо? Рассмотрим динамические предикаты, которые выполняются как настоящий LINQ (поэтому EF Core транслирует их в SQL): как они работают, когда использовать и несколько полезных советов.
Зачем?
- Неограниченные фильтры: интерфейсы администратора, конструкторы отчётов, сохранённые поисковые запросы — пользователи сами выбирают поля/операции.
- Разные тенанты: каждому клиенту нужны немного разные правила.
- Сохранение производительности EF: библиотека преобразует строку в лямбда-выражение и вызывает реальный метод LINQ (Where, OrderBy, …) для IQueryable; EF по-прежнему перекладывает работу на SQL.
Как работает?
С помощью C# Eval Expression вы можете предоставить выражение в виде строки и вызвать динамическое расширение: WhereDynamic, OrderByDynamic, SelectDynamic, FirstOrDefaultDynamic и т.д. Внутри библиотеки оно преобразуется в дерево выражений и вызывается фактический оператор LINQ. Это работает для IEnumerable и IQueryable (включая EF Core).
Мы рассмотрим некоторые самые распространённые варианты использования:
- WhereDynamic — динамическая фильтрация,
- OrderByDynamic/ThenByDynamic — динамическая сортировка,
- SelectDynamic — динамическая проекция,
- FirstOrDefaultDynamic — динамическое извлечение единичных значений.
1. WhereDynamic
До: куча условных блоков
После: один динамический предикат
Почему это лучше: один конвейер, отсутствие дублирующихся запросов, и вы можете добавлять необязательные критерии, не изменяя форму запроса.
Кстати, не обязательно вставлять литералы в строку. Передайте объект контекста (анонимный тип/словарь/expando/класс) и ссылайтесь на его свойства по имени внутри выражения:
Окончание следует…
Источник: https://thecodeman.net/posts/dynamic-linq-in-dotnet
Динамический LINQ. Начало
Вы реализуете простую конечную точку поиска… а затем приходит таска: «А можно отфильтровать по статусу? По последнему входу в систему? Сортировать по любому столбцу?» и т.п. И вот ваш красивый LINQ-запрос превращается в лес if, а каждое изменение означает повторное развёртывание. Знакомо? Рассмотрим динамические предикаты, которые выполняются как настоящий LINQ (поэтому EF Core транслирует их в SQL): как они работают, когда использовать и несколько полезных советов.
Зачем?
- Неограниченные фильтры: интерфейсы администратора, конструкторы отчётов, сохранённые поисковые запросы — пользователи сами выбирают поля/операции.
- Разные тенанты: каждому клиенту нужны немного разные правила.
- Сохранение производительности EF: библиотека преобразует строку в лямбда-выражение и вызывает реальный метод LINQ (Where, OrderBy, …) для IQueryable; EF по-прежнему перекладывает работу на SQL.
Как работает?
С помощью C# Eval Expression вы можете предоставить выражение в виде строки и вызвать динамическое расширение: WhereDynamic, OrderByDynamic, SelectDynamic, FirstOrDefaultDynamic и т.д. Внутри библиотеки оно преобразуется в дерево выражений и вызывается фактический оператор LINQ. Это работает для IEnumerable и IQueryable (включая EF Core).
Мы рассмотрим некоторые самые распространённые варианты использования:
- WhereDynamic — динамическая фильтрация,
- OrderByDynamic/ThenByDynamic — динамическая сортировка,
- SelectDynamic — динамическая проекция,
- FirstOrDefaultDynamic — динамическое извлечение единичных значений.
1. WhereDynamic
До: куча условных блоков
var q = context.Customers.AsQueryable();
if (onlyActive)
q = q.Where(x => x.Status == CustomerStatus.IsActive);
if (since != null)
q = q.Where(x => x.LastLogon >= since);
if (!string.IsNullOrWhiteSpace(search))
q = q.Where(x => x.Name.Contains(search));
var list = await q.OrderBy(x => x.Name).ToListAsync();
После: один динамический предикат
// using System.Linq;
// using Z.Expressions;
string filter = "x => true";
if (onlyActive)
filter += " && x.Status == 0";
if (since != null)
filter += $@" && x.LastLogon >= DateTime.Parse(""{since:yyyy-MM-dd}"")";
if (!string.IsNullOrWhiteSpace(search))
filter += $@" && x.Name.Contains(""{search}"")";
var list = await context.Customers
.WhereDynamic(filter)
.OrderBy(x => x.Name)
.ToListAsync();
Почему это лучше: один конвейер, отсутствие дублирующихся запросов, и вы можете добавлять необязательные критерии, не изменяя форму запроса.
Кстати, не обязательно вставлять литералы в строку. Передайте объект контекста (анонимный тип/словарь/expando/класс) и ссылайтесь на его свойства по имени внутри выражения:
var env = new {
IsActive = CustomerStatus.IsActive,
LastMonth = DateTime.Now.AddMonths(-1)
};
var recentActive = await context.Customers
.WhereDynamic(x => "x.Status == IsActive
&& x.LastLogon >= LastMonth", env)
.ToListAsync();Окончание следует…
Источник: https://thecodeman.net/posts/dynamic-linq-in-dotnet
👎28👍6
День 2463. #ЗаметкиНаПолях
Динамический LINQ. Окончание
Начало
2. OrderByDynamic/ThenByDynamic
Даём пользователю возможность отсортировать результаты по любой колонке (из белого списка).
3. SelectDynamic
Для сценариев отчётов/экспорта, выдаём только то, что запросил клиент:
4. FirstOrDefaultDynamic
Идеален для поиска одного элемента или валидации на основе критерия, определяющегося во время выполнения:
Бонус: Цепочки динамических конвейеров
Если очень нужно выполнить несколько шагов LINQ в одной динамической строке (фильтрация → упорядочивание → выборка → список), есть API Execute:
Это мощный метод, но код получается не особо читаемый.
Варианты использования
1. Администрирование «Конструктор запросов»
- UI генерирует: поле + оператор + значение;
- Бэкенд выбирает разрешённые поля/операции → строит WhereDynamic (и опционально OrderByDynamic);
- Результат: один конвейер запросов, почти бесконечное количество комбинаций.
2. Маркетинг «Конструктор сегментов»
- Сегменты сохраняются как читаемые выражения (например, «Активные, последние 90 дней,>$500,не тестовые аккаунты»);
- Приложение загружает правило → WhereDynamic → сохраняет результаты;
- Результат: правила развиваются без изменения кода или повторного развёртывания.
3. Многопользовательские правила
- Каждый пользователь хранит несколько предикатов (или базовых фильтров разрешения/запрета);
- Они комбинируются во время запроса и применяются динамически;
- Результат: меньше ветвлений/флагов, более чистая модель.
Замечания
- Белый список полей/операторов: не раскрывайте всю модель; предоставьте в UI только разрешённый список.
- Проверка выражений: отклоняйте неизвестные токены/поля перед выполнением.
- IQueryable до конца: применяйте динамические операции перед ToList(), чтобы EF мог преобразовать их в SQL.
- Нормализуйте значения: используйте даты ISO или параметры (объект env) вместо парсинга текста.
- Сохраняйте читаемость: предпочитайте короткие, компонуемые строки; добавляйте вспомогательные методы для частых случаев.
Когда не использовать
Если у вас 2-3 фиксированных фильтра, которые редко меняются, статический LINQ остается самым простым (и вполне приемлемым). Динамический LINQ имеет смысл при росте изменчивости и опциональности.
Источник: https://thecodeman.net/posts/dynamic-linq-in-dotnet
Динамический LINQ. Окончание
Начало
2. OrderByDynamic/ThenByDynamic
Даём пользователю возможность отсортировать результаты по любой колонке (из белого списка).
var sort = sortCol switch
{
"LastLogon" => "x => x.LastLogon",
"TotalSpent" => "x => x.TotalSpent",
_ => "x => x.Name"
};
var ordered = await context.Customers
.OrderByDynamic(sort)
.ThenByDynamic("x => x.Id")
.ToListAsync();
3. SelectDynamic
Для сценариев отчётов/экспорта, выдаём только то, что запросил клиент:
// Клиент выбрал: "Id,Name,Country"
var cols = selectedCols.Split(',')
.Select(c => c.Trim())
.Where(c => allowedCols.Contains(c));
var selectExpr = "x => new { "
+ string.Join(", ", cols.Select(c => $"{c} = x.{c}"))
+ " }";
var result = await context.Customers
.WhereDynamic("x => x.Status == 0")
.SelectDynamic(selectExpr)
.ToListAsync();
4. FirstOrDefaultDynamic
Идеален для поиска одного элемента или валидации на основе критерия, определяющегося во время выполнения:
csharp
var result = await context.Customers
.FirstOrDefaultDynamic(
"x => x.Email == \\\"[email protected]\\\" && x.Status == 0");
Бонус: Цепочки динамических конвейеров
Если очень нужно выполнить несколько шагов LINQ в одной динамической строке (фильтрация → упорядочивание → выборка → список), есть API Execute:
var env = new {
IsActive = CustomerStatus.IsActive,
LastMonth = DateTime.Now.AddMonths(-1) };
var result = context.Customers.Execute<IEnumerable>(
"Where(x => x.Status == IsActive && x.LastLogon >= LastMonth)" +
".Select(x => new { x.CustomerID, x.Name })" +
".OrderBy(x => x.CustomerID).ToList()", env);Это мощный метод, но код получается не особо читаемый.
Варианты использования
1. Администрирование «Конструктор запросов»
- UI генерирует: поле + оператор + значение;
- Бэкенд выбирает разрешённые поля/операции → строит WhereDynamic (и опционально OrderByDynamic);
- Результат: один конвейер запросов, почти бесконечное количество комбинаций.
2. Маркетинг «Конструктор сегментов»
- Сегменты сохраняются как читаемые выражения (например, «Активные, последние 90 дней,>$500,не тестовые аккаунты»);
- Приложение загружает правило → WhereDynamic → сохраняет результаты;
- Результат: правила развиваются без изменения кода или повторного развёртывания.
3. Многопользовательские правила
- Каждый пользователь хранит несколько предикатов (или базовых фильтров разрешения/запрета);
- Они комбинируются во время запроса и применяются динамически;
- Результат: меньше ветвлений/флагов, более чистая модель.
Замечания
- Белый список полей/операторов: не раскрывайте всю модель; предоставьте в UI только разрешённый список.
- Проверка выражений: отклоняйте неизвестные токены/поля перед выполнением.
- IQueryable до конца: применяйте динамические операции перед ToList(), чтобы EF мог преобразовать их в SQL.
- Нормализуйте значения: используйте даты ISO или параметры (объект env) вместо парсинга текста.
- Сохраняйте читаемость: предпочитайте короткие, компонуемые строки; добавляйте вспомогательные методы для частых случаев.
Когда не использовать
Если у вас 2-3 фиксированных фильтра, которые редко меняются, статический LINQ остается самым простым (и вполне приемлемым). Динамический LINQ имеет смысл при росте изменчивости и опциональности.
Источник: https://thecodeman.net/posts/dynamic-linq-in-dotnet
👍7👎1
День 2464. #ЗаметкиНаПолях
Потокобезопасная Инициализация с Помощью LazyInitializer
Хотя Lazy<T> является основным решением для ленивой инициализации в .NET, существует менее известная альтернатива, которая может быть более эффективной в некоторых сценариях: LazyInitializer.EnsureInitialized.
Этот статический метод обеспечивает потокобезопасную инициализацию с рядом ключевых характеристик:
- Только ссылочные типы: работает только с классами, но не со значениями.
- Обнаружение на основе NULL: использует значение NULL для определения необходимости инициализации.
- Эффективное использование памяти по сравнению с System.Lazy<T>: не требуется дополнительный объект-обёртка.
Метод предлагает несколько перегрузок для различных сценариев:
Замечания по потокобезопасности
По умолчанию LazyInitializer.EnsureInitialized ведёт себя как LazyThreadSafetyMode.PublicationOnly:
Несколько потоков могут создавать экземпляры одновременно. Сохраняется только первое успешное назначение. Остальные экземпляры отбрасываются.
Для строгой потокобезопасности используйте перегрузку с блокировкой синхронизации, чтобы гарантировать, что фабричный метод будет выполнен только один раз.
Внутренняя реализация этого аналогична следующему коду:
Преимущества
Главное преимущество перед Lazy<T> — вместо хранения объекта-обёртки Lazy<T> вы напрямую используете целевой экземпляр. Также это снижает затраты памяти, экономя одно поле на каждое лениво инициализированное значение.
Источник: https://www.meziantou.net/thread-safe-initialization-with-lazyinitializer.htm
Потокобезопасная Инициализация с Помощью LazyInitializer
Хотя Lazy<T> является основным решением для ленивой инициализации в .NET, существует менее известная альтернатива, которая может быть более эффективной в некоторых сценариях: LazyInitializer.EnsureInitialized.
Этот статический метод обеспечивает потокобезопасную инициализацию с рядом ключевых характеристик:
- Только ссылочные типы: работает только с классами, но не со значениями.
- Обнаружение на основе NULL: использует значение NULL для определения необходимости инициализации.
- Эффективное использование памяти по сравнению с System.Lazy<T>: не требуется дополнительный объект-обёртка.
Метод предлагает несколько перегрузок для различных сценариев:
Sample? _instance = null;
// Инициализация через конструктор по умолчанию
var instance =
LazyInitializer.EnsureInitialized(ref _instance);
// Фабричный метод
var instance =
LazyInitializer.EnsureInitialized(
ref _instance,
() => new Sample()
);
// Фабричный метод вызывается 1 раз, даже если нескольким потокам требуется инициализация
var syncLock = new object();
var instance =
LazyInitializer.EnsureInitialized(
ref _instance,
ref syncLock,
() => new Sample()
);
Замечания по потокобезопасности
По умолчанию LazyInitializer.EnsureInitialized ведёт себя как LazyThreadSafetyMode.PublicationOnly:
Несколько потоков могут создавать экземпляры одновременно. Сохраняется только первое успешное назначение. Остальные экземпляры отбрасываются.
Для строгой потокобезопасности используйте перегрузку с блокировкой синхронизации, чтобы гарантировать, что фабричный метод будет выполнен только один раз.
Внутренняя реализация этого аналогична следующему коду:
var instance = Volatile.Read(ref _instance) ??
Interlocked.CompareExchange(
ref _instance,
new Sample(),
null);
Преимущества
Главное преимущество перед Lazy<T> — вместо хранения объекта-обёртки Lazy<T> вы напрямую используете целевой экземпляр. Также это снижает затраты памяти, экономя одно поле на каждое лениво инициализированное значение.
Источник: https://www.meziantou.net/thread-safe-initialization-with-lazyinitializer.htm
👍12
День 2465.
Расширение Upgrade Assistant Недоступно в VS 17.14
Решили мы всё-таки начать обновляться с нашего .NET Framework до чего-то более современного. 🥳 Глядишь, где-нибудь к 12му .NET может и успеем. Но сейчас не об этом.
Я с удивлением обнаружил, что после последнего обновления в Visual Studio 2022 (версии 17.14) пропал пункт меню Upgrade, который вызывал расширение Upgrade Assistant.
Оказывается, расширение не поддерживается в версиях старше 17.13. Вместо этого появился пункт Modernize с иконкой Copilot. То есть вместо чёткого пошагового обновления предлагается отдать всё на откуп ИИ. Уже странное предложение. Но это ещё не всё. При попытке всё-таки начать обновление через Modernize, вылетает следующее сообщение:
GitHub Copilot app modernization is available exclusively to users on GitHub Copilot Pro, Pro+, Business, and Enterprise plans. For more details please refer to the documentation.
(Модернизация приложения GitHub Copilot доступна исключительно пользователям тарифных планов GitHub Copilot Pro, Pro+, Business и Enterprise. Подробнее см. в документации.)
То есть мало того, что не работает бесплатное работавшее раньше решение, так вместо него предлагается использовать недетерминированные ответы ИИ, да ещё только в платной подписке.
Стоит ли говорить, что у пользователей от такого нововведения серьёзно подгорело, и они стали строчить гневные отзывы в поддержку. Например, только в этом треде 128 постов.
В итоге в Microsoft сдали назад и вернули возможность использовать Upgrade Assistant. Для этого необходимо выполнить несколько шагов:
1. Удалить следующие расширения VS, если они имеются:
- Upgrade Assistant (.NET Upgrade Assistant - Visual Studio Marketplace)
- GitHub Copilot App Modernization – Upgrade for .NET (GitHub Copilot app modernization - upgrade for .NET - Visual Studio Marketplace)
- Azure Migrate Application and Code Assessment for .NET (Azure Migrate application and code assessment - Visual Studio Marketplace).
2. Перейти в Tools -> Options -> Projects and Solutions -> Modernization (Инструменты -> Настройки -> Проекты и Решения -> Модернизация) и установить Enable legacy Upgrade Assistant (Включить устаревший Помощник по Обновлению) в True.
3. Перезагрузить Visual Studio.
Расширение Upgrade Assistant Недоступно в VS 17.14
Решили мы всё-таки начать обновляться с нашего .NET Framework до чего-то более современного. 🥳 Глядишь, где-нибудь к 12му .NET может и успеем. Но сейчас не об этом.
Я с удивлением обнаружил, что после последнего обновления в Visual Studio 2022 (версии 17.14) пропал пункт меню Upgrade, который вызывал расширение Upgrade Assistant.
Оказывается, расширение не поддерживается в версиях старше 17.13. Вместо этого появился пункт Modernize с иконкой Copilot. То есть вместо чёткого пошагового обновления предлагается отдать всё на откуп ИИ. Уже странное предложение. Но это ещё не всё. При попытке всё-таки начать обновление через Modernize, вылетает следующее сообщение:
GitHub Copilot app modernization is available exclusively to users on GitHub Copilot Pro, Pro+, Business, and Enterprise plans. For more details please refer to the documentation.
(Модернизация приложения GitHub Copilot доступна исключительно пользователям тарифных планов GitHub Copilot Pro, Pro+, Business и Enterprise. Подробнее см. в документации.)
То есть мало того, что не работает бесплатное работавшее раньше решение, так вместо него предлагается использовать недетерминированные ответы ИИ, да ещё только в платной подписке.
Стоит ли говорить, что у пользователей от такого нововведения серьёзно подгорело, и они стали строчить гневные отзывы в поддержку. Например, только в этом треде 128 постов.
В итоге в Microsoft сдали назад и вернули возможность использовать Upgrade Assistant. Для этого необходимо выполнить несколько шагов:
1. Удалить следующие расширения VS, если они имеются:
- Upgrade Assistant (.NET Upgrade Assistant - Visual Studio Marketplace)
- GitHub Copilot App Modernization – Upgrade for .NET (GitHub Copilot app modernization - upgrade for .NET - Visual Studio Marketplace)
- Azure Migrate Application and Code Assessment for .NET (Azure Migrate application and code assessment - Visual Studio Marketplace).
2. Перейти в Tools -> Options -> Projects and Solutions -> Modernization (Инструменты -> Настройки -> Проекты и Решения -> Модернизация) и установить Enable legacy Upgrade Assistant (Включить устаревший Помощник по Обновлению) в True.
3. Перезагрузить Visual Studio.
👍30
День 2466. #ВопросыНаСобеседовании
Оптимизировал Тормозящий Микросервис и Шокировал Интервьюера
Техническое собеседование проходило как любое другое. Стандартные вопросы и страх, что спросят о чём-то, с чем ты работал года 2 назад. А потом прозвучал убийственный вопрос: «Один из микросервисов работает очень медленно из-за внешних вызовов API. Как его оптимизировать?»
Хм… Это не про заученный алгоритм и не про архитектуру. Надо размышлять как опытный бэкенд.
1. Без паники, сначала диагностика
Каждый интервьюер ожидает, что вы сразу перейдёте к «кэшированию» или «параллельным вызовам». Но я сказал: «Сначала я проверю, действительно ли внешний API является узким местом». Интервьюер приподнял бровь.
Я пояснил. Добавим трассировку (Open Telemetry) для отслеживания длительности каждого внешнего вызова. Посмотрим задержки для каждой зависимости (иногда медленные наша сериализация или парсинг JSON). Посмотрим на 95-й и 99-й процентили времени отклика, т.к., если 1 из 100 вызовов занимает 5 секунд, этого достаточно, чтобы сервис казался медленным. Проверим закономерности повторных попыток. Если API нестабилен и наш сервис повторяет запрос по три раза, мы утраиваем время отклика.
Цель не в том, чтобы исправить ошибку, а в том, чтобы доказать, что я не исправляю её вслепую. Интервьюер кивнул.
2. Оптимизаций на стороне клиента
Если внешний API действительно медленный, перейдём к улучшениям на клиенте:
- Установим разумные тайм-ауты (2–3 секунды), в зависимости от SLA, и быстрый выход при таймауте.
- Настроим повторные попытки с экспоненциальной задержкой и ограничим количество повторных попыток, например: 1 – через 200 мс, 2 – через 400мс, 3 – через 800мс. Если ответа нет – выходим.
- Добавим «выключатель» (Circuit Breaker). Если API продолжит давать сбои, прервём попытки. Нет смысла долбить сломанную конечную точку.
3. Параллелизм и асинхронные вызовы
Вместо последовательного выполнения нескольких внешних вызовов, распараллелим их, используя асинхронные шаблоны вроде класса Parallel или Task.WhenAll(). Теперь общее время всех запросов будет близко к времени самого медленного, а не к сумме всех.
4. Кэширование
Если данные из внешнего API меняются нечасто, кэшируем их. Можно использовать кэш в памяти, распределённый или гибридный и кэшировать ответы, например, на 10 минут, чтобы кэш достаточно часто обновлялся. Когда API недоступен, можно выдавать слегка устаревшие данные из кэша. Но здесь важно настроить стратегии инвалидации, отслеживать утечки памяти и соотношение попаданий и промахов.
5. Пакетирование и агрегация
Возможно, внешний API поддерживает пакетные запросы, так что можно делать 1 запрос вместо нескольких, например, отправлять несколько идентификаторов в одном запросе. Либо периодически скачивать все данные из API и использовать их локально.
6. Пересмотр архитектуры
Если сервис слишком сильно зависит от внешних API, можно рассмотреть событийно-ориентированную архитектуру. Вместо вызова API в реальном времени обрабатывать данные асинхронно через очереди сообщений (Kafka или RabbitMQ). Так основной поток будет реагировать быстро, а фоновые процессы будут извлекать или обновлять внешние данные позже.
Можно внедрить API Gateway, кэширующий или объединяющий несколько ответов внешнего API. Тогда наш микросервис будет обращаться к одной оптимизированной конечной точке вместо десяти разных.
7. Плавная деградация
Иногда нужно просто корректно завершить работу:
- Установить тайм-аут и возвращать кэшированные/резервные данные.
- Регистрировать инциденты, но не допускать сбоя в работе.
Пользователям всё равно, какой API дал сбой. Им важно, чтобы приложение работало.
8. Мониторинг
Добавим метрики, будем отслеживать задержку по каждой зависимости, оповещать о резком увеличении времени отклика, следить за закономерностями при замедлениях.
После того, как я закончил, интервьюер помолчал. А потом сказал: «Да… как-то так мы и решили эту проблему».
Источник: https://blog.stackademic.com/interviewer-i-optimized-a-laggy-microservice-and-shocked-my-interviewer-heres-what-i-did-562cd8f0dcee
Автор оригинала: Shanvika Devi
Оптимизировал Тормозящий Микросервис и Шокировал Интервьюера
Техническое собеседование проходило как любое другое. Стандартные вопросы и страх, что спросят о чём-то, с чем ты работал года 2 назад. А потом прозвучал убийственный вопрос: «Один из микросервисов работает очень медленно из-за внешних вызовов API. Как его оптимизировать?»
Хм… Это не про заученный алгоритм и не про архитектуру. Надо размышлять как опытный бэкенд.
1. Без паники, сначала диагностика
Каждый интервьюер ожидает, что вы сразу перейдёте к «кэшированию» или «параллельным вызовам». Но я сказал: «Сначала я проверю, действительно ли внешний API является узким местом». Интервьюер приподнял бровь.
Я пояснил. Добавим трассировку (Open Telemetry) для отслеживания длительности каждого внешнего вызова. Посмотрим задержки для каждой зависимости (иногда медленные наша сериализация или парсинг JSON). Посмотрим на 95-й и 99-й процентили времени отклика, т.к., если 1 из 100 вызовов занимает 5 секунд, этого достаточно, чтобы сервис казался медленным. Проверим закономерности повторных попыток. Если API нестабилен и наш сервис повторяет запрос по три раза, мы утраиваем время отклика.
Цель не в том, чтобы исправить ошибку, а в том, чтобы доказать, что я не исправляю её вслепую. Интервьюер кивнул.
2. Оптимизаций на стороне клиента
Если внешний API действительно медленный, перейдём к улучшениям на клиенте:
- Установим разумные тайм-ауты (2–3 секунды), в зависимости от SLA, и быстрый выход при таймауте.
- Настроим повторные попытки с экспоненциальной задержкой и ограничим количество повторных попыток, например: 1 – через 200 мс, 2 – через 400мс, 3 – через 800мс. Если ответа нет – выходим.
- Добавим «выключатель» (Circuit Breaker). Если API продолжит давать сбои, прервём попытки. Нет смысла долбить сломанную конечную точку.
3. Параллелизм и асинхронные вызовы
Вместо последовательного выполнения нескольких внешних вызовов, распараллелим их, используя асинхронные шаблоны вроде класса Parallel или Task.WhenAll(). Теперь общее время всех запросов будет близко к времени самого медленного, а не к сумме всех.
4. Кэширование
Если данные из внешнего API меняются нечасто, кэшируем их. Можно использовать кэш в памяти, распределённый или гибридный и кэшировать ответы, например, на 10 минут, чтобы кэш достаточно часто обновлялся. Когда API недоступен, можно выдавать слегка устаревшие данные из кэша. Но здесь важно настроить стратегии инвалидации, отслеживать утечки памяти и соотношение попаданий и промахов.
5. Пакетирование и агрегация
Возможно, внешний API поддерживает пакетные запросы, так что можно делать 1 запрос вместо нескольких, например, отправлять несколько идентификаторов в одном запросе. Либо периодически скачивать все данные из API и использовать их локально.
6. Пересмотр архитектуры
Если сервис слишком сильно зависит от внешних API, можно рассмотреть событийно-ориентированную архитектуру. Вместо вызова API в реальном времени обрабатывать данные асинхронно через очереди сообщений (Kafka или RabbitMQ). Так основной поток будет реагировать быстро, а фоновые процессы будут извлекать или обновлять внешние данные позже.
Можно внедрить API Gateway, кэширующий или объединяющий несколько ответов внешнего API. Тогда наш микросервис будет обращаться к одной оптимизированной конечной точке вместо десяти разных.
7. Плавная деградация
Иногда нужно просто корректно завершить работу:
- Установить тайм-аут и возвращать кэшированные/резервные данные.
- Регистрировать инциденты, но не допускать сбоя в работе.
Пользователям всё равно, какой API дал сбой. Им важно, чтобы приложение работало.
8. Мониторинг
Добавим метрики, будем отслеживать задержку по каждой зависимости, оповещать о резком увеличении времени отклика, следить за закономерностями при замедлениях.
После того, как я закончил, интервьюер помолчал. А потом сказал: «Да… как-то так мы и решили эту проблему».
Источник: https://blog.stackademic.com/interviewer-i-optimized-a-laggy-microservice-and-shocked-my-interviewer-heres-what-i-did-562cd8f0dcee
Автор оригинала: Shanvika Devi
👍35
This media is not supported in your browser
VIEW IN TELEGRAM
День 2467. #ЧтоНовенького
Планирование с Copilot в Visual Studio
Быстрые подсказки ИИ отлично подходят для небольших исправлений, но неэффективны для крупных проектов. В итоге приходится переписывать инструкции, постоянно их корректировать и надеяться, что модель останется на верном пути. Это даёт Copilot наглядный, структурированный путь, который обновляется по мере выполнения и позволяет вам контролировать каждый этап.
В режиме агента Copilot теперь может создавать планы, которые исследуют вашу кодовую базу, разбивают крупные задачи на части и выполняют их пошагово, выполняя итерации по ходу выполнения. В результате получается более предсказуемый и прозрачный рабочий процесс, который помогает вам точно понимать, что происходит.
Планирование с Copilot доступно в публичной предварительной версии в Visual Studio 2022 17.14. Оно внедряется постепенно, поэтому после обновления VS может быть как включена, так и выключена. Если она у вас недоступна, вы можете включить её в меню Tools > Options > Copilot > Enable Planning (Инструменты > Параметры > Copilot > Включить планирование).
Как это работает
Планирование использует прозрачные вызовы инструментов для структурированного управления сложными задачами. Когда вы просите Copilot выполнить многоэтапную задачу, он автоматически определяет, следует ли ответить сразу или перейти к планированию. Во втором случае Copilot создаёт markdown-файл, в котором определяет задачу, этапы выполнения и обновляет ход выполнения на каждом этапе. В процессе работы Copilot пересматривает и дорабатывает план, адаптируя его к новому контексту или результатам.
Детали
- План записывается во временный файл (
- Если вы редактируете план во время выполнения ответа, изменения могут вступить в силу не сразу. Остановите ответ, обновите файл или запрос и перезапустите его. Поддержите эту идею, если это для важно развитие возможности редактировать план во время выполнения.
- Прогресс отслеживается непосредственно в файле плана, поэтому вы всегда можете видеть, что выполнено и что предстоит сделать дальше.
Планирование делает Copilot более предсказуемым и последовательным, предоставляя ему структурированный способ анализа вашего проекта. Оно основано на методах, полученных в ходе исследований иерархического и замкнутого цикла планирования, что позволяет Copilot планировать на высоком уровне, выполнять задачи поэтапно и динамически корректировать их по мере получения информации о вашей кодовой базе и проблемах, возникающих в ходе реализации.
Что дальше?
Microsoft активно собирают отзывы и развивают систему планирования Copilot, чтобы она лучше соответствовала вашему рабочему процессу. Это закладывает основу для разработки на основе планирования в Visual Studio. Дальнейшие улучшения будут сосредоточены на более интеллектуальном кэшировании, улучшенном анализе и более глубоком контексте проекта. Ваши отзывы можете оставить в этом треде.
Источник: https://devblogs.microsoft.com/visualstudio/introducing-planning-in-visual-studio-public-preview/
Планирование с Copilot в Visual Studio
Быстрые подсказки ИИ отлично подходят для небольших исправлений, но неэффективны для крупных проектов. В итоге приходится переписывать инструкции, постоянно их корректировать и надеяться, что модель останется на верном пути. Это даёт Copilot наглядный, структурированный путь, который обновляется по мере выполнения и позволяет вам контролировать каждый этап.
В режиме агента Copilot теперь может создавать планы, которые исследуют вашу кодовую базу, разбивают крупные задачи на части и выполняют их пошагово, выполняя итерации по ходу выполнения. В результате получается более предсказуемый и прозрачный рабочий процесс, который помогает вам точно понимать, что происходит.
Планирование с Copilot доступно в публичной предварительной версии в Visual Studio 2022 17.14. Оно внедряется постепенно, поэтому после обновления VS может быть как включена, так и выключена. Если она у вас недоступна, вы можете включить её в меню Tools > Options > Copilot > Enable Planning (Инструменты > Параметры > Copilot > Включить планирование).
Как это работает
Планирование использует прозрачные вызовы инструментов для структурированного управления сложными задачами. Когда вы просите Copilot выполнить многоэтапную задачу, он автоматически определяет, следует ли ответить сразу или перейти к планированию. Во втором случае Copilot создаёт markdown-файл, в котором определяет задачу, этапы выполнения и обновляет ход выполнения на каждом этапе. В процессе работы Copilot пересматривает и дорабатывает план, адаптируя его к новому контексту или результатам.
Детали
- План записывается во временный файл (
%TEMP%\VisualStudio\copilot-vs\). Чтобы использовать его в разных потоках, добавьте его в свой репозиторий. Если вам нужны варианты более долгосрочного хранения планов, поддержите этот запрос.- Если вы редактируете план во время выполнения ответа, изменения могут вступить в силу не сразу. Остановите ответ, обновите файл или запрос и перезапустите его. Поддержите эту идею, если это для важно развитие возможности редактировать план во время выполнения.
- Прогресс отслеживается непосредственно в файле плана, поэтому вы всегда можете видеть, что выполнено и что предстоит сделать дальше.
Планирование делает Copilot более предсказуемым и последовательным, предоставляя ему структурированный способ анализа вашего проекта. Оно основано на методах, полученных в ходе исследований иерархического и замкнутого цикла планирования, что позволяет Copilot планировать на высоком уровне, выполнять задачи поэтапно и динамически корректировать их по мере получения информации о вашей кодовой базе и проблемах, возникающих в ходе реализации.
Что дальше?
Microsoft активно собирают отзывы и развивают систему планирования Copilot, чтобы она лучше соответствовала вашему рабочему процессу. Это закладывает основу для разработки на основе планирования в Visual Studio. Дальнейшие улучшения будут сосредоточены на более интеллектуальном кэшировании, улучшенном анализе и более глубоком контексте проекта. Ваши отзывы можете оставить в этом треде.
Источник: https://devblogs.microsoft.com/visualstudio/introducing-planning-in-visual-studio-public-preview/
👎6👍5
День 2468. #ЗаметкиНаПолях #ЧтоНовенького
LeftJoin и RightJoin в EF LINQ
Если вы когда-либо работали с базами данных, вы знаете о LEFT JOIN (RIGHT JOIN). Они используются довольно часто, но в Entity Framework Core выполнение левого объединения всегда было… довольно замороченным. Приходилось проделывать сложные трюки с GroupJoin и DefaultIfEmpty, что затрудняло чтение и поддержку кода. Но в .NET 10 это наконец-то исправлено с помощью новых методов LeftJoin и RightJoin.
LEFT JOIN возвращает все строки из левой части и соответствующие им строки из правой. Если совпадений нет, правая часть равна null. Это используется, чтобы сохранить «владельцев», даже если у них нет связанных строк (например, показать все товары, даже если у некоторых нет отзывов).
Старый способ
До .NET 10 для левого объединения в LINQ требовалось вызвать GroupJoin, а затем DefaultIfEmpty, чтобы оставить строки без совпадений. Это работало, но смысл был скрыт в шуме.
Вот SQL, который генерируется для этого запроса:
GroupJoin сопоставляет строки, DefaultIfEmpty вставляет значение по умолчанию (NULL) при отсутствии совпадений, поэтому строка из левого множества всё равно отображается. Это слишком многословно для такого распространённого явления, как левое объединение.
Новый подход в EF 10
Теперь мы можем выразить то, что хотим:
Сгенерированный SQL-код идентичен предыдущему примеру.
Здесь очевидна цель: вы видите LeftJoin, вы знаете, чего ожидать. Меньше кода, меньше изменяемых элементов (нет GroupJoin, DefaultIfEmpty, SelectMany). Тот же результат: все товары сохранены, отзывы могут отсутствовать.
RightJoin
RightJoin аналогичен - сохраняет все строки с правой стороны и только соответствующие строки с левой. EF Core преобразует это в RIGHT JOIN. Это удобно, когда необходимо сохранить вторую последовательность.
Итого
Левые объединения нужны довольно часто. Отображение всех пользователей с необязательными настройками профиля. Все товары с необязательными отзывами. Все заказы с необязательными данными о доставке. Теперь это так же просто, как и любой другой метод LINQ.
Несколько советов:
- В проекции защитите сторону, допускающую null:
- Сохраняйте проекции небольшими, чтобы не извлекать больше столбцов, чем необходимо;
- Добавляйте индексы по ключам объединений для улучшения планов запросов.
Источник: https://www.milanjovanovic.tech/blog/whats-new-in-ef-core-10-leftjoin-and-rightjoin-operators-in-linq
LeftJoin и RightJoin в EF LINQ
Если вы когда-либо работали с базами данных, вы знаете о LEFT JOIN (RIGHT JOIN). Они используются довольно часто, но в Entity Framework Core выполнение левого объединения всегда было… довольно замороченным. Приходилось проделывать сложные трюки с GroupJoin и DefaultIfEmpty, что затрудняло чтение и поддержку кода. Но в .NET 10 это наконец-то исправлено с помощью новых методов LeftJoin и RightJoin.
LEFT JOIN возвращает все строки из левой части и соответствующие им строки из правой. Если совпадений нет, правая часть равна null. Это используется, чтобы сохранить «владельцев», даже если у них нет связанных строк (например, показать все товары, даже если у некоторых нет отзывов).
Старый способ
До .NET 10 для левого объединения в LINQ требовалось вызвать GroupJoin, а затем DefaultIfEmpty, чтобы оставить строки без совпадений. Это работало, но смысл был скрыт в шуме.
var query = dbContext.Products
.GroupJoin(
dbContext.Reviews,
prod => prod.Id,
review => review.ProductId,
(prod, reviews) =>
new { prod, subgroup = reviews })
.SelectMany(
j => j.subgroup.DefaultIfEmpty(),
(j, review) => new
{
ProductId = j.prod.Id,
j.prod.Name,
j.prod.Price,
ReviewId = (int?)review!.Id ?? 0,
Rating = (int?)review!.Rating ?? 0,
Comment = review!.Comment ?? "N/A"
})
.OrderBy(x => x.ProductId)
.ThenBy(x => x.ReviewId);
Вот SQL, который генерируется для этого запроса:
SELECT
p."Id" AS "ProductId",
p."Name",
p."Price",
COALESCE(r."Id", 0) AS "ReviewId",
COALESCE(r."Rating", 0) AS "Rating",
COALESCE(r."Comment", 'N/A') AS "Comment"
FROM "Products" AS p
LEFT JOIN "Reviews" AS r ON p."Id" = r."ProductId"
ORDER BY p."Id", COALESCE(r."Id", 0)
GroupJoin сопоставляет строки, DefaultIfEmpty вставляет значение по умолчанию (NULL) при отсутствии совпадений, поэтому строка из левого множества всё равно отображается. Это слишком многословно для такого распространённого явления, как левое объединение.
Новый подход в EF 10
Теперь мы можем выразить то, что хотим:
var query = dbContext.Products
.LeftJoin(
dbContext.Reviews,
prod => prod.Id,
review => review.ProductId,
(prod, review) => new
{
ProductId = prod.Id,
prod.Name,
prod.Price,
ReviewId = (int?)review.Id ?? 0,
Rating = (int?)review.Rating ?? 0,
Comment = review.Comment ?? "N/A"
})
.OrderBy(x => x.ProductId)
.ThenBy(x => x.ReviewId)
Сгенерированный SQL-код идентичен предыдущему примеру.
Здесь очевидна цель: вы видите LeftJoin, вы знаете, чего ожидать. Меньше кода, меньше изменяемых элементов (нет GroupJoin, DefaultIfEmpty, SelectMany). Тот же результат: все товары сохранены, отзывы могут отсутствовать.
RightJoin
RightJoin аналогичен - сохраняет все строки с правой стороны и только соответствующие строки с левой. EF Core преобразует это в RIGHT JOIN. Это удобно, когда необходимо сохранить вторую последовательность.
Итого
Левые объединения нужны довольно часто. Отображение всех пользователей с необязательными настройками профиля. Все товары с необязательными отзывами. Все заказы с необязательными данными о доставке. Теперь это так же просто, как и любой другой метод LINQ.
Несколько советов:
- В проекции защитите сторону, допускающую null:
review.Comment ?? "N/A";- Сохраняйте проекции небольшими, чтобы не извлекать больше столбцов, чем необходимо;
- Добавляйте индексы по ключам объединений для улучшения планов запросов.
Источник: https://www.milanjovanovic.tech/blog/whats-new-in-ef-core-10-leftjoin-and-rightjoin-operators-in-linq
52👍32
День 2469. #ЗаметкиНаПолях
Сброс Кэша на Диск в Windows
При работе с файловыми системами Windows понимание принципов работы дискового кэша важно для обеспечения целостности данных и оптимизации производительности. Дисковое кэширование повышает производительность системы за счёт временного хранения операций записи в памяти перед их сохранением на диск. Однако это также означает, что в случае отключения питания или сбоя системы данные могут быть потеряны, если они не были сброшены из кэша в постоянное хранилище.
Если дисковое кэширование включено, Windows сохраняет запросы записи в энергозависимой памяти (RAM) вместо немедленной записи на диск. Это обеспечивает повышение производительности, но влечет за собой риски:
- Преимущества: более высокая производительность приложений, снижение нагрузки на дисковый ввод-вывод.
- Риски: возможная потеря данных при сбоях питания или системных сбоях.
Вы можете управлять кэшированием записи на уровне тома через диспетчер устройств или управление дисками, но в большинстве случаев, когда целостность данных критически важна, требуется программно сбрасывать кэш на диск.
Для приложений .NET, простейший способ сделать это - вызвать метод
Параметр
Предотвращение кэширования при небуферизованном вводе-выводе
Если нужно полностью избежать кэширования, можно использовать флаг
Обратите внимание, что этот вариант может работать медленнее, чем использование буферизованного ввода-вывода с
Источник: https://www.meziantou.net/flushing-disk-caches-on-windows-a-comprehensive-guide.htm
Сброс Кэша на Диск в Windows
При работе с файловыми системами Windows понимание принципов работы дискового кэша важно для обеспечения целостности данных и оптимизации производительности. Дисковое кэширование повышает производительность системы за счёт временного хранения операций записи в памяти перед их сохранением на диск. Однако это также означает, что в случае отключения питания или сбоя системы данные могут быть потеряны, если они не были сброшены из кэша в постоянное хранилище.
Если дисковое кэширование включено, Windows сохраняет запросы записи в энергозависимой памяти (RAM) вместо немедленной записи на диск. Это обеспечивает повышение производительности, но влечет за собой риски:
- Преимущества: более высокая производительность приложений, снижение нагрузки на дисковый ввод-вывод.
- Риски: возможная потеря данных при сбоях питания или системных сбоях.
Вы можете управлять кэшированием записи на уровне тома через диспетчер устройств или управление дисками, но в большинстве случаев, когда целостность данных критически важна, требуется программно сбрасывать кэш на диск.
Для приложений .NET, простейший способ сделать это - вызвать метод
FileStream.Flush(true):using System.Text.Encoding;
using var fs =
new FileStream(@"C:\data.txt", FileMode.Create);
var data = UTF8.GetBytes("Важные данные");
fs.Write(data);
// Сброс в операционную систему
fs.Flush();
// Сброс на диск (обходит буферы ОС)
fs.Flush(flushToDisk: true);
Параметр
flushToDisk: true гарантирует, что данные будут записаны в обход любых промежуточных буферов непосредственно на устройство хранения.Предотвращение кэширования при небуферизованном вводе-выводе
Если нужно полностью избежать кэширования, можно использовать флаг
FileOptions.WriteThrough при создании файла:await using var fs =
new FileStream(path,
FileMode.Create,
FileAccess.Write,
FileShare.None,
4096,
FileOptions.WriteThrough);
Обратите внимание, что этот вариант может работать медленнее, чем использование буферизованного ввода-вывода с
Flush(flushToDisk: true). В большинстве случаев вы можете произвести запись в файл несколько раз без сброса, а затем выполнить один сброс, чтобы гарантировать запись всех данных на диск.Источник: https://www.meziantou.net/flushing-disk-caches-on-windows-a-comprehensive-guide.htm
👍12
День 2470. #ЗаметкиНаПолях
Вы Всё Ещё Используете new Random()? Начало
Распространённое в сети мнение об использовании Random: new Random() — зло!
Что new Random() использует системные часы для посева, и, если несколько экземпляров создаются в течение короткого промежутка времени, они генерируют идентичные результаты. Т.е. якобы:
Выведет: 42 42 42 42 42
В то время как:
Выведет: 17 82 19 23 5
Откуда это взялось?
В .NET Framework new Random полагался на DateTime.Now.Ticks, который менялся примерно каждые 16 мс. Таким образом, в течение 16 миллисекунд вы получали одно и то же начальное значение посева, что каждый раз приводило к одному и тому же результату Next(…). Но это не так со времён .NET Core. Можете выполнить код выше и убедиться.
А что насчёт потокобезопасности?
Наивный пользователь Random, которому нужен один общий генератор случайных чисел, может поддаться искушению сделать что-то вроде следующего:
Несмотря на то, что этот код не является потокобезопасным, он, скорее всего, не выявит никаких проблем. До случая…
Random по умолчанию возвращает 0 при обнаружении проблемы потокобезопасности, поэтому следующий код выведет количество обнаруженных проблем:
Здесь уже интереснее. В .NET5 этот код показывает больше 9000 нулей в разных потоках, т.е. подавляющая часть вызовов Next сталкивается с проблемой потокобезопасности. В .NET8+ этого уже не происходит (по крайней мере, массово), однако, если предоставить посев при создании объекта Random, эта проблема вернётся:
Random.Shared.Next() использует потокобезопасный экземпляр (один на поток) через атрибут [ThreadStatic]. Таким образом, если вы используете последние версии .NET и вам не нужно предоставлять определённый посев, можно использовать Random.Shared (но и с new Random() скорее всего проблем не возникнет).
А что делать в .NET Framework? Об этом далее.
Окончание следует…
Источники:
- https://steven-giesel.com/blogPost/cc2a3f52-9d35-43d2-9687-c276a2de8e07/linkedin-are-you-still-using-new-random-everywhere
- https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/
Вы Всё Ещё Используете new Random()? Начало
Распространённое в сети мнение об использовании Random: new Random() — зло!
Что new Random() использует системные часы для посева, и, если несколько экземпляров создаются в течение короткого промежутка времени, они генерируют идентичные результаты. Т.е. якобы:
for(var i = 0; i < 5; i++)
{
var random = new Random();
Console.WriteLine(random.Next(1,100));
}
Выведет: 42 42 42 42 42
В то время как:
for(var i = 0; i < 5; i++)
Console.WriteLine(Random.Shared.Next(1,100));
Выведет: 17 82 19 23 5
Откуда это взялось?
В .NET Framework new Random полагался на DateTime.Now.Ticks, который менялся примерно каждые 16 мс. Таким образом, в течение 16 миллисекунд вы получали одно и то же начальное значение посева, что каждый раз приводило к одному и тому же результату Next(…). Но это не так со времён .NET Core. Можете выполнить код выше и убедиться.
А что насчёт потокобезопасности?
Наивный пользователь Random, которому нужен один общий генератор случайных чисел, может поддаться искушению сделать что-то вроде следующего:
Random rng = new();
Parallel.For(0, 10, x =>
{
var value = rng.Next();
Console.WriteLine(value);
});
Несмотря на то, что этот код не является потокобезопасным, он, скорее всего, не выявит никаких проблем. До случая…
Random по умолчанию возвращает 0 при обнаружении проблемы потокобезопасности, поэтому следующий код выведет количество обнаруженных проблем:
Random rng = new();
Parallel.For(0, 10, x =>
{
var nums = new int[10_000];
for (int i = 0; i < nums.Length; ++i)
nums[i] = rng.Next();
Console.WriteLine(
$"{nums.Count(x => x == 0)} нулей");
});
Здесь уже интереснее. В .NET5 этот код показывает больше 9000 нулей в разных потоках, т.е. подавляющая часть вызовов Next сталкивается с проблемой потокобезопасности. В .NET8+ этого уже не происходит (по крайней мере, массово), однако, если предоставить посев при создании объекта Random, эта проблема вернётся:
Random rng = new(123);
Random.Shared.Next() использует потокобезопасный экземпляр (один на поток) через атрибут [ThreadStatic]. Таким образом, если вы используете последние версии .NET и вам не нужно предоставлять определённый посев, можно использовать Random.Shared (но и с new Random() скорее всего проблем не возникнет).
А что делать в .NET Framework? Об этом далее.
Окончание следует…
Источники:
- https://steven-giesel.com/blogPost/cc2a3f52-9d35-43d2-9687-c276a2de8e07/linkedin-are-you-still-using-new-random-everywhere
- https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/
👍29
День 2471. #ЗаметкиНаПолях
Вы Всё Ещё Используете new Random()? Окончание
Начало
Проблема с Random в .NET Framework
Основных проблем с Random в .NET Framework две:
1. При вызове new Random() в .NET Framework он использует системные часы для посева;
2. Системные часы имеют конечное разрешение.
Т.е. при быстром последовательном вызове new Random() в .NET Framework могут возникнуть два экземпляра Random с одинаковым начальным значением, что означает, что экземпляры Random вернут идентичную последовательность чисел:
При выполнении этого кода несколько чисел (а то и все) могут быть одинаковыми, а это не то, чего мы хотим.
Решение
Создать один экземпляр Random, который используется исключительно для предоставления начальных значений для остальных экземпляров Random. Поскольку начальные значения случайны, экземпляры Random не коррелируют, и вы можете полностью избежать описанной выше проблемы. Этот подход к инициализации используется по умолчанию в .NET Core, поэтому проблема возникает только в .NET Framework.
Мы можем реализовать тот же подход в классе-обёртке ThreadSafeRandom, показанном ниже. Этот класс использует [ThreadStatic], избегая проблем с инициализацией в .NET Framework:
Эту потокобезопасную реализацию, можно использовать во всех версиях фреймворка:
Если вы используете .NET6+, рекомендуется использовать встроенный Random.Shared. В более ранних версиях вы можете использовать приведённый выше ThreadSafeRandom для решения этих проблем. Если вы ориентируетесь как на .NET6+, так и на .NET Framework, можно использовать директивы #if для разделения реализации в зависимости от целевой среды.
Источник: https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/
Вы Всё Ещё Используете new Random()? Окончание
Начало
Проблема с Random в .NET Framework
Основных проблем с Random в .NET Framework две:
1. При вызове new Random() в .NET Framework он использует системные часы для посева;
2. Системные часы имеют конечное разрешение.
Т.е. при быстром последовательном вызове new Random() в .NET Framework могут возникнуть два экземпляра Random с одинаковым начальным значением, что означает, что экземпляры Random вернут идентичную последовательность чисел:
// <TargetFramework>net48</TargetFramework>
Parallel.For(0, 10, x =>
{
Random rnd = new();
var value = rnd.Next();
Console.WriteLine(value);
});
При выполнении этого кода несколько чисел (а то и все) могут быть одинаковыми, а это не то, чего мы хотим.
Решение
Создать один экземпляр Random, который используется исключительно для предоставления начальных значений для остальных экземпляров Random. Поскольку начальные значения случайны, экземпляры Random не коррелируют, и вы можете полностью избежать описанной выше проблемы. Этот подход к инициализации используется по умолчанию в .NET Core, поэтому проблема возникает только в .NET Framework.
Мы можем реализовать тот же подход в классе-обёртке ThreadSafeRandom, показанном ниже. Этот класс использует [ThreadStatic], избегая проблем с инициализацией в .NET Framework:
internal static class ThreadSafeRandom
{
[ThreadStatic]
private static Random? _local;
private static readonly
Random Global = new Random();
private static Random Instance
{
get
{
if (_local is null)
{
int seed;
// избегаем конкурентного доступа
lock (Global)
{
seed = Global.Next();
}
_local = new Random(seed);
}
return _local;
}
}
public static int Next() => Instance.Next();
}
Эту потокобезопасную реализацию, можно использовать во всех версиях фреймворка:
Parallel.For(0, 10, x =>
{
var value = ThreadSafeRandom.Next();
Console.WriteLine(value);
});
Если вы используете .NET6+, рекомендуется использовать встроенный Random.Shared. В более ранних версиях вы можете использовать приведённый выше ThreadSafeRandom для решения этих проблем. Если вы ориентируетесь как на .NET6+, так и на .NET Framework, можно использовать директивы #if для разделения реализации в зависимости от целевой среды.
Источник: https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/
👍11
День 2472. #ЗаметкиНаПолях
AsNoTrackingWithIdentityResolution в EF
При работе с Entity Framework Core понимание поведения отслеживания изменений критически важно как для производительности, так и для согласованности данных. Про AsNoTracking() знают все, а вот его мощная альтернатива AsNoTrackingWithIdentityResolution() не так популярна.
Что такое AsNoTracking()?
По умолчанию EF Core отслеживает все сущности, возвращаемые запросами в трекере изменений. Отслеживание обеспечивает:
- автоматическое обнаружение изменений в сущностях;
- операции обновления без явного присоединения сущностей;
- разрешение идентификаторов (гарантируя наличие только одного экземпляра каждой сущности в памяти).
Однако отслеживание сопряжено с накладными расходами. При выполнении операций только чтения, когда не требуется обновлять данные, AsNoTracking() повышает производительность, полностью отменяя отслеживание изменений:
Проблема - дублирование экземпляров сущностей. Если трекер изменений не будет выполнять разрешение идентификаторов, в памяти может оказаться несколько экземпляров одной и той же сущности, например:
Если несколько заказов принадлежат одному и тому же клиенту, вы получите отдельные экземпляры Customer для каждого заказа, даже если они представляют одну и ту же запись в БД. Это означает:
- увеличение потребления памяти,
- возможность путаницы при сравнении сущностей,
- потерю ссылочной целостности в графе объектов.
AsNoTrackingWithIdentityResolution()
AsNoTrackingWithIdentityResolution() решает эту проблему, объединяя преимущества двух подходов:
- отсутствие отслеживания изменений (выигрыш в производительности),
- разрешение идентификаторов (гарантия единственности экземпляров сущностей).
При этом EF Core:
- выполняет запрос без присоединения сущностей к трекеру изменений;
- сохраняет временную карту идентификации во время материализации запроса;
- гарантирует, что сущности с одинаковым ключом ссылаются на один и тот же экземпляр;
- удаляет карту идентификации после завершения запроса.
Т.е. что вы получаете разрешение идентификации во время выполнения запроса без дополнительных затрат на отслеживание изменений.
Также можно установить AsNoTrackingWithIdentityResolution() как поведение трекера по умолчанию:
Когда использовать
1. Отслеживание по умолчанию:
- необходимо обновлять, удалять или отслеживать изменения сущностей;
- небольшие наборы результатов, где затраты на отслеживание незначительны;
- необходимо автоматическое обнаружение изменений.
2. AsNoTracking:
- операции только чтения;
- нет свойств навигации или связанных сущностей;
- максимальная производительность критически важна;
- результаты не содержат дублирующихся сущностей.
3. AsNoTrackingWithIdentityResolution:
- операции только чтения с include/join;
- требуется ссылочная целостность в графе объектов;
- запросы, возвращающие одну сущность несколько раз;
- требуется сравнивать сущности по ссылке.
Источник: https://bartwullems.blogspot.com/2025/10/understanding-asnotrackingwithidentityr.html
AsNoTrackingWithIdentityResolution в EF
При работе с Entity Framework Core понимание поведения отслеживания изменений критически важно как для производительности, так и для согласованности данных. Про AsNoTracking() знают все, а вот его мощная альтернатива AsNoTrackingWithIdentityResolution() не так популярна.
Что такое AsNoTracking()?
По умолчанию EF Core отслеживает все сущности, возвращаемые запросами в трекере изменений. Отслеживание обеспечивает:
- автоматическое обнаружение изменений в сущностях;
- операции обновления без явного присоединения сущностей;
- разрешение идентификаторов (гарантируя наличие только одного экземпляра каждой сущности в памяти).
Однако отслеживание сопряжено с накладными расходами. При выполнении операций только чтения, когда не требуется обновлять данные, AsNoTracking() повышает производительность, полностью отменяя отслеживание изменений:
var customers = await context.Customers
.AsNoTracking()
.ToListAsync();
Проблема - дублирование экземпляров сущностей. Если трекер изменений не будет выполнять разрешение идентификаторов, в памяти может оказаться несколько экземпляров одной и той же сущности, например:
var orders = await context.Orders
.AsNoTracking()
.Include(o => o.Customer)
.Where(o => o.OrderDate > DateTime.Now.AddDays(-30))
.ToListAsync();
Если несколько заказов принадлежат одному и тому же клиенту, вы получите отдельные экземпляры Customer для каждого заказа, даже если они представляют одну и ту же запись в БД. Это означает:
- увеличение потребления памяти,
- возможность путаницы при сравнении сущностей,
- потерю ссылочной целостности в графе объектов.
AsNoTrackingWithIdentityResolution()
AsNoTrackingWithIdentityResolution() решает эту проблему, объединяя преимущества двух подходов:
- отсутствие отслеживания изменений (выигрыш в производительности),
- разрешение идентификаторов (гарантия единственности экземпляров сущностей).
var orders = await context.Orders
.AsNoTrackingWithIdentityResolution()
.Include(o => o.Customer)
.Where(o => o.OrderDate > DateTime.Now.AddDays(-30))
.ToListAsync();
// Теперь все заказы от одного клиента
// будут ссылаться на один экземпляр Customer в памяти
При этом EF Core:
- выполняет запрос без присоединения сущностей к трекеру изменений;
- сохраняет временную карту идентификации во время материализации запроса;
- гарантирует, что сущности с одинаковым ключом ссылаются на один и тот же экземпляр;
- удаляет карту идентификации после завершения запроса.
Т.е. что вы получаете разрешение идентификации во время выполнения запроса без дополнительных затрат на отслеживание изменений.
Также можно установить AsNoTrackingWithIdentityResolution() как поведение трекера по умолчанию:
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(
QueryTrackingBehavior.NoTrackingWithIdentityResolution);
}
Когда использовать
1. Отслеживание по умолчанию:
- необходимо обновлять, удалять или отслеживать изменения сущностей;
- небольшие наборы результатов, где затраты на отслеживание незначительны;
- необходимо автоматическое обнаружение изменений.
2. AsNoTracking:
- операции только чтения;
- нет свойств навигации или связанных сущностей;
- максимальная производительность критически важна;
- результаты не содержат дублирующихся сущностей.
3. AsNoTrackingWithIdentityResolution:
- операции только чтения с include/join;
- требуется ссылочная целостность в графе объектов;
- запросы, возвращающие одну сущность несколько раз;
- требуется сравнивать сущности по ссылке.
Источник: https://bartwullems.blogspot.com/2025/10/understanding-asnotrackingwithidentityr.html
👍41
День 2473. #ВопросыНаСобеседовании
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы). Я решил разобрать их тут. Начнём с 4го, поскольку первые 3 приведены в его книге, обзор на которую я недавно выкладывал.
7. Делегаты и события
«Можете ли вы объяснить разницу между делегатами и событиями в C# и как они используются для реализации паттерна Наблюдатель? Приведите пример сценария, в котором вы бы их использовали».
Хороший ответ
«В C# делегаты — это объекты, ссылающиеся на методы. Это типобезопасные указатели на функции, позволяющие передавать методы в качестве параметров. Делегаты используются для определения методов обратного вызова и реализации обработки событий в .NET. Событие, с другой стороны, — это механизм, который использует делегаты для предоставления уведомлений о важных событиях в системе. События, по сути, являются обёртками для делегатов, добавляющими уровень защиты; они могут быть вызваны только из класса, в котором они объявлены, что предотвращает прямую активацию события внешними объектами.
Паттерн Наблюдатель реализуется с помощью делегатов и событий, позволяя подписчикам регистрироваться у поставщика событий. При существенном изменении условия или состояния генерируется событие, и подписанные наблюдатели получают уведомления.
Например, в UI-приложении можно использовать событие для уведомления компонентов UI об изменении модели данных. Можно определить делегат, определяющий сигнатуру методов, которые могут реагировать на событие, и событие, основанное на этом делегате. Компоненты UI, которым необходимо обновляться при изменении модели, могут подписаться на это событие и реагировать соответствующим образом.
Этот подход отделяет отправителя события от получателей, поскольку отправителю не нужно знать ничего о методах, которые будут обрабатывать событие. Он обеспечивает эффективное взаимодействие компонентов без тесной связи.»
Часто встречающийся некорректный ответ
«Делегаты и события в C# — это одно и то же; они используются для указания методов, которые можно вызвать позже. Я использую их взаимозаменяемо в своих приложениях для запуска методов из других классов».
Этот ответ демонстрирует путаницу между делегатами и событиями и упускает из виду ключевые аспекты их использования:
- Путаница между делегатами и событиями: В этом ответе не проводится должного различия между делегатами и событиями. Хотя оба метода действительно используют указатели на методы, события — это особый вид применения делегатов, предназначенный для обеспечения инкапсуляции и предоставления стандартного шаблона обработки событий. События защищают список вызовов делегата, гарантируя, что только класс-владелец может вызвать событие.
- Неправильное использование делегатов и событий: Заявление об их взаимозаменяемости упускает из виду цель событий — инкапсуляцию экземпляра делегата и безопасное управление списками подписок. Такое неправильное использование может привести к неудачному выбору архитектуры, когда делегаты могут быть доступны публично, что может поставить под угрозу целостность и безопасность приложения.
- Недостаточное понимание инкапсуляции и слабой связанности: правильное использование событий критически важно для поддержания слабой связанности между компонентами приложения, что является ключевым преимуществом, которое не учитывается в этом ответе.
Эта ошибка обычно возникает из-за поверхностного понимания модели делегатов и событий в C# и указывает на необходимость более глубокого изучения основополагающих концепций .NET в области событийно-управляемого программирования.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
Марк Прайс предложил свой набор из 60 вопросов (как технических, так и на софт-скилы). Я решил разобрать их тут. Начнём с 4го, поскольку первые 3 приведены в его книге, обзор на которую я недавно выкладывал.
7. Делегаты и события
«Можете ли вы объяснить разницу между делегатами и событиями в C# и как они используются для реализации паттерна Наблюдатель? Приведите пример сценария, в котором вы бы их использовали».
Хороший ответ
«В C# делегаты — это объекты, ссылающиеся на методы. Это типобезопасные указатели на функции, позволяющие передавать методы в качестве параметров. Делегаты используются для определения методов обратного вызова и реализации обработки событий в .NET. Событие, с другой стороны, — это механизм, который использует делегаты для предоставления уведомлений о важных событиях в системе. События, по сути, являются обёртками для делегатов, добавляющими уровень защиты; они могут быть вызваны только из класса, в котором они объявлены, что предотвращает прямую активацию события внешними объектами.
Паттерн Наблюдатель реализуется с помощью делегатов и событий, позволяя подписчикам регистрироваться у поставщика событий. При существенном изменении условия или состояния генерируется событие, и подписанные наблюдатели получают уведомления.
Например, в UI-приложении можно использовать событие для уведомления компонентов UI об изменении модели данных. Можно определить делегат, определяющий сигнатуру методов, которые могут реагировать на событие, и событие, основанное на этом делегате. Компоненты UI, которым необходимо обновляться при изменении модели, могут подписаться на это событие и реагировать соответствующим образом.
Этот подход отделяет отправителя события от получателей, поскольку отправителю не нужно знать ничего о методах, которые будут обрабатывать событие. Он обеспечивает эффективное взаимодействие компонентов без тесной связи.»
Часто встречающийся некорректный ответ
«Делегаты и события в C# — это одно и то же; они используются для указания методов, которые можно вызвать позже. Я использую их взаимозаменяемо в своих приложениях для запуска методов из других классов».
Этот ответ демонстрирует путаницу между делегатами и событиями и упускает из виду ключевые аспекты их использования:
- Путаница между делегатами и событиями: В этом ответе не проводится должного различия между делегатами и событиями. Хотя оба метода действительно используют указатели на методы, события — это особый вид применения делегатов, предназначенный для обеспечения инкапсуляции и предоставления стандартного шаблона обработки событий. События защищают список вызовов делегата, гарантируя, что только класс-владелец может вызвать событие.
- Неправильное использование делегатов и событий: Заявление об их взаимозаменяемости упускает из виду цель событий — инкапсуляцию экземпляра делегата и безопасное управление списками подписок. Такое неправильное использование может привести к неудачному выбору архитектуры, когда делегаты могут быть доступны публично, что может поставить под угрозу целостность и безопасность приложения.
- Недостаточное понимание инкапсуляции и слабой связанности: правильное использование событий критически важно для поддержания слабой связанности между компонентами приложения, что является ключевым преимуществом, которое не учитывается в этом ответе.
Эта ошибка обычно возникает из-за поверхностного понимания модели делегатов и событий в C# и указывает на необходимость более глубокого изучения основополагающих концепций .NET в области событийно-управляемого программирования.
Источник: https://github.com/markjprice/tools-skills-net8/blob/main/docs/interview-qa/readme.md
1👍21
День 2474. #ЗаметкиНаПолях #Git
Использование Условных Включений Git для Нескольких Конфигураций
При работе с несколькими репозиториями Git часто требуются разные настройки для каждого контекста. Например, вы можете использовать личную электронную почту для пет-проектов и рабочую — для корпоративных. Хотя Git можно настроить глобально или для каждого репозитория, ручное управление этими настройками становится утомительным и подверженным ошибкам.
Функция условных включений Git решает эту проблему, автоматически применяя различные конфигурации на основе таких критериев, как расположение репозитория, удалённый URL или имя ветки. Это гарантирует, что правильные настройки всегда будут использоваться без ручного вмешательства.
Вот практический пример применения определённой конфигурации при работе с вашими личными репозиториями GitHub:
Когда Git обнаруживает удалённый URL, соответствующий этому шаблону, он автоматически добавляет настройки из файла .gitconfig-github-personal, который может содержать ваш личный email и ключ подписи.
Git поддерживает несколько типов условий для условных включений, каждый из которых предназначен для разных случаев использования.
1.
Условие gitdir сопоставляет репозитории на основе пути к их файловой системе. В Unix-подобных системах оно чувствительно к регистру.
Это применит .gitconfig-personal ко всем репозиториям в ~/personal/ и .gitconfig-work – к репозиториям в ~/work/.
2.
Условие onbranch применяет конфигурацию на основе ветки, для которой сделан checkout:
Это полезно для применения различных настроек при работе с ветками production и development.
3.
Условие hasconfig проверяет существование определённого значения конфигурации и его соответствие шаблону. Это особенно полезно для сопоставления удалённых URL:
Это позволяет применять различные конфигурации в зависимости от того, где размещён ваш репозиторий, независимо от его локального пути.
Источник: https://www.meziantou.net/using-git-conditional-includes-for-multiple-configurations.htm
Использование Условных Включений Git для Нескольких Конфигураций
При работе с несколькими репозиториями Git часто требуются разные настройки для каждого контекста. Например, вы можете использовать личную электронную почту для пет-проектов и рабочую — для корпоративных. Хотя Git можно настроить глобально или для каждого репозитория, ручное управление этими настройками становится утомительным и подверженным ошибкам.
Функция условных включений Git решает эту проблему, автоматически применяя различные конфигурации на основе таких критериев, как расположение репозитория, удалённый URL или имя ветки. Это гарантирует, что правильные настройки всегда будут использоваться без ручного вмешательства.
Вот практический пример применения определённой конфигурации при работе с вашими личными репозиториями GitHub:
[includeIf "hasconfig:remote.*.url:https://github.com/myname/*"]
path = .gitconfig-github-personal
Когда Git обнаруживает удалённый URL, соответствующий этому шаблону, он автоматически добавляет настройки из файла .gitconfig-github-personal, который может содержать ваш личный email и ключ подписи.
Git поддерживает несколько типов условий для условных включений, каждый из которых предназначен для разных случаев использования.
1.
gitdir — Сопоставление по пути к репозиториюУсловие gitdir сопоставляет репозитории на основе пути к их файловой системе. В Unix-подобных системах оно чувствительно к регистру.
[includeIf "gitdir:~/personal/"]
path = .gitconfig-personal
[includeIf "gitdir:~/work/"]
path = .gitconfig-work
Это применит .gitconfig-personal ко всем репозиториям в ~/personal/ и .gitconfig-work – к репозиториям в ~/work/.
gitdir/i аналогичен для нечувствительного к регистру сопоставления.2.
onbranch – Сопоставление по текущей веткеУсловие onbranch применяет конфигурацию на основе ветки, для которой сделан checkout:
[includeIf "onbranch:main"]
path = .gitconfig-production
[includeIf "onbranch:feature/**"]
path = .gitconfig-development
Это полезно для применения различных настроек при работе с ветками production и development.
3.
hasconfig — Сопоставление с существующей конфигурациейУсловие hasconfig проверяет существование определённого значения конфигурации и его соответствие шаблону. Это особенно полезно для сопоставления удалённых URL:
[includeIf "hasconfig:remote.*.url:https://github.com/myname/*"]
path = .gitconfig-github-personal
[includeIf "hasconfig:remote.*.url:[email protected]:*/**"]
path = .gitconfig-company
Это позволяет применять различные конфигурации в зависимости от того, где размещён ваш репозиторий, независимо от его локального пути.
Источник: https://www.meziantou.net/using-git-conditional-includes-for-multiple-configurations.htm
👍23
День 2475. #SystemDesign101
Типы Серверов в Современных Системах
Источник: https://blog.bytebytego.com
Типы Серверов в Современных Системах
Источник: https://blog.bytebytego.com
👍15👎1
День 2476. #ЗаметкиНаПолях
Больше Никаких Моков ILogger'a
Имитация зависимостей в процессе разработки может быть сложной задачей, особенно если вы не являетесь владельцем интерфейса, а взаимодействие с интерфейсом в коде осуществляется через методы расширения. Так происходит с ILogger из базового пакета Microsoft. Интерфейс ILogger относительно прост, однако вы редко взаимодействуете с ним напрямую. Как же проверить, что был вызван правильный метод и параметры, чтобы гарантировать корректность ведения журнала?
Можно использовать мок ILogger<> с помощью Moq и внедрить его в тестируемую систему, а затем проверять, вызывается ли он. Однако это может быть затруднительно, и также потребуется копировать код проверки мока. Это неидеально.
FakeLogger предоставляет полный доступ ко всем данным, которые вызываются при использовании ILogger в коде, и позволяет проверять их значения в тестах.
Начало работы
С момента создания он немного изменился. Сейчас его можно найти в пакете Microsoft.Extensions.Diagnostics.Testing. После добавления NuGet-пакета, его можно использовать в модульных тестах. Рассмотрим простой сервис в качестве тестируемой системы:
В сервисе выше мы хотим проверить, что при выполнении метода LogMe вызывается соответствующий метод логирования.
Начнём с создания экземпляра тестируемой системы, а для этого необходимо создать экземпляр FakeLogger:
Т.к. FakeLogger<> реализует нужный интерфейс, мы можем использовать его для создания нашего DemoService.
После выполнения блока Act https://t.iss.one/NetDeveloperDiary/1003 теста мы можем проверить результаты:
Самый простой способ сделать это — посмотреть свойство LatestRecord. Это выведет последний вызов экземпляра логгера в тестируемой системе, и это всё, что нужно для базового использования!
Итого
FakeLogger удобно использовать при тестировании системы, требующей реализации ILogger. Он позволяет тестировать корректные уровни логирования, сообщения и структурированные данные, указанные в системных сообщениях логгера.
Источник: https://adamstorr.co.uk/blog/no-more-mocking-ilogger/
Больше Никаких Моков ILogger'a
Имитация зависимостей в процессе разработки может быть сложной задачей, особенно если вы не являетесь владельцем интерфейса, а взаимодействие с интерфейсом в коде осуществляется через методы расширения. Так происходит с ILogger из базового пакета Microsoft. Интерфейс ILogger относительно прост, однако вы редко взаимодействуете с ним напрямую. Как же проверить, что был вызван правильный метод и параметры, чтобы гарантировать корректность ведения журнала?
Можно использовать мок ILogger<> с помощью Moq и внедрить его в тестируемую систему, а затем проверять, вызывается ли он. Однако это может быть затруднительно, и также потребуется копировать код проверки мока. Это неидеально.
FakeLogger предоставляет полный доступ ко всем данным, которые вызываются при использовании ILogger в коде, и позволяет проверять их значения в тестах.
Начало работы
С момента создания он немного изменился. Сейчас его можно найти в пакете Microsoft.Extensions.Diagnostics.Testing. После добавления NuGet-пакета, его можно использовать в модульных тестах. Рассмотрим простой сервис в качестве тестируемой системы:
public class DemoService(ILogger<DemoService> logger)
{
public void LogMe()
{
logger.LogInformation("Пишем в лог.");
}
}
В сервисе выше мы хотим проверить, что при выполнении метода LogMe вызывается соответствующий метод логирования.
Начнём с создания экземпляра тестируемой системы, а для этого необходимо создать экземпляр FakeLogger:
var fakeLogger = new FakeLogger<DemoService>();
var sut = new DemoService(fakeLogger);
Т.к. FakeLogger<> реализует нужный интерфейс, мы можем использовать его для создания нашего DemoService.
После выполнения блока Act https://t.iss.one/NetDeveloperDiary/1003 теста мы можем проверить результаты:
fakeLogger
.LatestRecord
.Message
.Should()
.Be("Пишем в лог.");
Самый простой способ сделать это — посмотреть свойство LatestRecord. Это выведет последний вызов экземпляра логгера в тестируемой системе, и это всё, что нужно для базового использования!
Итого
FakeLogger удобно использовать при тестировании системы, требующей реализации ILogger. Он позволяет тестировать корректные уровни логирования, сообщения и структурированные данные, указанные в системных сообщениях логгера.
Источник: https://adamstorr.co.uk/blog/no-more-mocking-ilogger/
👍34
День 2477. #ЗаметкиНаПолях
Тестирование с Использованием FakeLogger
Вместо того, чтобы заниматься сложными настройками моков или проверять вызовы вручную, FakeLogger собирает сообщения журнала, области действия и структурированные данные в памяти, делая утверждения простыми и выразительными. Сегодня рассмотрим, как тестировать области логирования с помощью FakeLogger, зачем нужны области и как использовать их в коде.
Зачем использовать области действия?
Область логирования в .NET позволяет прикреплять контекстную информацию ко всем записям журнала в блоке кода. Представьте себе облегчённый набор пар «ключ-значение», который дополняет сообщения журнала, не заставляя вас предоставлять одни и те же поля в каждую запись журнала.
Например, вы можете добавить идентификатор запроса, идентификатор корреляции или идентификатор сущности в область действия, тем самым предоставляя в каждую запись журнала в этом блоке одинаковые контекстные метаданные. Это значительно упрощает фильтрацию и анализ журналов в распределённых системах или при отладке конкретных запросов.
Использование FakeLogger с областями действия
Начнём с простого метода, использующего область действия журналирования:
Здесь вызов BeginScope создаёт контекст логирования с единственным ключом (id). При вызове lgr.LogInformation область действия автоматически включает эти контекстные данные. Но как это проверить в тесте?
FakeLogger отслеживает каждую запись журнала, включая шаблоны сообщений, уровни ведения журнала, исключения и области действия. Проверим, правильно ли DemoService пишет в лог, включая id:
В этом тесте мы:
- Создаём экземпляр тестируемой системы (SUT), используя FakeLogger<DemoService>.
- Вызываем метод LogMe(), генерирующий одну запись журнала.
- Проверяем содержимое сообщения и проверяем, записана ли коллекция из области действия.
Каждая область действия регистрируется как объект, и в данном случае это Dictionary<string, object>, содержащий id. Этот подход понятен, выразителен и поддерживается фреймворком, устраняя необходимость в пользовательских оболочках для логирования или ненадёжных настройках верификации моков.
Недостаток в том, что вам, вероятно, потребуется знать, как использовалась область действия в коде, чтобы тесты проверяли то, что вам нужно, однако в данном контексте это приемлемо.
Источник: https://adamstorr.co.uk/blog/testing-with-fakelogger-scopes/
Тестирование с Использованием FakeLogger
Вместо того, чтобы заниматься сложными настройками моков или проверять вызовы вручную, FakeLogger собирает сообщения журнала, области действия и структурированные данные в памяти, делая утверждения простыми и выразительными. Сегодня рассмотрим, как тестировать области логирования с помощью FakeLogger, зачем нужны области и как использовать их в коде.
Зачем использовать области действия?
Область логирования в .NET позволяет прикреплять контекстную информацию ко всем записям журнала в блоке кода. Представьте себе облегчённый набор пар «ключ-значение», который дополняет сообщения журнала, не заставляя вас предоставлять одни и те же поля в каждую запись журнала.
Например, вы можете добавить идентификатор запроса, идентификатор корреляции или идентификатор сущности в область действия, тем самым предоставляя в каждую запись журнала в этом блоке одинаковые контекстные метаданные. Это значительно упрощает фильтрацию и анализ журналов в распределённых системах или при отладке конкретных запросов.
Использование FakeLogger с областями действия
Начнём с простого метода, использующего область действия журналирования:
public partial class
DemoService(ILogger<DemoService> lgr)
{
public void LogMe(Guid id)
{
using var _ =
lgr.BeginScope(
new Dictionary<string, object> { { "id", id } });
lgr.LogInformation("Пишем в лог.");
}
}
Здесь вызов BeginScope создаёт контекст логирования с единственным ключом (id). При вызове lgr.LogInformation область действия автоматически включает эти контекстные данные. Но как это проверить в тесте?
FakeLogger отслеживает каждую запись журнала, включая шаблоны сообщений, уровни ведения журнала, исключения и области действия. Проверим, правильно ли DemoService пишет в лог, включая id:
public class DemoServiceTests
{
[Fact]
public void ScopeUsage()
{
var fakeLogger = new FakeLogger<DemoService>();
var sut = new DemoService(fakeLogger);
var id = Guid.NewGuid();
sut.LogMe(id);
fakeLogger
.LatestRecord
.Message
.Should()
.Be("Пишем в лог.");
var scope = fakeLogger
.LatestRecord
.Scopes[0] as Dictionary<string, object>;
scope.Should().NotBeNull();
scope.Keys.Should().Contain("id");
scope.Values.Should().Contain(id);
}
}
В этом тесте мы:
- Создаём экземпляр тестируемой системы (SUT), используя FakeLogger<DemoService>.
- Вызываем метод LogMe(), генерирующий одну запись журнала.
- Проверяем содержимое сообщения и проверяем, записана ли коллекция из области действия.
Каждая область действия регистрируется как объект, и в данном случае это Dictionary<string, object>, содержащий id. Этот подход понятен, выразителен и поддерживается фреймворком, устраняя необходимость в пользовательских оболочках для логирования или ненадёжных настройках верификации моков.
Недостаток в том, что вам, вероятно, потребуется знать, как использовалась область действия в коде, чтобы тесты проверяли то, что вам нужно, однако в данном контексте это приемлемо.
Источник: https://adamstorr.co.uk/blog/testing-with-fakelogger-scopes/
👍7
День 2478. #ЗаметкиНаПолях
Ограничение Доступа к Методу Действия в ASP.NET Core MVC. Начало
Рассмотрим несколько вариантов ограничения доступа к определённому методу действия (или ко всем методам) в контроллере ASP.NET Core MVC.
Мы всегда должны добавлять поддержку аутентификации (кто пользователь) и авторизации (что он может делать) в конвейер ASP.NET Core. Авторизация требует аутентификации, но аутентификация может существовать и сама по себе в определённых схемах аутентификации:
Использование фильтров
Фильтры ASP.NET Core хорошо известны и популярны, и, пожалуй, это самый простой способ ограничить доступ к конечной точке. Фильтры можно применять:
- через атрибут, к методу действия или контроллеру,
- глобально для всех контроллеров и действий.
Пользовательские фильтры
Использование:
Возврат результата осуществляется через свойство Result объекта AuthorizationFilterContext:
-
-
-
-
Продолжение следует…
Источник: https://developmentwithadot.blogspot.com/2025/10/restricting-access-to-action-method-in.html
Ограничение Доступа к Методу Действия в ASP.NET Core MVC. Начало
Рассмотрим несколько вариантов ограничения доступа к определённому методу действия (или ко всем методам) в контроллере ASP.NET Core MVC.
Мы всегда должны добавлять поддержку аутентификации (кто пользователь) и авторизации (что он может делать) в конвейер ASP.NET Core. Авторизация требует аутентификации, но аутентификация может существовать и сама по себе в определённых схемах аутентификации:
builder.Services.AddAuthentication()
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
builder.Services.AddAuthorization();
Использование фильтров
Фильтры ASP.NET Core хорошо известны и популярны, и, пожалуй, это самый простой способ ограничить доступ к конечной точке. Фильтры можно применять:
- через атрибут, к методу действия или контроллеру,
- глобально для всех контроллеров и действий.
Пользовательские фильтры
IAuthorizationFilter (IAsyncAuthorizationFilter) — интерфейс, определяющий правила авторизации для данной конечной точки. Его можно реализовать с помощью атрибута, который затем применить к методу действия, классу контроллера или глобально ко всем запросам:[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class DayOfWeekFilterAttribute(
params DayOfWeek[] daysOfWeek)
: Attribute, IAsyncAuthorizationFilter
{
public Task OnAuthorizationAsync(
AuthorizationFilterContext context)
{
if (!(daysOfWeek ?? [])
.Contains(DateTime.Today.DayOfWeek))
context.Result = new ForbidResult();
return Task.CompletedTask;
}
}
Использование:
[DaysOfWeek(DayOfWeek.Saturday, DayOfWeek.Sunday)]
public IActionResult Index() { … }
Возврат результата осуществляется через свойство Result объекта AuthorizationFilterContext:
-
ForbidResult: HTTP 403 Forbidden;-
RedirectResult, RedirectToActionResult, RedirectToPageResult, RedirectToRouteResult, LocalRedirectResult: различные виды перенаправлений (HTTP 3xx);-
EmptyResult: ничего с кодом 200 OK;-
StatusCodeResult: пользовательский код статуса.Продолжение следует…
Источник: https://developmentwithadot.blogspot.com/2025/10/restricting-access-to-action-method-in.html
👍14
День 2479. #ЗаметкиНаПолях
Ограничение Доступа к Методу Действия в ASP.NET Core MVC. Продолжение
Начало
Атрибут Authorize
Существует также встроенный атрибут
- Политика: ограничение по именованной политике, которая должна быть определена.
- Роли: одна или несколько ролей, разделённых запятыми. Текущий пользователь должен иметь одну из указанных ролей. Роли берутся из утверждений запроса.
Возможно наличие нескольких атрибутов [Authorize]:
- если в одном [Authorize] несколько ролей, пользователю необходимо иметь одну из них;
- если в нескольких [Authorize] заданы роли, пользователь должен иметь их все.
То же относится и к политикам.
Атрибут
Если мы хотим использовать именованные политики, мы должны определить их:
Теперь можно использовать политику "AdminPolicy":
Роли и политики
Это разные способы управления доступом. Роли напрямую сопоставляются с требованиями аутентификации или группами пользователей, в зависимости от используемого типа аутентификации. Политики же позволяют настраивать требования, которые могут включать роли, но не ограничиваются этим. В определении политики мы можем требовать:
- аутентификации:
- утверждений (claim):
- одной из ролей:
- определённого имени:
- определённого условия:
А также:
- Объединять несколько политик:
- Добавлять требования:
Имена политик должны быть уникальными и могут использоваться в различных местах, связанных с авторизацией.
Также возможно иметь более структурированный и переиспользуемый контроль доступа, об этом далее.
Окончание следует…
Источник: https://developmentwithadot.blogspot.com/2025/10/restricting-access-to-action-method-in.html
Ограничение Доступа к Методу Действия в ASP.NET Core MVC. Продолжение
Начало
Атрибут Authorize
Существует также встроенный атрибут
[Authorize], который может быть применён к классам контроллеров или методам действий, но не реализует ни один из этих интерфейсов. Он допускает несколько ограничений:- Политика: ограничение по именованной политике, которая должна быть определена.
- Роли: одна или несколько ролей, разделённых запятыми. Текущий пользователь должен иметь одну из указанных ролей. Роли берутся из утверждений запроса.
Возможно наличие нескольких атрибутов [Authorize]:
- если в одном [Authorize] несколько ролей, пользователю необходимо иметь одну из них;
- если в нескольких [Authorize] заданы роли, пользователь должен иметь их все.
То же относится и к политикам.
Атрибут
[AllowAnonymous], если присутствует, обходит любой атрибут [Authorize].[Authorize(Roles = "Admin")]
//все методы действия контроллера требуют роли "Admin"
public class AdminController
: Controller
{
public IActionResult Index()
{ … } //требует роли "Admin"
[AllowAnonymous]
public IActionResult SignOut()
{ … } //может быть вызван кем угодно
}
Если мы хотим использовать именованные политики, мы должны определить их:
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminPolicy", p =>
{
p.RequireRole("Admin");
p.RequireAuthenticatedUser();
});
Теперь можно использовать политику "AdminPolicy":
[Authorize(Policy = "AdminPolicy")]
public IActionResult Restricted()
{ … } //требует удовлетворять политике "AdminPolicy"
Роли и политики
Это разные способы управления доступом. Роли напрямую сопоставляются с требованиями аутентификации или группами пользователей, в зависимости от используемого типа аутентификации. Политики же позволяют настраивать требования, которые могут включать роли, но не ограничиваются этим. В определении политики мы можем требовать:
- аутентификации:
RequireAuthenticatedUser();- утверждений (claim):
RequireClaim();- одной из ролей:
RequireRole();- определённого имени:
RequireUserName();- определённого условия:
RequireAssertion().А также:
- Объединять несколько политик:
Combine();- Добавлять требования:
AddRequirements().Имена политик должны быть уникальными и могут использоваться в различных местах, связанных с авторизацией.
Также возможно иметь более структурированный и переиспользуемый контроль доступа, об этом далее.
Окончание следует…
Источник: https://developmentwithadot.blogspot.com/2025/10/restricting-access-to-action-method-in.html