День 1808. #ЧтоНовенького #CSharp13
Коллекции в Params
Всего месяц после выпуска .NET 8, а команда dotnet уже работает над следующей итерацией: .NET 9. Сегодня посмотрим на одно из предлагаемых нововведений: коллекции в параметрах с ключевым словом params.
Ключевое слово params в C# появилось давно. Оно позволяет передавать в метод переменное количество аргументов:
До сих пор оно было доступно только для массивов. Нововведение позволяет использовать его для коллекций любого типа:
И теперь эти методы могут быть вызваны любым способом:
Это позволяет добавить вариативности в использовании ваших методов клиентами и иметь более эффективный код внутри метода. Для полноты картины, в C# 12 вы уже могли сделать так:
Тем не менее, новая версия удобнее. Также обещают решить проблемы производительности, присущие нынешней версии params, но это не точно (с).
Подробнее про нововведение можно почитать в предложении на GitHub или попробовать его в действии на SharpLab.
Источник: https://steven-giesel.com/blogPost/5f4fef86-d251-4a47-b893-ca4c515ca314/the-first-possible-new-feature-of-c-13-params-collection
Коллекции в Params
Всего месяц после выпуска .NET 8, а команда dotnet уже работает над следующей итерацией: .NET 9. Сегодня посмотрим на одно из предлагаемых нововведений: коллекции в параметрах с ключевым словом params.
Ключевое слово params в C# появилось давно. Оно позволяет передавать в метод переменное количество аргументов:
void PrintNums(params int[] nums)
{
foreach (var n in nums)
Console.WriteLine(n);
}
До сих пор оно было доступно только для массивов. Нововведение позволяет использовать его для коллекций любого типа:
void PrintNumsSpan(
params ReadOnlySpan<int> nums)
{
foreach (var n in nums)
Console.WriteLine(n);
}
void PrintNumsList(
params List<int> nums)
{
nums.ForEach(Console.WriteLine);
}
И теперь эти методы могут быть вызваны любым способом:
PrintNumsSpan(1, 2, 3);
PrintNumsList([1, 2, 3]);
Это позволяет добавить вариативности в использовании ваших методов клиентами и иметь более эффективный код внутри метода. Для полноты картины, в C# 12 вы уже могли сделать так:
List<int> numbers = [1,2,3];
PrintNums([..numbers]);
Тем не менее, новая версия удобнее. Также обещают решить проблемы производительности, присущие нынешней версии params, но это не точно (с).
Подробнее про нововведение можно почитать в предложении на GitHub или попробовать его в действии на SharpLab.
Источник: https://steven-giesel.com/blogPost/5f4fef86-d251-4a47-b893-ca4c515ca314/the-first-possible-new-feature-of-c-13-params-collection
👍22
День 1817. #ЧтоНовенького #CSharp13
Новый Тип Блокировки в .NET 9.
Продолжаем заглядывать в будущее. Новый тип System.Threading.Lock, представлен для .NET 9.
Предупреждение: тип Lock всё ещё находится в режиме предварительного просмотра. Вы можете загрузить текущую сборку .NET 9 и выбрать новый тип блокировки с помощью переключателя
в файле csproj. Тем не менее, в будущем в этом типе ещё могут произойти кардинальные изменения.
Новый тип блокировки имеет одну основную цель: быть блокировкой. Т.е. представляет собой блокировку и только блокировку. Нет никакой двусмысленности в том, что он делает. До сих пор мы сделали что-то вроде следующего:
Новый тип Lock выражает это более явно:
Есть и другое предложение: "Паттерн оператора блокировки". Идея в том, чтобы иметь маркерный интерфейс ILockPattern, который «специально обрабатывается» компилятором и несколько новых типов блокировок. Например, следующий код:
Будет преобразовано в:
Здесь _lock может быть любым типом блокировки, реализующим ILockPattern. EnterLockScope будет выдавать disposable контекст блокировки.
Источник: https://steven-giesel.com/blogPost/d7f923b3-13ff-4ecc-8b8f-d847ae581f68/a-new-lock-type-in-net-9
Новый Тип Блокировки в .NET 9.
Продолжаем заглядывать в будущее. Новый тип System.Threading.Lock, представлен для .NET 9.
Предупреждение: тип Lock всё ещё находится в режиме предварительного просмотра. Вы можете загрузить текущую сборку .NET 9 и выбрать новый тип блокировки с помощью переключателя
<EnablePreviewFeatures>true</EnablePreviewFeatures>
в файле csproj. Тем не менее, в будущем в этом типе ещё могут произойти кардинальные изменения.
Новый тип блокировки имеет одну основную цель: быть блокировкой. Т.е. представляет собой блокировку и только блокировку. Нет никакой двусмысленности в том, что он делает. До сих пор мы сделали что-то вроде следующего:
object _obj = new();
void DoSomething()
{
lock (_obj)
{
// Что-то делаем
}
}
Новый тип Lock выражает это более явно:
Lock _lock = new();Использование его абсолютно такое же (на данный момент). Но новый тип явный и теоретически может быть быстрее. Это зависит от используемой базовой реализации. До сих пор конструкция lock { … } всегда заменялась на пару операторов Monitor.Enter/Monitor.Exit, но теперь это не обязательно будет так.
void DoSomething()
{
lock (_lock)
{
// Что-то делаем
}
}
Есть и другое предложение: "Паттерн оператора блокировки". Идея в том, чтобы иметь маркерный интерфейс ILockPattern, который «специально обрабатывается» компилятором и несколько новых типов блокировок. Например, следующий код:
class MyDataStructure
{
private Lock _lock = new();
void Foo()
{
lock (_lock)
{
// Что-то делаем
}
}
}
Будет преобразовано в:
class MyDataStructure
{
private Lock _lock = new();
void Foo()
{
using (_lock.EnterLockScope())
{
// Что-то делаем
}
}
}
Здесь _lock может быть любым типом блокировки, реализующим ILockPattern. EnterLockScope будет выдавать disposable контекст блокировки.
Источник: https://steven-giesel.com/blogPost/d7f923b3-13ff-4ecc-8b8f-d847ae581f68/a-new-lock-type-in-net-9
👍24
День 1831. #ЧтоНовенького #CSharp13
Три новых метода LINQ в .NET 9
Продолжаем заглядывать в будущее .NET. Сегодня посмотрим, какие новые методы будут добавлены во всеми любимый LINQ.
CountBy
Многие функции LINQ имеют расширение By, в котором можно предоставить функцию выбора для группировки элементов. Например, MinBy, MaxBy, DistinctBy и т. д. Теперь у нас есть новый: CountBy. Он группирует элементы по функции селектора и возвращает перечисление KeyValuePairs. Ключ — это объект, а значение — количество элементов в группе.
Index
Не то, чтоб совсем новинка, это можно делать и сейчас, но с другим синтаксисом. Index возвращает элемент и его индекс в коллекции:
Сейчас такого можно добиться, используя перегрузку метода Select:
Интересно, что в методе Index решили поменять порядок индекса и элемента. Оказывается, это было осознанным решением команды дотнет: «Мы решили сначала возвращать индекс, а затем значение, потому что это кажется более естественным, несмотря на то, что существующий метод Select() сначала возвращает значение, а потом индекс.»
AggregateBy
Этот метод похож на CountBy, но вместо подсчёта элементов он их агрегирует. Вы можете предоставить начальное значение и функцию агрегирования. Функция получает текущее агрегированное значение и текущий элемент в качестве параметров и должна вернуть новое агрегированное значение.
Источник: https://steven-giesel.com/blogPost/0594ba85-356b-47f1-89a9-70e9761c582e/three-new-linq-methods-in-net-9
Три новых метода LINQ в .NET 9
Продолжаем заглядывать в будущее .NET. Сегодня посмотрим, какие новые методы будут добавлены во всеми любимый LINQ.
CountBy
Многие функции LINQ имеют расширение By, в котором можно предоставить функцию выбора для группировки элементов. Например, MinBy, MaxBy, DistinctBy и т. д. Теперь у нас есть новый: CountBy. Он группирует элементы по функции селектора и возвращает перечисление KeyValuePairs. Ключ — это объект, а значение — количество элементов в группе.
public record Person(string FirstName, string LastName);
List<Person> people =
[
new("Steve", "Jobs"),
new("Steve", "Carell"),
new("Elon", "Musk")
];
foreach (var p in people.CountBy(p => p.FirstName))
Console.WriteLine($"{p.Value} людей с именем {p.Key}");
Вывод:
2 людей с именем Steve
1 людей с именем Elon
Index
Не то, чтоб совсем новинка, это можно делать и сейчас, но с другим синтаксисом. Index возвращает элемент и его индекс в коллекции:
foreach (var (index, item) in people.Index())
Console.WriteLine($"№{index}: {item}");
Сейчас такого можно добиться, используя перегрузку метода Select:
foreach (var (item, index) in
people.Select((item, index) => (item, index)))
{
Console.WriteLine($"№{index}: {item}");
}
Интересно, что в методе Index решили поменять порядок индекса и элемента. Оказывается, это было осознанным решением команды дотнет: «Мы решили сначала возвращать индекс, а затем значение, потому что это кажется более естественным, несмотря на то, что существующий метод Select() сначала возвращает значение, а потом индекс.»
AggregateBy
Этот метод похож на CountBy, но вместо подсчёта элементов он их агрегирует. Вы можете предоставить начальное значение и функцию агрегирования. Функция получает текущее агрегированное значение и текущий элемент в качестве параметров и должна вернуть новое агрегированное значение.
public record Person(
string FirstName,
string Department,
int Salary);
List<Person> people =
[
new("Jobs", "Sales", 100),
new("Carell", "Sales", 120),
new("Musk", "IT", 290)
];
var aggregateBy = people.AggregateBy(
p => p.Department,
x => 0,
(x, y) => x + y.Salary
);
foreach (var kvp in aggregateBy)
Console.WriteLine($"ФОТ отдела {kvp.Key}: {kvp.Value}");
Вывод:
ФОТ отдела Sales: 220
ФОТ отдела IT: 290
Источник: https://steven-giesel.com/blogPost/0594ba85-356b-47f1-89a9-70e9761c582e/three-new-linq-methods-in-net-9
👍25
День 1840. #ЧтоНовенького #CSharp13
Улучшения SearchValues в .NET 9
Продолжаем заглядывать в будущее .NET.
Класс System.Buffers.SearchValues<T>, представленный в .NET 8, предназначен для эффективного поиска набора байтов или символов в другом наборе, например, в реализации String.IndexOfAny(char[]). При создании экземпляра SearchValues<T> все данные, необходимые для оптимизации будущего поиска, вычисляются заранее и выбирается наиболее эффективный алгоритм поиска.
Вот простой пример:
Пока мы ограничены поиском только символов. Но гораздо чаще мы хотим искать строковые значения, то есть текст, внутри другого текста. В .NET 9 обещают это ввести! Появились новые перегрузки, и SearchValues можно будет применять в поиске текста:
Кроме того, есть предложение добавить SearchValues в регулярные выражения.
Источник: https://steven-giesel.com/blogPost/080c8f82-f376-489f-a304-72d419978294/searchvalues-object-become-better-with-net-9
Улучшения SearchValues в .NET 9
Продолжаем заглядывать в будущее .NET.
Класс System.Buffers.SearchValues<T>, представленный в .NET 8, предназначен для эффективного поиска набора байтов или символов в другом наборе, например, в реализации String.IndexOfAny(char[]). При создании экземпляра SearchValues<T> все данные, необходимые для оптимизации будущего поиска, вычисляются заранее и выбирается наиболее эффективный алгоритм поиска.
Вот простой пример:
var searchValues = SearchValues.Create(
new[] { 'a', 'e', 'i', 'o', 'u' });
Console.WriteLine(
ContainsVowel("Hello, World!")); // True
bool ContainsVowel(ReadOnlySpan<char> text)
=> text.ContainsAny(searchValues);
Пока мы ограничены поиском только символов. Но гораздо чаще мы хотим искать строковые значения, то есть текст, внутри другого текста. В .NET 9 обещают это ввести! Появились новые перегрузки, и SearchValues можно будет применять в поиске текста:
var names = SearchValues.Create(
["Steven", "Sherlock", "Holmes", "Michael", "Scott"],
StringComparison.OrdinalIgnoreCase);
var text = "This is Steven and Michael. You know Michael Scott from the office.";
Console.WriteLine(
MemoryExtensions.ContainsAny(text, names)); // True
Кроме того, есть предложение добавить SearchValues в регулярные выражения.
Источник: https://steven-giesel.com/blogPost/080c8f82-f376-489f-a304-72d419978294/searchvalues-object-become-better-with-net-9
👍22
День 1901. #ЧтоНовенького #Csharp13
Простая Обработка Асинхронных Задач по Мере их Завершения в .NET 9
Допустим, вам нужно обрабатывать результаты нескольких асинхронных задач по мере их завершения. Мы можем предположить, что это обращения к API, БД, файловой системе, долгие вычисления и т.п.
Например, симулируем некоторую задачу Calculate, которая выдаёт результат через случайное (от 0,5 до 5 секунд) время:
И пусть у нас есть несколько таких задач:
Если мы будем ожидать окончания всех задач, и выводить их результаты, то получим результаты в порядке очереди (от 1 до 10) вне зависимости от того, когда завершилась каждая задача, потому что мы сначала ждём завершения всех:
Более эффективно было бы ожидать завершения каждой задачи и обрабатывать её результат сразу. До сих пор существовал некоторый обходной путь, позволяющий это сделать. Примерно так:
Это работает, но логика не совсем очевидная. Кроме того, должна быть доступна операция удаления выполненной задачи (т.е. должен быть список задач).
В .NET 9 появился новый метод, позволяющий упростить эту логику, Tasks.WhenEach, который выдаёт IAsyncEnumerable. Тогда код выше сокращается до:
Теперь вы избавлены от необходимости иметь список задач в List’е (метод принимает Task<T>[], ReadOnlySpan<Task<T>>, IEnumerable<Task<T>>. Кроме того, он использует новые возможности ключевого слова params.
Аналогичная функциональность уже существует в библиотеке AsyncEx Стивена Клири. Там это метод OrderByCompletion. Поэтому можно эмулировать метод WhenEach так:
Источники:
- https://youtu.be/WqXgl8EZzcs
- https://github.com/dotnet/runtime/issues/61959
- https://devblogs.microsoft.com/pfxteam/processing-tasks-as-they-complete/
Простая Обработка Асинхронных Задач по Мере их Завершения в .NET 9
Допустим, вам нужно обрабатывать результаты нескольких асинхронных задач по мере их завершения. Мы можем предположить, что это обращения к API, БД, файловой системе, долгие вычисления и т.п.
Например, симулируем некоторую задачу Calculate, которая выдаёт результат через случайное (от 0,5 до 5 секунд) время:
async Task<int> Calculate(int order)
{
var wait = Random.Shared.Next(500, 5000);
await Task.Delay(wait);
return order;
}
И пусть у нас есть несколько таких задач:
var tasks = Enumerable.Range(1,10)
.Select(Calculate).ToList();
Если мы будем ожидать окончания всех задач, и выводить их результаты, то получим результаты в порядке очереди (от 1 до 10) вне зависимости от того, когда завершилась каждая задача, потому что мы сначала ждём завершения всех:
var results = await Task.WhenAll(tasks);
foreach (var r in results)
Console.WriteLine(r);
Более эффективно было бы ожидать завершения каждой задачи и обрабатывать её результат сразу. До сих пор существовал некоторый обходной путь, позволяющий это сделать. Примерно так:
while (tasks.Any())
{
var finished = await Task.WhenAny(tasks);
tasks.Remove(finished);
Console.WriteLine(await finished);
}
Это работает, но логика не совсем очевидная. Кроме того, должна быть доступна операция удаления выполненной задачи (т.е. должен быть список задач).
В .NET 9 появился новый метод, позволяющий упростить эту логику, Tasks.WhenEach, который выдаёт IAsyncEnumerable. Тогда код выше сокращается до:
await foreach (var finished
in Task.WhenEach(tasks))
{
Console.WriteLine(await finished);
}
Теперь вы избавлены от необходимости иметь список задач в List’е (метод принимает Task<T>[], ReadOnlySpan<Task<T>>, IEnumerable<Task<T>>. Кроме того, он использует новые возможности ключевого слова params.
Аналогичная функциональность уже существует в библиотеке AsyncEx Стивена Клири. Там это метод OrderByCompletion. Поэтому можно эмулировать метод WhenEach так:
async IAsyncEnumerable<T> WhenEach(Task<T>[] tasks) {
foreach (Task<T> task in tasks.OrderByCompletion())
yield return await task;
}
}
await foreach (var task in WhenEach(tasks))
{
Console.WriteLine(await finished);
}Источники:
- https://youtu.be/WqXgl8EZzcs
- https://github.com/dotnet/runtime/issues/61959
- https://devblogs.microsoft.com/pfxteam/processing-tasks-as-they-complete/
👍40
День 1958. #ЧтоНовенького #CSharp13
Типы-Расширения
Начиная с C# 3, методы расширения позволяют добавлять методы к базовому типу, даже если вы не можете изменить его код. LINQ — пример набора методов расширения IEnumerable<T>. Методы расширения LINQ выглядят так, как если бы они были методами экземпляра базового типа.
C# 13 идёт дальше, добавляя типы-расширения. Это новая разновидность типов языка, которая предоставляет элементы расширения для базового типа. Расширение включает методы, свойства и другие члены, которые могут быть как экземплярными, так и статическими. Типы-расширения экземпляра не могут хранить состояние, например, не могут включать экземплярные поля, но могут получать доступ к состоянию базового типа.
Виды
1. Неявные
Применяются ко всем экземплярам базового типа так же, как методы расширения.
2. Явные
Применяются только к экземплярам базового типа, которые были преобразованы в тип явного расширения (по аналогии с явной реализацией интерфейсов).
Пусть у нас есть базовые типы и нет доступа к изменению их кода:
Небольшой код LINQ поможет определить, является ли человек лидом. Но мы не хотим писать его каждый раз, поэтому можно написать метод расширения и контролировать доступ к нему через пространства имён. Или можно использовать неявный тип-расширение и предоставить свойство IsLead всем экземплярам Person:
Явные расширения позволяют предоставлять дополнительные возможности конкретным экземплярам типа. Например, чтобы узнать, какие команды возглавляет человек, явное расширение может предоставлять свойство Teams только лидам (через приведение экземпляра Person к типу Lead):
Как неявные, так и явные типы-расширения поддерживают и статические, и экземплярные члены. Вариант использования статических членов — предоставить значения по умолчанию, специфичные для вашего сценария. В данном случае у нас одна компания, и указывать её каждый раз при создании человека неудобно:
С точки зрения использования типы-расширения позволяют упростить код, обеспечивающий важную работу и логику приложения, настраивая конкретные экземпляры базовых объектов под ваши нужды. С технической точки зрения типы-расширения представляют собой усовершенствование методов расширения, которые вы используете сегодня.
Источник: https://devblogs.microsoft.com/dotnet/dotnet-build-2024-announcements/
Типы-Расширения
Начиная с C# 3, методы расширения позволяют добавлять методы к базовому типу, даже если вы не можете изменить его код. LINQ — пример набора методов расширения IEnumerable<T>. Методы расширения LINQ выглядят так, как если бы они были методами экземпляра базового типа.
C# 13 идёт дальше, добавляя типы-расширения. Это новая разновидность типов языка, которая предоставляет элементы расширения для базового типа. Расширение включает методы, свойства и другие члены, которые могут быть как экземплярными, так и статическими. Типы-расширения экземпляра не могут хранить состояние, например, не могут включать экземплярные поля, но могут получать доступ к состоянию базового типа.
Виды
1. Неявные
Применяются ко всем экземплярам базового типа так же, как методы расширения.
2. Явные
Применяются только к экземплярам базового типа, которые были преобразованы в тип явного расширения (по аналогии с явной реализацией интерфейсов).
Пусть у нас есть базовые типы и нет доступа к изменению их кода:
public class Person()
{
public string FirstName { get; init; }
public string LastName { get; init; }
public Company Company { get; init; }
}
public class Company()
{
public string Name { get; init; }
public List<Team> Teams { get; init; }
}
public class Team()
{
public string TeamName { get; init; }
public Person Lead { get; init; }
public IEnumerable<Person> Members { get; init; }
}
Небольшой код LINQ поможет определить, является ли человек лидом. Но мы не хотим писать его каждый раз, поэтому можно написать метод расширения и контролировать доступ к нему через пространства имён. Или можно использовать неявный тип-расширение и предоставить свойство IsLead всем экземплярам Person:
public implicit extension PersonExtension for Person
{
public bool IsLead
=> this.Company
.Teams
.Any(team => team.Lead == this);
}
// Использование
if (person.IsLead) { … }
Явные расширения позволяют предоставлять дополнительные возможности конкретным экземплярам типа. Например, чтобы узнать, какие команды возглавляет человек, явное расширение может предоставлять свойство Teams только лидам (через приведение экземпляра Person к типу Lead):
public explicit extension Lead for Person
{
public IEnumerable<Team> Teams
=> this.Company
.Teams
.Where(team => team.Lead == this);
}
Как неявные, так и явные типы-расширения поддерживают и статические, и экземплярные члены. Вариант использования статических членов — предоставить значения по умолчанию, специфичные для вашего сценария. В данном случае у нас одна компания, и указывать её каждый раз при создании человека неудобно:
public implicit extension CompanyExtension for Company
{
private static Company company
= new Company("C# Design");
public static Person CreatePerson(
string firstName, string lastName)
=> new(firstName, lastName, company);
}
// Использование
var person = Company
.CreatePerson("Jon", "Smith");
// … добавляем ещё людей и команды
if (person.IsLead)
{
Lead lead = person;
PrintReport(lead.Teams);
}
С точки зрения использования типы-расширения позволяют упростить код, обеспечивающий важную работу и логику приложения, настраивая конкретные экземпляры базовых объектов под ваши нужды. С технической точки зрения типы-расширения представляют собой усовершенствование методов расширения, которые вы используете сегодня.
Источник: https://devblogs.microsoft.com/dotnet/dotnet-build-2024-announcements/
👍24
День 1965. #ЧтоНовенького #CSharp13
Полуавтоматические Свойства
Полуавтоматические свойства позволяют вам добавлять логику в методы get и set, не создавая явно приватного поля как для полноценного свойства, а используя ключевое слово field.
Если это звучит знакомо, то вы не ошибаетесь. Эта функциональность давно была готова, и её пытались добавить в C# на протяжении нескольких последних версий. Проблема состояла в добавлении нового ключевого слова в язык, т.е. добавлении ломающего изменения.
Автоматические свойства
Автоматические свойства появились очень давно и используются повсеместно:
Этот код за кулисами создаёт приватное поле и два метода для получения и задания его значения. Т.е. следующий код эквивалентен коду выше:
Но что, если мы хотим добавить какую-то логику в метод get или set автосвойства? К примеру, мы хотим, чтобы имя всегда задавалось без лишних пробелов. До сих пор нам ничего не оставалось, кроме как отказаться от автосвойства и использовать полноценное свойство, явно создавая поле для него:
Полуавтоматические свойства
Начиная с С#13 мы сможем использовать для этого ключевое слово field, и не создавать поле явно:
Интересно, что, даже если вам нужно изменить только set, то придётся реализовать и get (пусть и в таком элементарном виде). То есть, его нельзя оставить в виде
Очевидно, что добавление нового ключевого слова может сломать чей-то существующий код. Если в вашем коде использовалось поле с названием field, то в такой ситуации будет не понятно, нужно обращаться к этому полю, либо к неявному полю для свойства Name.
Microsoft годами пыталась избегать ломающих изменений, в частности при вводе новых ключевых слов. Раньше они создавали сложные правила для разрешения этих конфликтов. Но теперь решили пойти другим путём. На примере этой функции они хотят протестировать инструмент «раннего предупреждения» о ломающем изменении. Теперь, когда вы обновляете .NET SDK на новую версию (при этом используя старую версию .NET в проектах), анализатор кода в таких случаях будет выдавать предупреждение, что этот код не будет работать в новой версии языка и предлагать автоматический рефакторинг кода под новую версию.
В этом случае для обращения к полю field из свойства нужно будет добавить @:
Источник: https://www.youtube.com/watch?v=3jb9Du9pMes
Полуавтоматические Свойства
Полуавтоматические свойства позволяют вам добавлять логику в методы get и set, не создавая явно приватного поля как для полноценного свойства, а используя ключевое слово field.
Если это звучит знакомо, то вы не ошибаетесь. Эта функциональность давно была готова, и её пытались добавить в C# на протяжении нескольких последних версий. Проблема состояла в добавлении нового ключевого слова в язык, т.е. добавлении ломающего изменения.
Автоматические свойства
Автоматические свойства появились очень давно и используются повсеместно:
public class Test
{
public string Name { get; set; }
}
Этот код за кулисами создаёт приватное поле и два метода для получения и задания его значения. Т.е. следующий код эквивалентен коду выше:
csharp
public class Test
{
private string _name;
public string get_Name()
{
return _name;
}
public void set_Name(string name)
{
_name = name;
}
}
Но что, если мы хотим добавить какую-то логику в метод get или set автосвойства? К примеру, мы хотим, чтобы имя всегда задавалось без лишних пробелов. До сих пор нам ничего не оставалось, кроме как отказаться от автосвойства и использовать полноценное свойство, явно создавая поле для него:
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value.Trim();
}
}
Полуавтоматические свойства
Начиная с С#13 мы сможем использовать для этого ключевое слово field, и не создавать поле явно:
public string Name {
get => field;
set => field = value.Trim();
}Интересно, что, даже если вам нужно изменить только set, то придётся реализовать и get (пусть и в таком элементарном виде). То есть, его нельзя оставить в виде
get;. Возможно, к релизу это исправят.Очевидно, что добавление нового ключевого слова может сломать чей-то существующий код. Если в вашем коде использовалось поле с названием field, то в такой ситуации будет не понятно, нужно обращаться к этому полю, либо к неявному полю для свойства Name.
Microsoft годами пыталась избегать ломающих изменений, в частности при вводе новых ключевых слов. Раньше они создавали сложные правила для разрешения этих конфликтов. Но теперь решили пойти другим путём. На примере этой функции они хотят протестировать инструмент «раннего предупреждения» о ломающем изменении. Теперь, когда вы обновляете .NET SDK на новую версию (при этом используя старую версию .NET в проектах), анализатор кода в таких случаях будет выдавать предупреждение, что этот код не будет работать в новой версии языка и предлагать автоматический рефакторинг кода под новую версию.
В этом случае для обращения к полю field из свойства нужно будет добавить @:
private string field;
public string Field {
get => @field;
set => @field = value.Trim();
}
Источник: https://www.youtube.com/watch?v=3jb9Du9pMes
👍31
День 1982. #ЧтоНовенького #CSharp13
ReadOnlySet<T> в .NET 9
Очередная, шестая, превью версия .NET 9 представит новый тип ReadOnlySet<T>. Это множество только для чтения, аналогичное ReadOnlyCollection<T>. Посмотрим, как это работает и зачем оно добавлено.
IReadOnlySet недостаточно
У нас уже есть интерфейс IReadOnlySet<T>. Например, его реализует FrozenSet. Почему его недостаточно? Рассмотрим List<T> и IReadOnlyList<T>, которые страдают от той же проблемы:
Вывод (внезапно):
Конечно, потребителям вашего API не следует этого делать, но вы не можете этого предотвратить. Вот почему у нас есть AsReadOnly:
AsReadOnly возвращает ReadOnlyCollection<T>, поэтому приведение её к изменяемому списку невозможно.
Той же проблемой страдает и IReadOnlySet<T>. Именно для этого создан ReadOnlySet<T>:
Неправильный обходной путь
Если попытаться реализовать собственный способ создания множества только для чтения, пришлось бы создать что-то вроде ImmutableHashSet<T> или FrozenSet.
Хотя технически это работает, у этого две основные проблемы:
1. Неизменяемые коллекции, такие как ImmutableHashSet<T> или FrozenSet, должны cкопировать всю коллекцию в свою память, чтобы гарантировать неизменяемость. Это может быть пустой тратой памяти и циклов процессора.
2. Это не совсем множество только для чтения. «Только для чтения» и «неизменяемый» — это два разных понятия, как мы разобрали недавно.
Подробнее о предложении новинки тут.
Источник: https://steven-giesel.com/blogPost/f368c7d3-488e-4bea-92b4-abf176353fa3/readonlysett-in-net-9
ReadOnlySet<T> в .NET 9
Очередная, шестая, превью версия .NET 9 представит новый тип ReadOnlySet<T>. Это множество только для чтения, аналогичное ReadOnlyCollection<T>. Посмотрим, как это работает и зачем оно добавлено.
IReadOnlySet недостаточно
У нас уже есть интерфейс IReadOnlySet<T>. Например, его реализует FrozenSet. Почему его недостаточно? Рассмотрим List<T> и IReadOnlyList<T>, которые страдают от той же проблемы:
var list = new List<int> { 1, 2, 3 };
IReadOnlyList<int> readOnlyList = list;
// Вы можете привести это обратно к List<T> и изменить
var list2 = (List<int>)readOnlyList;
list2.Add(4);
Console.WriteLine($"List: {list.Count}");
Console.WriteLine($"ReadOnlyList: {readOnlyList.Count}");
Console.WriteLine($"List2: {list2.Count}");Вывод (внезапно):
List: 4
ReadOnlyList: 4
List2: 4
Конечно, потребителям вашего API не следует этого делать, но вы не можете этого предотвратить. Вот почему у нас есть AsReadOnly:
List<int> list = [1, 2, 3];
var readOnlyList = list.AsReadOnly();
// Вы не можете привести это к List<T>
var list2 = (List<int>)readOnlyList; // Исключение
AsReadOnly возвращает ReadOnlyCollection<T>, поэтому приведение её к изменяемому списку невозможно.
Той же проблемой страдает и IReadOnlySet<T>. Именно для этого создан ReadOnlySet<T>:
var set = new HashSet<int> { 1, 2, 3 };
var readonlySet = new ReadOnlySet<int>(set);Неправильный обходной путь
Если попытаться реализовать собственный способ создания множества только для чтения, пришлось бы создать что-то вроде ImmutableHashSet<T> или FrozenSet.
var set = new HashSet<int> { 1, 2, 3 };
var readOnly = set.ToFrozenSet(); // или ToImmutableHashSet()Хотя технически это работает, у этого две основные проблемы:
1. Неизменяемые коллекции, такие как ImmutableHashSet<T> или FrozenSet, должны cкопировать всю коллекцию в свою память, чтобы гарантировать неизменяемость. Это может быть пустой тратой памяти и циклов процессора.
2. Это не совсем множество только для чтения. «Только для чтения» и «неизменяемый» — это два разных понятия, как мы разобрали недавно.
Подробнее о предложении новинки тут.
Источник: https://steven-giesel.com/blogPost/f368c7d3-488e-4bea-92b4-abf176353fa3/readonlysett-in-net-9
👍11
День 1989. #CSharp13
⚡️BREAKING! Типов-Расширений не Будет в C#13
О типах-расширениях я уже рассказывал, их с помпой представили Мэдс Торгенсен и Дастин Кэмбелл аж на Microsoft Build как главную новинку C# 13. Но позавчера в очередном обзоре новинок Кэтлин Доллард, главный программный менеджер .NET, сообщила, что «разработка и реализация потребуют больше времени. Ищите типы расширений в ранних предварительных версиях C#14 (NET 10)».
Подробности проблемы были описаны в саммари встречи о дизайне языка. Идея с генератором кода расширений, использовавшего
Мы видим два основных подхода:
1) Полностью посвятить себя поддержке во время выполнения. Если бы мы хотели пойти по пути поддержки во время выполнения, мы бы не полагались только на Unsafe.As; мы бы обновили среду выполнения, чтобы напрямую разрешить присвоение ref базового типа типу расширения. Это было бы безопаснее на уровне выполнения, поскольку даёт примитив, который можно проверить. Это также может дать лучшую отправную точку для дальнейшей поддержки членов интерфейса. Однако, к сожалению, мы не сможем достаточно обкатать функциональность до выхода .NET 9. Мы не боимся, что обещанные функции не будут выпущены, если они не готовы. Но это, скорее всего, будет означать, что расширения не будут выпущены в стабильной форме, по крайней мере, до выхода .NET 11.
2) Вернуться к рассмотрению расширений как сахара над статическими методами для типов расширений, что очень похоже на современные методы расширения. Здесь возникают повышенные трудности с представлением компилятора: публичная модель (сахар) и внутренняя реализация модели этих типов должны будут конвертироваться между собой. И дальнейшее расширение сахара возможно, но будет иметь некоторые сложности. Также потребуется больше переписывать тела членов, чтобы имитировать семантику методов экземпляра. Преимущества:
- Такая версия может быть выпущена в период с выходом .NET 9, по крайней мере, в предварительной версии.
- Существующие методы расширения фактически могут быть преобразованы в новую форму. Это будет не идеально; в частности, любой класс-держатель методов расширения, имеющий расширения для нескольких типов, скорее всего, не сможет работать.»
По итогам встречи было принято решение остановиться на втором варианте, но спустя чуть менее, чем месяц, видимо, разработчики языка решили подумать ещё.
Что ж, лично я могу только поддержать стремление выпускать только полноценно готовые новые фичи. На примере первичных конструкторов мы уже видели, что получается, когда создатели языка идут по другому пути.
С другой стороны, по большому счёту из новинок #CSharp13 теперь и выделить то нечего. Так, мелкие улучшения в довольно нишевых сценариях.
Что думаете?
Источники:
- https://devblogs.microsoft.com/dotnet/csharp-13-explore-preview-features/#update-on-extension-types
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#extensions
⚡️BREAKING! Типов-Расширений не Будет в C#13
О типах-расширениях я уже рассказывал, их с помпой представили Мэдс Торгенсен и Дастин Кэмбелл аж на Microsoft Build как главную новинку C# 13. Но позавчера в очередном обзоре новинок Кэтлин Доллард, главный программный менеджер .NET, сообщила, что «разработка и реализация потребуют больше времени. Ищите типы расширений в ранних предварительных версиях C#14 (NET 10)».
Подробности проблемы были описаны в саммари встречи о дизайне языка. Идея с генератором кода расширений, использовавшего
Unsafe.As, «была отвергнута архитекторами среды выполнения .NET как принципиально небезопасная. Существуют потенциальные проблемы с псевдонимами, которые могут привести к путанице в частях JIT, особенно в редких сценариях оптимизации, и нельзя гарантировать, что они всегда будут безопасно обрабатываться с учётом предполагаемых вариантов использования. Нужно будет научить среду выполнения обрабатывать эти сценарии. Это долгосрочная функция, и нам потребуется время, чтобы опробовать её с реальными пользователями, прежде чем мы будем полностью уверены, что у нас правильный дизайн, поэтому мы хотели вернуться к чертёжной доске. Мы видим два основных подхода:
1) Полностью посвятить себя поддержке во время выполнения. Если бы мы хотели пойти по пути поддержки во время выполнения, мы бы не полагались только на Unsafe.As; мы бы обновили среду выполнения, чтобы напрямую разрешить присвоение ref базового типа типу расширения. Это было бы безопаснее на уровне выполнения, поскольку даёт примитив, который можно проверить. Это также может дать лучшую отправную точку для дальнейшей поддержки членов интерфейса. Однако, к сожалению, мы не сможем достаточно обкатать функциональность до выхода .NET 9. Мы не боимся, что обещанные функции не будут выпущены, если они не готовы. Но это, скорее всего, будет означать, что расширения не будут выпущены в стабильной форме, по крайней мере, до выхода .NET 11.
2) Вернуться к рассмотрению расширений как сахара над статическими методами для типов расширений, что очень похоже на современные методы расширения. Здесь возникают повышенные трудности с представлением компилятора: публичная модель (сахар) и внутренняя реализация модели этих типов должны будут конвертироваться между собой. И дальнейшее расширение сахара возможно, но будет иметь некоторые сложности. Также потребуется больше переписывать тела членов, чтобы имитировать семантику методов экземпляра. Преимущества:
- Такая версия может быть выпущена в период с выходом .NET 9, по крайней мере, в предварительной версии.
- Существующие методы расширения фактически могут быть преобразованы в новую форму. Это будет не идеально; в частности, любой класс-держатель методов расширения, имеющий расширения для нескольких типов, скорее всего, не сможет работать.»
По итогам встречи было принято решение остановиться на втором варианте, но спустя чуть менее, чем месяц, видимо, разработчики языка решили подумать ещё.
Что ж, лично я могу только поддержать стремление выпускать только полноценно готовые новые фичи. На примере первичных конструкторов мы уже видели, что получается, когда создатели языка идут по другому пути.
С другой стороны, по большому счёту из новинок #CSharp13 теперь и выделить то нечего. Так, мелкие улучшения в довольно нишевых сценариях.
Что думаете?
Источники:
- https://devblogs.microsoft.com/dotnet/csharp-13-explore-preview-features/#update-on-extension-types
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-12.md#extensions
👍13
День 2003. #ЧтоНовенького #CSharp13
Regex.EnumerateSplits
В 6м превью .NET 9 представлен новый метод класса Regex, который позволяет разбивать входные данные по шаблону без выделения памяти.
Класс Regex предоставляет метод Split, по концепции аналогичный методу String.Split. С помощью String.Split вы предоставляете один или несколько разделителей (символов или строк), и реализация разбивает входной текст по этим разделителям. Вот пример использования String.Split:
который выводит:
Regex предоставляет аналогичный метод Split, но вместо указания разделителя в виде символа или строки он указывается как шаблон регулярного выражения:
Вывод:
Однако Regex.Split принимает в качестве входных данных только строки и не поддерживает входные данные в виде ReadOnlySpan<char>, а также выводит полный набор результатов в виде массива строк, что требует выделения как массива строк для хранения результатов, так и выделения строк для каждого результата. В .NET 9 новый метод EnumerateSplits позволяет выполнять ту же операцию, но с входными данными в виде ReadOnlySpan<char> и без какого-либо выделения памяти. Он принимает ReadOnlySpan<char> и возвращает перечислитель (ValueSplitEnumerator) результатов в виде диапазона (Range). В следующем примере показано использование Regex.EnumerateSplits:
Вывод будет такой же, как в предыдущем примере, но без выделения памяти. Здесь r – диапазон, например, {0..1} для первого результата, и на консоль выводится срез входных данных, соответствующий диапазону (это результат типа ReadOnlySpan<char>, поэтому он помещён в интерполированную строку для использования в Console.WriteLine.
Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview6/libraries.md
Regex.EnumerateSplits
В 6м превью .NET 9 представлен новый метод класса Regex, который позволяет разбивать входные данные по шаблону без выделения памяти.
Класс Regex предоставляет метод Split, по концепции аналогичный методу String.Split. С помощью String.Split вы предоставляете один или несколько разделителей (символов или строк), и реализация разбивает входной текст по этим разделителям. Вот пример использования String.Split:
foreach (var s in "Hello, world! How are you?".Split('w'))
{
Console.WriteLine(s);
}который выводит:
Hello,
orld! Ho
are you?
Regex предоставляет аналогичный метод Split, но вместо указания разделителя в виде символа или строки он указывается как шаблон регулярного выражения:
foreach (var s in Regex.Split(
"Hello, world! How are you?", "[aeiou]"))
{
Console.WriteLine(s);
}
Вывод:
H
ll
, w
rld! H
w
r
y
?
Однако Regex.Split принимает в качестве входных данных только строки и не поддерживает входные данные в виде ReadOnlySpan<char>, а также выводит полный набор результатов в виде массива строк, что требует выделения как массива строк для хранения результатов, так и выделения строк для каждого результата. В .NET 9 новый метод EnumerateSplits позволяет выполнять ту же операцию, но с входными данными в виде ReadOnlySpan<char> и без какого-либо выделения памяти. Он принимает ReadOnlySpan<char> и возвращает перечислитель (ValueSplitEnumerator) результатов в виде диапазона (Range). В следующем примере показано использование Regex.EnumerateSplits:
ReadOnlySpan<char> input =
"Hello, world! How are you?";
foreach (Range r in
Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"{input[r]}");
}
Вывод будет такой же, как в предыдущем примере, но без выделения памяти. Здесь r – диапазон, например, {0..1} для первого результата, и на консоль выводится срез входных данных, соответствующий диапазону (это результат типа ReadOnlySpan<char>, поэтому он помещён в интерполированную строку для использования в Console.WriteLine.
Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview6/libraries.md
👍21