Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин
Thread.CurrentThread.CurrentCulture.
Создадим класс, который реализует интерфейс IDisposable, чтобы иметь ограниченный блок кода, в котором будет использоваться новая культура:
public class TempCulture : IDisposable
{
CultureInfo _old;
public TempCulture(CultureInfo culture)
{
_old = CultureInfo.CurrentCulture;
Thread.CurrentThread.CurrentCulture = culture;
}
public void Dispose()
{
Thread.CurrentThread.CurrentCulture = _old;
}
}
В конструкторе сохраняем текущую культуру в приватном поле. Затем, когда мы вызываем метод Dispose (который неявно вызывается при закрытии блока using), мы используем это значение для восстановления исходной культуры.
Например, проверим символ валюты:
Thread.CurrentThread.CurrentCulture =
new CultureInfo("ja-jp");
Console.WriteLine(
Thread.CurrentThread.CurrentCulture
.NumberFormat.CurrencySymbol
);
//¥
using (new TempCulture(new CultureInfo("ru-ru")))
{
Console.WriteLine(
Thread.CurrentThread.CurrentCulture
.NumberFormat.CurrencySymbol
);
//₽
}
Console.WriteLine(
Thread.CurrentThread.CurrentCulture
.NumberFormat.CurrencySymbol
);
//¥
Сначала мы устанавливаем культуру текущего потока на японскую, и получаем символ валюты – йену ¥. Затем временно переходим на русскую культуру и в консоль выводится рубль ₽. Наконец, когда мы выходим за пределы блока using, мы снова получаем йену.
Итого
Использование класса, реализующего IDisposable, — хороший способ создать временную среду с характеристиками, отличными от характеристик основной среды.
Источник: dev.to/bellone…ure-2bp7ImmutableDictionary<TKey, TValue> и ImmutableSortedDictionary<TKey, TValue> из пространства имён System.Collections.Immutable, которые имеют очень похожие члены:
var d = ImmutableDictionary<int, string>.Empty; d = d.Add(10, "Ten"); d = d.Add(21, "Twenty-One"); d = d.SetItem(10, "Diez"); // Выводит "10Diez" и "21Twenty-One" в непредсказуемом порядке. foreach (var item in d) Console.WriteLine(item.Key + item.Value); string ten = d[10]; // ten == "Diez" d = d.Remove(21);Обратите внимание на использование
SetItem. В изменяемом словаре можно было бы использовать конструкцию вида dictionary[key] = item, но неизменяемые словари должны возвращать обновлённый неизменяемый словарь, поэтому вместо этого они должны использовать метод SetItem.
Несортированные и отсортированные словари обладают сходным быстродействием - O(log N) для всех типов операций, но рекомендуется использовать неупорядоченные словари, если только не требуется, чтобы элементы были отсортированы (они могут работать в целом немного быстрее). Кроме того, несортированные словари могут использоваться с любыми типами ключей, тогда как отсортированные словари требуют, чтобы тип ключей был сравнимым (например, int или реализующий IComparable).
Словари являются полезным и общепринятым инструментом при работе с состоянием приложения. Они могут использоваться в любых сценариях, связанных с ключами/значениями или подстановками.
Неизменяемые словари, как и другие неизменяемые коллекции, поддерживают механизм для эффективного построения экземпляров, содержащих большое количество элементов. Например, если исходные данные загружаются в начале работы программы, вы сможете воспользоваться механизмом построителей для конструирования исходного неизменяемого словаря. С другой стороны, если данные строятся постепенно во время выполнения, вероятно, можно будет воспользоваться обычным методом Add неизменяемых словарей.
См. также:
- Неизменяемые стеки и очереди
- Неизменяемые списки
- Неизменяемые множества
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 9.public record PersonUpdate()
{
public string Name {get; init;}
//... остальные свойства
};
Производная (с добавленным свойством Id):
public record Person(int Id)
: PersonUpdate() {};
Задача стандартная: получить Person из базы или API, создать PersonUpdate с изменёнными свойствами и отправить его в метод UpdatePerson. Код получается довольно простой:
var person = GetPerson(42);
var updated = (PersonUpdate)person
with { Name = "New Name", …};
UpdatePerson(42, updated);
Вроде всё должно работать. Но давайте посмотрим, что у нас получилось в updated:
Console.WriteLine(updated);
// Вывод:
// Person { Name = New Name, Id = 42 }
Свойство обновилось, но тип всё равно производный и содержит Id. Это, конечно, может не быть проблемой, но для Фила стало, т.к. API для обновления принимал объект без свойства Id, а в этом случае выдавал ошибку.
Фил спросил сообщество, как решить эту проблему. Предлагались разные варианты от использования маппера до сериализации/десериализации. Но решение нашлось довольно элегантное.
public record PersonUpdate()
{
//... свойства
public static PersonUpdate
Copy(PersonUpdate updated) => new(updated);
};
Статический метод принимает запись и выдаёт её копию. И действительно:
var updated = PersonUpdate.Copy(person)
with { Name = "New Name"};
Console.WriteLine(updated);
// Вывод:
// PersonUpdate { Name = New Name }
Источник: gist.github.com/haacked…7c09d28b[FromServices], сервис будет подставляться автоматически:
public class MyController : ControllerBase
{
public ActionResult Get(MyService service) => Ok();
}
Это можно отключить в настройках:
Services.Configure<ApiBehaviorOptions>(opt =>
{
opt.DisableImplicitFromServicesParameters = true;
})
2. Описания конечных точек
В минимальных API теперь можно добавить аннотации для спецификаций OpenAPI. Либо через методы расширения:
app.MapGet("/hello", () => ...)
.WithDescription("Hello request");
Либо через атрибуты:
app.MapGet("/hello", [EndpointSummary("Hello request")]() => ...);
3. Привязка массивов и StringValues
Теперь можно выполнять связывание из заголовков HTTPS и строки запроса в массивы примитивных типов, строк или в StringValues:
// GET /tags?q=1&q=2
app.MapGet("/tags", (int[] q) => $"1:{q[0]}, 2:{q[1]}");
// GET /tags?names=john&names=jack
app.MapGet("/tags", (string[] names) => $"1:{names[0]}, 2:{names[1]}");
app.MapGet("/tags", (StringValues names) => $"1:{names[0]}, 2:{names[1]} ");
4. Фильтры обработчиков маршрутов
Фильтры выполняются перед основной логикой обработчика маршрута и могут использоваться для проверки и изменения параметров обработчика или перехвата выполнения обработчика.
app.MapGet("/hello/{name}", HelloName)
.AddFilter(async (сontext, next) =>
{
return await next(сontext);
});
Это похоже на фильтры промежуточного ПО. Но теперь их можно «навесить» на отдельные конечные точки.
Подробнее про них рассказывает Ник Чапсас https://youtu.be/Kt9TiXrwIp4
5. Привязка с использованием TryParse
Теперь вы можете привязывать значения параметров действия контроллера с помощью метода TryParse. В примере ниже контроллер привязывает данные из строки запроса с помощью метода TryParse в типе параметра:
public class TryParseController : ControllerBase
{
// GET /tryparse?data=MyName
[HttpGet]
public ActionResult Get([FromQuery]CustomParse data)
{
…
}
public class CustomParse
{
public string? Name { get; set; }
public static bool TryParse(string s, out CustomParse result)
{
if (s is null)
{
result = default;
return false;
}
result = new CustomParse { Name = s };
return true;
}
}
}
Источники:
- devblogs.microsoft.com/dotnet/…review-2
- devblogs.microsoft.com/dotnet/…review-3netdeveloper2022JRGpc
Билеты, подробности и первая программа — на сайте.
#рекламаpublic class SealedBenchmark
{
NonSealedType nonSealedType = new();
SealedType sealedType = new();
public void CallMethod_NonSealed()
=> nonSealedType.Method();
public void CallMethod_Sealed()
=> sealedType.Method();
}
Здесь JIT не может знать тип nonSealedType. Он мог быть установлен в производный тип другим методом. Поэтому должен быть виртуальный вызов. В отличие от следующего кода:
var instance = new NonSealedType(); instance.Method();Здесь JIT точно знает тип, поэтому делает прямой вызов. Приведение типов (is / as) При приведении типов среда выполнения должна проверять тип объекта во время выполнения. При приведении к незапечатанному типу среда выполнения должна проверять наличие всех типов в иерархии. Однако при приведении к запечатанному типу среда выполнения должна проверять только тип объекта, поэтому оно выполняется быстрее. Массивы Массивы в .NET ковариантны. Это означает, что допустимо
BaseType[] value = new DerivedType[1];Ковариантность приводит к снижению производительности. JIT должен проверять тип объекта перед назначением элемента в массив. При использовании запечатанных типов JIT может удалить проверку. Преобразование массивов в Span<T> Вы можете преобразовать массивы в Span<T> или ReadOnlySpan<T>. По тем же причинам, что и в предыдущем разделе, JIT должен проверить тип объекта перед преобразованием массива в
Span<T>. При использовании запечатанного типа можно избежать проверки.
Результаты теста быстродействия на картинке ниже.
Обнаружение недостижимого кода
При использовании запечатанного типа компилятор знает, что некоторые преобразования недействительны. Таким образом, он может сообщать о предупреждениях и ошибках. Это поможет уменьшить количество ошибок в вашем приложении, а также удалить недостижимый код.
class Sample
{
public void Foo(NonSealedType obj)
{
_ = obj as IMyInterface;
// ОК, т.к. производные класса
// могут реализовывать интерфейс
}
public void Foo(SealedType obj)
{
_ = obj is IMyInterface;
// ⚠️ Предупреждение CS0184
_ = obj as IMyInterface;
// ❌ Ошибка CS0039
}
}
Источник: www.meziantou.net/perform…lass.htmCtrl+W.
- Автоматический фокус на обозревателе решений после загрузки проекта (даже если он был скрыт).
- Пункт меню для очистки недавно открытых документов.
- Пункт всплывающего меню «Открыть сбоку» для файлов в обозревателе решений (открывает файл справа от текущего документа).
- Пункт всплывающего меню «Найти в обозревателе решений» для вкладок.
2. IDE
- Перезапуск VS в меню Файл (также возможно по Shift+Alt+F4).
- Варианты запуска VS в безопасном режиме и в режиме презентации (доступны в выпадающем меню при правом клике на ярлыке VS).
- Отключение кнопок отладки F10/F11 (ох, сколько раз я промахивался мимо F12).
3. Сборка
- Выпадающий список уровня подробности сообщений сборки в окне Output.
- Сохранение решения при сборке.
- Удаление папок bin и obj при очистке решения.
- Отображение времени запуска и длительности сборки.
- Отображение актуальности файлов проекта (полезно знать, чтобы выяснить, почему проект пересобирается). Отображается даже на минимальном уровне подробностей в окне Output.
- Отображение порядка сборки проектов
4. Редактор
- Очистка кода при форматировании документа (Ctrl+K,D).
- Отмена копирования пустых строк. По умолчанию при нажатии Ctrl+C, если ничего не выделено, копируется текущая строка. Теперь, если строка пустая, она не будет скопирована.
- Копирование без отступов (удаляет отступы у последующих строк, если первая скопирована без отступа).
- Форматирование при перемещении строк. Alt+стрелки помогают переместить строку вверх/вниз. Теперь будет добавляться нужный отступ.
- Отмена зума по Ctrl+колёсико мыши (из-за случайных срабатываний). Работает через Ctrl+Shift+колёсико мыши.
- Возврат к стандартному зуму по Ctrl+0 (стандартный зум устанавливается в настройках).
- Пункт «Настройки» во всплывающем меню документа, вызывающий настройки для языка документа.
- Пункт «Специальная вставка» во всплывающем меню документа.
- Возможность свернуть и скрыть несколько атрибутов.
- Выделение всей строки по Shift+Alt+E.
- Предупреждение при вставке больших объёмов текста.
Изменить все упомянутые настройки после установки расширения можно в меню Tools > Options > Environment > Tweaks 2022.var data = new (int, bool)[] {
(20, true),
(50, true),
(35, false),
(55, true),
(16, false)
};
Заметьте, что в примере у нас всего 5 элементов. В реальной же модели обучения их могут быть миллионы, поэтому выделять новый массив будет очень затратно.
4. Использование ArraySegment<T>
var train = new ArraySegment<(int, bool)>(data, 0, 3); var test = new ArraySegment<(int, bool)>(data, 3, 2);Мы инициализируем новый экземпляр
ArraySegment, передавая массив, который мы хотим разделить, начальный индекс и количество элементов. Это создает оболочку вокруг массива, которая ограничивает элементы между указанными позициями. Теперь мы можем выполнить итерацию по этому сегменту и прочитать его значения.
То же самое можно сделать с помощью метода ArraySegment<T>.Slice():
var all = new ArraySegment<(int, bool)>(data); var train = all.Slice(0, 3); var test = all.Slice(3, 2);Мы создаем
ArraySegment, который охватывает весь массив, затем вызываем метод Slice(), который создает ещё один ArraySegment с указанными границами.
Замечание: мы должны быть осторожны, потому что это не новый массив, это на самом деле переменная типа-значения, содержащая указатели на позиции элементов в массиве, поэтому любое изменение значений в сегменте массива будет отражаться в исходном массиве.
5. Использование ReadOnlySpan<T> и Span<T>
ReadOnlySpan<T> предоставляет оболочку, которая позволяет только чтение из массива:
var train = new ReadOnlySpan<(int, bool)>(data, 0, 3); var test = new ReadOnlySpan<(int, bool)>(data, 3, 2);
ReadOnlySpan очень похож на ArraySegment. Он принимает массив, начальный индекс и количество элементов. Мы можем перечислить его, но, если мы попытаемся изменить какой-либо элемент через экземпляр ReadOnlySpan, мы получим ошибку компиляции.
Код, использующий Span<T> и его поведение аналогичны коду с ArraySegment<T>. Их отличие в том, что Span<T> можно использовать не только для массивов. Кроме того, ArraySegment<T> реализует IEnumerable<T> таким образом позволяет вернуть результат как IEnumerable<T>, тогда как ReadOnlySpan<T> и Span<T> возвращают соответствующие типы, хотя оба имеют метод GetEnumerator(), поэтому их также можно перечислить через foreach.
Сравнение быстродействия
На массиве из 10 строк быстродействие выделения подмассива из 5 элементов и их перечисления. Общий вид методов:
public int <Метод>()
{
// получение подмассива указанным методом
var slice = <подмассив>;
var cnt = 0;
foreach (var item in slice)
cnt++;
return cnt;
}
Результаты на картинке ниже.
Источник: https://code-maze.com/csharp-array-slicing/var posts = new string[] { "post1", "post2", "post3", "post4", "post5", "post6", "post7", "post8", "post9", "post10" };
1. Использование LINQ
В LINQ есть методы Skip() и Take():
var sliced = posts.Skip(0).Take(5); // Элементы с post1 до post5Мы передаем количество элементов, которые хотим пропустить, методу
Skip() - начальный индекс, и количество элементов, которые хотим включить, в метод Take(). Операция возвращает новый IEnumerable, который мы можем перечислить.
2. Использование метода Copy()
Тот же сценарий, но помощью метода массива Copy():
var sliced = new string[5]; Array.Copy(posts, 0, sliced, 0, 5);Здесь мы инициализируем целевой массив, затем мы вызываем метод
Copy(), который принимает исходный массив, начальный индекс, целевой массив, начальный целевой индекс (ноль, потому что мы копируем в новый массив) и количество элементов, которые мы хотим скопировать. Метод возвращает новый массив, содержащий скопированные элементы.
3. Оператор диапазона (x..y) в C# 8.0+
Начиная с C#8 появился новый оператор диапазона, который сделал выделение подмассива синтаксически очень простым. Он позволяет выделить элементы между индексом «x» и индексом «y», не включая элемент с индексом «y»:
var s1 = posts[2..]; // co 2го до конца var s2 = posts[..2]; // с начала до 1го var s3 = posts[1..3]; // с 1го до 2го var s4 = posts[..]; // весь массив var s5 = posts[3..^0]; // с 3го до предпоследнегоОператор диапазона также выделяет новый массив. Окончание следует… Источник: https://code-maze.com/csharp-array-slicing/