День 2028. #ЧтоНовенького #CSharp13
Альтернативный Доступ к Коллекциям
До C#13 ref-структуры не могли быть аргументами параметра-типа в обобщённых типах. Теперь в объявление обобщённого типа после ключевого слова where добавлено «анти-ограничение» allows ref struct, которое сообщает, что аргументом для параметра типа может быть ref-структура. Анти-ограничение оно, потому что по факту оно не ограничивает возможный набор параметров типа, а расширяет его. Это анти-ограничение налагает все доступные для ref-структур правила (вроде запрета размещения в куче) на все экземпляры параметра-типа. Смысл этого изменения также в том, чтобы можно было использовать типы Span в обобщённых-алгоритмах.
Например, таблицы поиска (lookup-таблицы) типа Dictionary<TKey,TValue> и HashSet<T> часто используются в качестве своего рода кэша. Однако, так как ключи таблиц обычно строкового типа, раньше приходилось выделять строку, чтобы обратиться в lookup-таблицу по ключу. Теперь упомянутое выше «анти-ограничение» добавляет новые возможности для этих типов коллекций.
Следующий код считает количество различных слов в тексте, используя так называемые альтернативные ключи словаря:
Сначала мы создаём lookup-словарь counts и указываем, что нам не важен регистр ключей.
Затем, чтобы искать в словаре по spanам, а не по строкам, мы создаём структуру lookup для альтернативного доступа к элементам. Здесь она вот такого длинного типа Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>. В примере указан тип полностью для демонстрации. В реальном коде это можно заменить на var. Заметьте, что тут используется обобщённый тип AlternateLookup с параметром типа виде ref-структуры ReadOnlySpan<char>.
Далее мы разбиваем текст по «не-словам», используя Regex.EnumerateSplits. В результате получаем перечислитель диапазонов результатов. С его помощью мы делаем срезы исходного текста, которые представляют собой отдельные слова. Это переменная word типа ReadOnlySpan<char>. И вот здесь, чтобы не создавать строк для доступа в словарь counts по строковому ключу, нам и потребуется структура lookup, которая обеспечит доступ к словарю по альтернативному ключу типа ReadOnlySpan<char>. В данном случае мы увеличиваем счётчик для текущего слова.
Теперь мы можем взять текст и посчитать в нём слова:
Источник: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13
Альтернативный Доступ к Коллекциям
До C#13 ref-структуры не могли быть аргументами параметра-типа в обобщённых типах. Теперь в объявление обобщённого типа после ключевого слова where добавлено «анти-ограничение» allows ref struct, которое сообщает, что аргументом для параметра типа может быть ref-структура. Анти-ограничение оно, потому что по факту оно не ограничивает возможный набор параметров типа, а расширяет его. Это анти-ограничение налагает все доступные для ref-структур правила (вроде запрета размещения в куче) на все экземпляры параметра-типа. Смысл этого изменения также в том, чтобы можно было использовать типы Span в обобщённых-алгоритмах.
Например, таблицы поиска (lookup-таблицы) типа Dictionary<TKey,TValue> и HashSet<T> часто используются в качестве своего рода кэша. Однако, так как ключи таблиц обычно строкового типа, раньше приходилось выделять строку, чтобы обратиться в lookup-таблицу по ключу. Теперь упомянутое выше «анти-ограничение» добавляет новые возможности для этих типов коллекций.
Следующий код считает количество различных слов в тексте, используя так называемые альтернативные ключи словаря:
static Dictionary<string, int>
CountWords(ReadOnlySpan<char> input)
{
Dictionary<string, int> counts =
new(StringComparer.OrdinalIgnoreCase);
Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>
lookup = counts.GetAlternateLookup<string, int, ReadOnlySpan<char>>();
foreach (Range r in Regex.EnumerateSplits(input, @"\b\W+\b"))
{
ReadOnlySpan<char> word = input[r];
lookup[word] = lookup
.TryGetValue(word, out int count) ? count + 1 : 1;
}
return counts;
}
Сначала мы создаём lookup-словарь counts и указываем, что нам не важен регистр ключей.
Затем, чтобы искать в словаре по spanам, а не по строкам, мы создаём структуру lookup для альтернативного доступа к элементам. Здесь она вот такого длинного типа Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>. В примере указан тип полностью для демонстрации. В реальном коде это можно заменить на var. Заметьте, что тут используется обобщённый тип AlternateLookup с параметром типа виде ref-структуры ReadOnlySpan<char>.
Далее мы разбиваем текст по «не-словам», используя Regex.EnumerateSplits. В результате получаем перечислитель диапазонов результатов. С его помощью мы делаем срезы исходного текста, которые представляют собой отдельные слова. Это переменная word типа ReadOnlySpan<char>. И вот здесь, чтобы не создавать строк для доступа в словарь counts по строковому ключу, нам и потребуется структура lookup, которая обеспечит доступ к словарю по альтернативному ключу типа ReadOnlySpan<char>. В данном случае мы увеличиваем счётчик для текущего слова.
Теперь мы можем взять текст и посчитать в нём слова:
var text = "<какой-то длинный текст>";
foreach (var word in CountWords(text))
Console.WriteLine(word);
Источник: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13
👍20
День 2032. #ЧтоНовенького #CSharp13
Флаги функций с поддержкой тримминга
Два новых атрибута позволяют определять флаги функций, которые можно использовать для включения/отключения областей функциональности, а также для автоматического включения/отключения функций при тримминге или AOT-компиляции.
FeatureSwitchDefinitionAttribute
Атрибут FeatureSwitchDefinition может быть использован, чтобы флаг функции после сборки определялся как константа.
Здесь мы извлекаем значение IsSupported из конфигурации сборки. В данном случае оно определено в файле проекта (.csproj):
Мы добавляем RuntimeHostConfigurationOption с именем нужной функции (соответствующим параметру атрибута) и булевым значением, включена она или нет.
При сборке с включённым триммингом недосягаемый код, находящийся под флагом, удаляется. Когда приложение собирается с такой настройкой в файле проекта, Feature.IsSupported расценивается как константа false и Feature.Implementation удаляется из сборки.
Предложение описывает использование атрибута FeatureSwitchDefinition в библиотеках и для тримминга. Возможно ли его использование как флага функции в бизнес-логике пока не понятно, но, думаю, можно попробовать.
FeatureGuardAttribute
Атрибут FeatureGuard можно использовать для свойства флага функции в качестве защитной конструкции для кода, аннотированного атрибутами RequiresUnreferencedCode, RequiresAssemblyFiles или RequiresDynamicCode:
Здесь RuntimeFeature определяет API, которые доступны в среде выполнения. В данном случае свойство IsDynamicCodeSupported показывает, поддерживается ли в среде выполнения динамический код.
Поскольку свойство IsSupported возвращает false, когда динамический код не поддерживается, его можно использовать в качестве защиты для методов, которым требуется динамический код во время выполнения.
Атрибут FeatureGuard сообщает об этом анализатору тримминга и инструментам тримминга. Поэтому, если мы в файле проекта укажем
то Feature.DynamicImplementation будет удалён при AOT-публикации.
Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview4/runtime.md
Флаги функций с поддержкой тримминга
Два новых атрибута позволяют определять флаги функций, которые можно использовать для включения/отключения областей функциональности, а также для автоматического включения/отключения функций при тримминге или AOT-компиляции.
FeatureSwitchDefinitionAttribute
Атрибут FeatureSwitchDefinition может быть использован, чтобы флаг функции после сборки определялся как константа.
public class Feature
{
[FeatureSwitchDefinition("Feature.IsSupported")]
internal static bool IsSupported =>
AppContext.TryGetSwitch("Feature.IsSupported", out bool enabled)
? enabled
: true;
internal static void Implementation() => …;
}
Здесь мы извлекаем значение IsSupported из конфигурации сборки. В данном случае оно определено в файле проекта (.csproj):
<ItemGroup>
<RuntimeHostConfigurationOption Include="Feature.IsSupported"
Value="false" Trim="true" />
</ItemGroup>
Мы добавляем RuntimeHostConfigurationOption с именем нужной функции (соответствующим параметру атрибута) и булевым значением, включена она или нет.
При сборке с включённым триммингом недосягаемый код, находящийся под флагом, удаляется. Когда приложение собирается с такой настройкой в файле проекта, Feature.IsSupported расценивается как константа false и Feature.Implementation удаляется из сборки.
Предложение описывает использование атрибута FeatureSwitchDefinition в библиотеках и для тримминга. Возможно ли его использование как флага функции в бизнес-логике пока не понятно, но, думаю, можно попробовать.
FeatureGuardAttribute
Атрибут FeatureGuard можно использовать для свойства флага функции в качестве защитной конструкции для кода, аннотированного атрибутами RequiresUnreferencedCode, RequiresAssemblyFiles или RequiresDynamicCode:
public class Feature
{
[FeatureGuard(typeof(RequiresDynamicCodeAttribute))]
internal static bool IsSupported =>
RuntimeFeature.IsDynamicCodeSupported;
[RequiresDynamicCode("Feature requires dynamic code support.")]
internal static void DynamicImplementation()
=> …; // Использует dynamic
}
Здесь RuntimeFeature определяет API, которые доступны в среде выполнения. В данном случае свойство IsDynamicCodeSupported показывает, поддерживается ли в среде выполнения динамический код.
Поскольку свойство IsSupported возвращает false, когда динамический код не поддерживается, его можно использовать в качестве защиты для методов, которым требуется динамический код во время выполнения.
if (Feature.IsSupported)
Feature.DynamicImplementation();
Атрибут FeatureGuard сообщает об этом анализатору тримминга и инструментам тримминга. Поэтому, если мы в файле проекта укажем
<PublishAot>true</PublishAot>
то Feature.DynamicImplementation будет удалён при AOT-публикации.
Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview4/runtime.md
6👍7