Начиная с версии 8 стал платной для коммерческого использования библиотекой за ~$130 на юзера
Вы занимаетесь программированием на C# и .NET? Тогда канал ".NET epeshk blog" идеально подойдет вам! Здесь вы найдете заметки, советы и полезные материалы по этим темам. Разбираясь в деталях разработки на C# и во всем, что связано с платформой .NET, вы сможете значительно улучшить свои навыки и стать более эффективным специалистом. Кроме того, канал предлагает возможность поддержать его, перейдя по ссылке https://t.me/blog_donate/2. Здесь вы также можете оставить свои комментарии, предложения и отзывы, чтобы канал был еще более полезным и интересным для вас. Присоединяйтесь к .NET epeshk blog и станьте частью сообщества разработчиков, которые стремятся к постоянному росту и совершенствованию своих навыков!
14 Jan, 19:08
24 Dec, 11:47
12 Nov, 19:54
11 Nov, 15:04
08 Nov, 08:39
[StructLayout(LayoutKind.Sequential, Size = 84977)]
class LOHject { int _; }
Console.WriteLine(GC.GetGeneration(new LOHject())); // 2
double
от 1000 элементов. Сделано этого для оптимизации работы с double
на 32-битных системах, т.к. объекты в LOH на них выравнены по границе 8 байт. Этот факт полезен только на собеседованиях и квизах на конференцияхGCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
CompactOnce
и нет требований к размеру объекта. Нужна для массивов, указатели на которые передаются в нативный код.fixed
или GCHandle
создаёт проблемы для компактящего сборщика мусора. Поэтому для них сделали отдельную кучу. GC.AllocateArray<int>(128, pinned: true);
GC.AllocateUninitializedArray<int>(128, pinned: true);
Type
, Array.Empty<>()
, ... Публичного API для создания объектов в FOH нет. Нужна для оптимизаций — 1) GC не просматривает эти объекты, 2) JIT может генерировать более эффективный код, зная что объект никуда не переместится, не изменится, и что о новых ссылках на него не нужно сообщать сборщику мусора03 Nov, 14:12
03 Nov, 11:20
25 Oct, 11:22
16 Aug, 13:06
a.Equals(b) => a.GetHashCode() == b.GetHashCode()
.GetHashCode
переопределён, то первый хэш-код из заголовка объекта все равно можно извлечь — используя метод RuntimeHelpers.GetHashCode(object)
. Иногда это полезно, например так оптимизирован кэш message template в Serilog, в котором ключи — константные строки.IEqualityComparer
. Например, у строк будут разные хэш-коды для StringComparer.Ordinal/OrdinalIgnoreCase/InvariantCulture/...
. Эти хэш-коды тоже вычисляются заново при вызове GetHashCode
.Equals/GetHashCode
для структур настолько плохи, что лучше всегда реализовывать IEquatable<T>
, или сразу использовать record
.16 Aug, 11:27
Lock
, как на обычный объект с помощью Monitor
?lock ((object)_lock) { }
после релиза .NET 9 — будет breaking change. Неужели ассерт на тип объекта, переданного в Monitor.Enter настолько дорогой?16 Aug, 10:13
object o = new object(); lock (o) { ... }
, предлагается писать так:Lock @lock = new Lock();
using (@lock.EnterScope()) { ... }
// или, также поддержана конструкция lock
lock (@lock) { ... }
lock
на основе нативного Monitor
использует то же поле из заголовка объекта, что и хэшкод — это приводит к багам и проблемам с производительностью. Вспоминается, однажды баг в локе привёл и к падению рантайма, увы, не нашел сейчас этот issue.Lock
, в том виде, в каком его планируется добавить в .NET 9, не выглядит проработанным до конца. В основном его проблемы связаны с тем, что было решено поддержать lock statement
для нового типа.Lock
можно присвоить переменной типа object
. Вот так:private object _sync = new Lock();
lock(_sync)
будет использовать Monitor lock
вместо managed lock
. Выходит, что под новый Lock
может зайти два потока сразу — один честно, через managed блокировку, второй — ошибочно, через Monitor
.Lock
, рекомендую сразу включить <WarningsAsErrors>CS9216</WarningsAsErrors>
. Warning отлавливает не все варианты скастить Lock
к object
, например можно скастить через generic-метод вида object ToObject<T>(T obj) => obj;
.CS9216 : A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement.
lock
произвольными типами. Например — lock(SpinLock)
, lock (Semaphore)
, lock(MyDistributedLock)
... — любые типы, имеющие методы Enter/Exit, или специальный интерфейс для блокировки. Но в финальную версию вошла поддержка только типа System.Threading.Lock
, а расширяемость оставлена на будущее.Monitor
namespace System.Threading {
public class Lock {
public Scope EnterScope() {
Console.WriteLine("EnterScope");
return default;
}
public ref struct Scope {
public void Dispose() => Console.WriteLine("Dispose");
}
}
}
Monitor.Wait/Pulse/PulseAll
Lock
лучше сразу включить <WarningsAsErrors>CS9216</WarningsAsErrors>
, до того, как кто-нибудь попробует им воспользоваться.09 Aug, 07:03
01 Aug, 12:51
ValueTask
полезен только если метод чаще всего завершается синхронно[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder))]
для ValueTask
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
для ValueTask<T>
ValueTask
может оборачивать переиспользуемый объект IValueTaskSource
, умеющий сигнализировать об окончании асинхронного ожидания. Примеры таких объектов:AsyncOperation<T>
в System.Threading.Channels ManualResetValueTaskSourceCore<T>
— структура с логикой для упрощения создания своей реализации IValueTaskSource
. По сути — переиспользуемый аналог TaskCompletionSource
SemaphoreCompletionSource
в ConcurrencyToolkitValueTask
должен await
-иться только один раз. Точнее, метод .GetResult()
должен вызываться ровно один раз, т.к. именно в нём реализуется логика по освобождению IValueTaskSource
для переиспользования.ValueTask
не await
-ится, и на нём не вызывается .GetResult()
— это тоже плохо. Значит IValueTaskSource
не будет переиспользоватьсяTask
, ValueTask
не реализует интерфейс IDisposable
. Это полезно, если результат асинхронного метода предполагается использовать в using
.struct LockHolder : IDisposable;
Task<LockHolder> LockAsync();
ValueTask<LockHolder> ValueLockAsync();
using (LockAsync()) // баг, диспоузится Task
using (ValueLockAsync()) // ошибка компиляции, ValueTask<T> не IDisposable
using (await ValueLockAsync()) // OK
30 Jul, 12:07
29 Jul, 14:42
Dictionary
и HashSet
перечисляют элементы в порядке добавления, если из коллекции не было удалений.var hs = new HashSet<int> { 1, 4, 2, 5, 3 };
Console.WriteLine(string.Join(", ", hs));
// output: 1, 4, 2, 5, 3
Dictionary
и HashSet
организована в виде двух массивов: _buckets
и _entries
. Значения хранятся в массиве _entries
, который заполняется от начала к концу, а при увеличении размера коллекции копируется как есть._entries
образуется пустое место, которое будет занято новым элементом, что нарушит упорядоченность..CountBy()
. Даже есть тест на случай, если устройство Dictionary
изменится в будущем.Hashtable
такой особенностью не обладает — эта реализация хэш-таблицы использует один массив и для элементов, и для адресации: элементы располагаются в соответствии с их хэш-кодами, а не по порядку добавления, и перечисляются в том же порядке, что и хранятся.29 Jul, 07:25
24 Jul, 18:33
StringBuilder
устроен внутри не так, как List<char>
, внутри которого массив, увеличивающийся в 2 раза. Точнее, таким он был в древние времена (до .NET Framework 4.0), а сейчас внутри связный список объектов, ссылающихся на массивы char[]
. Размер этих массивов растёт по мере наполнения билдера, но ограничен, чтобы они не попадали в LOH.StringBuilder
можно переиспользовать. Перед этим нужно сбросить его содержимое вызовом .Clear()
или .Count = 0
. При этом связный список выбрасывается, и вместо него аллоцируется один большой массив такого же capacity. И этот новый массив уже может попасть в LOH. Отсюда вывод: если StringBuilder
не переиспользуется — не нужно очищать его вручную.StringBuilder
метод .ToString()
. Например, можно использовать .CopyTo(Span<char>)
, или пройтись по списку ReadOnlyMemory<char>
, соответствующих элементам связного списка через енумератор .GetChunks()
StringBuilder
переиспользуется, то скорее всего, внутри у него связный список из одного элемента с большим массивом, который содержит всю строку-результат. Её можно извлечь без копирования с помощью того же енумератора. Полученный Memory будет валиден до следующих модификаций билдера, по аналогии с методами из CollectionsMarshal
:public static bool TryGetMemory(this StringBuilder sb, out ReadOnlyMemory<char> memory) {
var enumerator = sb.GetChunks();
if (!enumerator.MoveNext()) {
memory = ReadOnlyMemory<char>.Empty;
return true;
}
memory = enumerator.Current;
return !enumerator.MoveNext();
}
StringBuilder
оптимизирован под добавление в конец (Append). Методы Insert
, Remove
, Replace
не только алгоритмически сложны, но и могут испортить внутреннюю структуру связного списка, что приведёт к увеличению сложности других операций с билдером или лишним аллокациям. Особенно не стоит использовать эти методы, если StringBuilder
переиспользуется24 Jul, 12:36
19 Jul, 12:02
params
-массивов в методах, принимающих несколько аргументов.params
-спаны! И они уже доступны в превью .NET 9.Method(1, 2, 3, 4, 5, 6, 7);
void Method(params Span<int> span) { }
18 Jul, 13:42
18 Jul, 09:45
.StartsWith(string)
зависит от локали, даже если передана строка из одного ASCII-символа.var s = ((char)8490).ToString(); // Kelvin sign
Console.WriteLine(s.StartsWith("K")); // true
Console.WriteLine(s.StartsWith('K')); // false
Console.WriteLine(s.StartsWith("K", StringComparison.Ordinal)); // false
.StartsWith(string, StringComparison.Ordinal)
уже оптимизирован для односимвольных строк. В Serilog это даже учли, но Pull Request пока не влит.18 Jul, 06:09
.StartsWith("$")
вместо .StartsWith('$')
. В первом случае передаётся строка, во втором — символ.16 Jul, 14:13
Guid
V4 как ключей в БД. В preview 6 пока этого метода нет, можно попробовать в альфа-билдеGuid
добавили новые свойства:Version
— позволяет отличить 4 и 7 версиюVariant
— "the most significant 4 bits of the 8th byte". Не придумал лучшего описания этому свойству, взял из документации.Guid
остались:CoCreateGuid
(Windows) и /dev/urandom
(Unix-like). В некоторых ситуациях допустимо обменять случайность на производительность, а значит споры об алгоритме генерации гуида точно не закончатся. Быстрый способ реализован, например, в библиотеке Cysharp/Ulid16 Jul, 12:16
Directory.Packages.props
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Newtonsoft.Json" VersionOverride="13.0.2" />
CentralPackageTransitivePinningEnabled
— обновляет транзитивные зависимости до версий, указанных в Directory.Packages.props
GlobalPackageReference
— подключает пакет во все проекты, которые импортируют Directory.Packages.props
VersionOverride
— без них солюшен даже не собирался. Риск обновить в них пакеты разом и словить в рантайме на проде баги и просадки производительности тоже выглядит большим.14 Jul, 04:36
domain.com?method=create&type=user&shouldcreate=true
, то после обновления они стали domain.com?method=REDUCTED&type=REDUCTED&shouldcreate=REDUCTED
. Да, они вырезают все данные без разбора, не пытаясь разобраться, что же там происходит или что это за данные. Вам просто ломают ноги и говорят, что это для безопасности, чтобы вы случайно с моста не прыгнули.internal bool DisableUrlQueryRedaction { get; set; }
11 Jun, 15:22