.NET Разработчик
6.54K subscribers
442 photos
3 videos
14 files
2.12K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День шестьсот пятый. #ЧтоНовенького #CSharp9
C#9: Улучшенное Сопоставление с Образцом
В своё время VB стал популярным из-за своей выразительности. Программу можно было читать почти как обычный английский текст. Для сравнения, C# и C++ оптимизированы для краткости синтаксиса и производительности. Это сделало их быстрыми, но в некоторых случаях трудным для чтения. Сравните VB:
If Not a And b Then

Else

EndIf

И C#:
if(!(a) && b)
{

}
else
{

}

Это простой пример, но вы можете представить, что люди создают намного более сложные логические выражения, содержащие множество символов.

C#9 теперь поддерживает несколько новых ключевых слов: not, and и or. Это упрощает чтение некоторых выражений, но вы по-прежнему можете использовать более короткий синтаксис с использованием символов:
if(s is not string)
на мой взгляд, проще для чтения, чем
if(!(s is string))

Сопоставление с образцом
Эти новые операторы полезны при сопоставлении с образцом. Кстати, теперь вы также можете использовать реляционные шаблоны, такие как >=, > и т.д., непосредственно в выражениях:
record Person(int? Weight);
Person p = new Person(175);
var category = p.Weight switch
{
< 150 => "лёгкий",
>= 150 and < 200 => "средний",
not null => "неизвестно",
null => "ошибка"
};

До C#9 некоторые шаблоны было невозможно выразить, например, not null. В основном это было ограничением компилятора, потому что !null не был допустимым синтаксическим токеном в C#. То же самое относится к таким выражениям, как
>= 150 and < 200
До C#9 компилятор не знал, как анализировать &&, за которым следует другой логический символ, например <. В выражении сопоставления с образцом компилятор знает, с какой переменной мы сопоставляем, и понимает, что означает это выражение, без повторяющихся объявлений переменных, которые только загромождали выражение. В итоге мы получили синтаксис, который намного проще понять людям, читающим код.

Источник: https://blog.miguelbernard.com/c-9-0-improved-pattern-matching/
День шестьсот сорок четвёртый. #ЧтоНовенького #CSharp9
C# 9 Неизвестные Фишки
В C# 9 появилось много новых возможностей (см. посты по тегу #CSharp9) и несколько очень полезных малоизвестных вещей.

Инициализаторы модулей
Этот серьёзный недостаток, который до сих пор можно было исправить только некоторыми сторонними инструментами, теперь поддерживается в .NET. Инициализаторы модулей позволят библиотекам выполнять инициализацию при загрузке с минимальными накладными расходами и без необходимости явного вызова со стороны пользовательского кода. В основном это встречалось в библиотеках тестов, где вы хотите что-то инициализировать перед запуском одного, нескольких или даже всех тестов в сборке. Например, его можно использовать для переопределения некоторых статических переменных перед запуском тестирования. Чтобы использовать эту функцию, пометьте метод атрибутом ModuleInitializer. Не все методы могут поддерживать эту функцию. Метод должен:
- быть статическим
- без параметров
- возвращать void
- не быть обобщённым
- не содержаться в обобщённом классе
- быть доступным из содержащего его модуля (internal или public).
[ModuleInitializer]
public static void Magic() { … }

Упрощенная проверка на null
Для реализации проверок на null требуется много шаблонного кода и хорошая дисциплина для их последовательного применения. C# 9 решает эту проблему, предоставляя упрощённый синтаксис. Просто добавьте ! в конце имени параметра метода:
public void Before(string name) {
if (name is null)
throw new ArgumentNullException();
}
Следующий метод приведёт к аналогичному сгенерированному IL-коду:
public void Now(string name!) { }

Ковариантные Переопределения
Ковариантные переопределения теперь позволяют объявлять более конкретный тип при переопределении метода базового класса, содержащего менее конкретный тип возврата:
abstract class Animal {
public abstract Food GetFood();
}
class Tiger : Animal {
public override Meat GetFood() => …;
}

Это значительное улучшение, поскольку теперь вы можете избежать приведения возвращаемого значения во время выполнения при использовании дочернего типа:
var t = new Tiger();
// Раньше
Meat m = (Meat)t.GetFood();
// Сейчас
Meat m2 = t.GetFood();

Int Нативного Размера
Это новая конструкция, которая позволяет объявлять int, размер которого определяется платформой: 32 или 64 бита. nint и nuint - новые ключевые слова для этого.

Такая возможность уже существовала с IntPtr и UIntPtr. nint и nuint - это просто оболочки над этими типами, предоставляющие дополнительные возможности, такие как преобразование и арифметические операции, которые невозможны с IntPtr. Это может значительно оптимизировать производительность приложения при выполнении интенсивных вычислений, требующих низкоуровневого доступа и где важен каждый байт.

Источник: https://blog.miguelbernard.com/c-9-the-unknown-goodies/
День шестьсот шестидесятый. #ЧтоНовенького #EFCore5
Вместе с .NET 5 выпущено множество обновлений. Про новшества в C#9 я писал в постах с тегом #CSharp9. Теперь рассмотрим, что нового в Entity Framework Core 5.0.

Отношение многие-ко-многим
EF Core 5.0 поддерживает отношения многие-ко-многим без явной привязки вспомогательной таблицы. Рассмотрим следующие сущности постов в блоге и тегов:
public class Post {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag {
public int Id { get; set; }
public string Text { get; set; }
public ICollection<Post> Posts { get; set; }
}

Заметьте, что Post содержит коллекцию элементов Tag и наоборот. EF Core 5.0 по соглашению распознает это как отношение многие-ко-многим. То есть добавлять специальный код в OnModelCreating не требуется. Когда для создания базы данных используются миграции (или EnsureCreated), EF Core автоматически создаст вспомогательную таблицу. Например, в SQL Server для этой модели EF Core сгенерирует:
CREATE TABLE [Posts] (
[Id] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
CONSTRAINT [PK_Posts] PRIMARY KEY ([Id])
);
CREATE TABLE [Tag] (
[Id] int NOT NULL IDENTITY,
[Text] nvarchar(max) NULL,
CONSTRAINT [PK_Tag] PRIMARY KEY ([Id])
);
CREATE TABLE [PostTag] (
[PostsId] int NOT NULL,
[TagsId] int NOT NULL,

);

Создание и связывание экземпляров объектов Tag и Post приведёт к автоматическому обновлению вспомогательной таблицы. После вставки сообщений и тегов EF автоматически создаст строки во вспомогательной таблице. Для запросов Include и другие операции будут работать так же, как и для любых других отношений.

В отличие от EF6, EF Core также позволяет полностью настраивать вспомогательную таблицу. Например, приведенный ниже код настраивает отношение «многие-ко-многим» через вспомогательный объект, в котором вспомогательный объект также содержит свойство с полезными данными (PublicationDate):
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder
.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany()
.HasForeignKey(pt => pt.TagId),
j => j
.HasOne(pt => pt.Post)
.WithMany()
.HasForeignKey(pt => pt.PostId),
j => {
j.Property(pt => pt.PublicationDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}

Источник: https://docs.microsoft.com/ru-ru/ef/core/what-is-new/ef-core-5.0/whatsnew#many-to-many
День шестьсот шестьдесят шестой. #ЗаметкиНаПолях #CSharp9
Атрибуты для Свойств Записей в C# 9
Записи обеспечивают простое создание неизменяемых объектов, особенно при использовании первичного конструктора:
public record User(string Name, DateTime DOB);
Это значительно сокращает код. Рассмотрим ситуацию, когда вы хотите сериализовать запись, чтобы получить следующий результат:
{"User":"Jon Smith","DateOfBirth":"1970-01-01T00:00:00"}
Заметьте, что ключи не совпадают с именами свойств записи. Обычно это потребовало бы добавления к свойствам записи атрибута JsonPropertyAttribute. То есть в полной записи это выглядело бы так:
public record User {  
[JsonProperty("User")]
public string Name{get;init;}

[JsonProperty("DateOfBirth")]
public DateTime DOB{get;init;}
}

При использовании первичного конструктора есть соблазн сделать аналогично:
public record User(
[JsonProperty("User")]
string Name,
[JsonProperty("DateOfBirth")]
DateTime DOB
);

Но это неверно. В этом случае атрибуты добавятся к параметрам конструктора, а не к свойствам.

Решение заключается в указании цели, к которой применяется атрибут. Как сказано в документации Microsoft:
Атрибуты могут быть применены к синтезированному автоматическому свойству или его вспомогательному полю, используя указатель цели атрибута property: или field: соответственно для атрибутов, синтаксически применяемых к соответствующему параметру записи.

В итоге мы получим следующую запись:
public record User(
[property:JsonProperty("User")]
string Name,
[property:JsonProperty("DateOfBirth")]
DateTime DOB
);

Теперь можно сериализовать нашу запись и получить желаемый результат:
var data = new User("Jon Smith",new DateTime(1970,1,1));  
var serializedData = JsonConvert.SerializeObject(data);

// Вывод
{"User":"Jon Smith","DateOfBirth":"1970-01-01T00:00:00"}

Источник: https://www.c-sharpcorner.com/blogs/attributes-for-record-properties-in-c-sharp-9
День шестьсот восьмидесятый. #ЧтоНовенького #CSharp9
Ещё Раз про Сопоставления с Образцом
Я уже писал об изменениях в сопоставлении с образцом в C#9. Здесь же хочу привести некое саммари всех изменений с примерами:

1. Шаблоны типа используются для сопоставления с типом. Если тип входных данных соответствует типу, указанному в шаблоне, совпадение считается успешным.
object checkType = new int();
var getType = checkType switch {
string => "string",
int => "int",
_ => "obj"
};
Console.WriteLine(getType);
// Вывод: int

2. Реляционные шаблоны позволяют сопоставить входные данные с константами, используя знаки >, < или = (a также >= или <=):
var person = new Person("John", 42);
var person2 = new Person("Jane", 8);
var ageInRange = person switch {
//тип Person указан явно
Person(_, < 18) => "меньше 18",
//тип выводится компилятором
(_, > 18) => "больше 18",
(_, 18) => "18!"
};
Console.WriteLine(ageInRange);
// Вывод: больше 18

3. Комбинаторные шаблоны позволяют комбинировать несколько шаблонов в одной строке:
var person = new Person("John", 42);

- Конъюнктивные представляют собой логическое «и» двух подшаблонов:
var ageInRange = person switch {
(_, < 18) => "меньше 18",
("John", _) and (_, > 18) => "Джону больше 18"
};
Console.WriteLine(ageInRange);
// Вывод: Джону больше 18

- Дизъюнктивные представляют собой логическое «или» двух подшаблонов:
var ageInRange = person switch {
(_, < 18) => "меньше 18",
(_, 18) or (_, > 18) => "18 или больше"
};
Console.WriteLine(ageInRange);
// Вывод: 18 или больше

- Отрицательные требуют несовпадения с заданным шаблоном:
var isJohn = person switch {
not ("John", 42) => "не Джон!",
_ => "Джон :)"
};
Console.WriteLine(isJohn);
// Вывод: Джон :)

4. В шаблонах допустимо использовать скобки:
public record IsNumber(bool IsValid, int Number);
var num = new IsNumber(true, 10);
var zeroToTen = num switch {
((_, >= 0 and <= 5) or (_, > 5 and <= 9))
or (_, 10) => "от 0 до 10",
_ => "больше 10"
};
Console.WriteLine(zeroToTen);
// Вывод: от 0 до 10

Источник: https://www.c-sharpcorner.com/article/c-sharp-9-cheatsheet/