Как стать автором
Обновить

Комментарии 38

Новых вопросов на собеседованиях добавится:)

Ага. А потом такой выходишь на работу, а там даже LINQ нельзя, потому что тимлид местный его то ли не любит, то ли не понимает, то ли и то и другое - я сам столкнулся с таким пару лет назад, сказал: "Ой", и на следующий день оттуда убежал :))

Модификатор доступа file

Ох, сколько тут легаси-кода попадает...

Почему?

Чтобы перейти на C# 11 вам придется перейти на .NET 7 и всю его экосистему. Это потребует определенных изменений в коде. Поэтому непонятно про какие падения идёт речь.

Не, с легаси всё ок. В статье это упущено, но как обычно с новыми ключевыми словами, это не полноценное, а контекстное ключевое слово (contextual keyword). Кроме как перед определением класса оно нового значения не имеет, может использоваться как обычно.

Действительно, не уточнил. Коллизия будет только в том случае, если файлом (причем именно в нижнем регистре) будет называться тип - таких и правда вряд ли найдётся много в легаси. Впрочем, как раз рядом писал о том, что теперь имена классов (типов) в нижнем регистре вызывают предупреждения, чтобы впредь таких коллизий избежать.

Обобщённые атрибуты это, конечно, хорошо, но помешало бы добавить способ атрибуту получать тип класса, на который его повесили, с учётом наследования.

Ну, уже сейчас есть способ вычленять тип через Source Generators, хоть это может быть и не сильно удобно

Не осветили улучшение связанное с Json Serialize, а именно атрибут JsonDerivedType, который позволяет намного корректнее работать с наследованием.

Эта статья по большей части затрагивает изменения в языке, нежели во фреймворке. Изначально были мысли включить что-то из .NET, но после того, как увидел размер этой статьи - решил, что если и писать, то отдельно 😅

Фича и правда примечательная (да и другие в грядущем релизе). Только недавно сокрушался, что такие вещи из коробки не делаются.

Также интересны улучшения производительности .NET 7. Например, при работе с рефлексией производительность выросла в 2+ раза. Что само по себе может быть аргументом для переезда с .NET 6 LTS на .NET 7 в некоторых случаях. Тем более, что поддержка у них заканчивается плюс/минус одинаково.

Да, а при работе с LINQ некоторые бенчмарки показывают ускорение в несколько десятков раз. Это не говоря о новых API. Ещё бы после такого на LTS сидеть :)

Я, пожалуй, и не буду :)

Да, а при работе с LINQ некоторые бенчмарки показывают ускорение в несколько десятков раз.

Даже интересно, изменится ли производительность C#-анализатора. Хотя с другой стороны, из критичных для производительности мест LINQ мы уже вычистили...

Видел сравнения нового LINQ и ручных реализаций, и там он тоже неплохо давал прикурить - может, зря убрали :)

Хотя конечно смотреть надо по частным случаям.

Жаль что field так и не попал, сценарии с source generators и свойствами (например в records) некоторые не сделать без него.

Да, разделяю боль :(
Только недавно сталкивался с тем, что без кодогенерации реализация какого-нибудь MVVM (под blazor) приводит к куче boilerplate полей, что раздражает. Теперь ещё год ждать :(

Required properties with dependency injection? Все ещё придется пробрасывать аргументы через всю иерархию наследования или наконец то можно будет забыть про это?

Но это же никогда не было большой проблемой...


Если вы ожидаете много аргументов (либо мало, но переменное количество) и собираетесь делать иерархию наследования — просто сгруппируйте зависимости в отдельный класс.

Видимо я чего то не знаю. Есть какой то конкретный шаблон проектирования? Было бы достаточно названия. Дальше я бы нашел где почитать.

Есть вероятность что я не совсем корректно сформулировал свою боль.

abstract class Base 
{
    public Base(IFirstInterface first, ISecondInterface second,..){}
 
    //all methods with bl here
}

abstract class Middle: Base 
{
    //painful 
    public Middle (IFirstInterface first,ISecondInterface second,...):base(first,second,...){}

    //some overrided methods
}

sealed class FinalA : Middle
{
    //painful 
    public FinalA(...) : base(...){}
}

sealed class FinalB : Middle
{
    //painful
    public FinalB(...):base(...){}
}
судя по описанию фичи required больне не будет необходимости пробрасывать аргументы конструктора. дотаточно будет поменить нужные свойства. вопрос в том будет ли это поддержано со стороны контейнеров инверсии зависимостей. 

Вот, смотрите
sealed record class BaseDeps(IFirstInterface first, ISecondInterface second, …);
abstract class Base 
{
    public Base(BaseDeps deps){}

    //all methods with bl here
}

sealed record class MiddleDeps(BaseDeps baseDeps, …);
abstract class Middle: Base 
{
    public Middle (MiddleDeps deps):base(deps.baseDeps){}

    //some overrided methods
}

sealed class FinalA : Middle
{
    public FinalA(MiddleDeps deps) : base(deps){}
}

sealed class FinalB : Middle
{
    public FinalB(MiddleDeps deps):base(deps){}
}

В общем-то, никакой особой боли, и даже есть расширяемость по зависимостям (при использовании контейнера зависимостей).

К сожалению даже в вашем примере вся боль бойлерплейтных конструкторов осталась на месте. Просто вместо пачки зависимостей они превратились в одну и вынесены в другой файл. Когда в FinalA и FinalB реализации интерфейсов используются различные то и объеденить все в один BaseDeps уже не получится. Это как раз мой случай.

Required фича, судя по описанию, позволит избавиться от этих конструкторов с пробросами. То есть вообще не придется их прописывать. И мой вопрос был в том : будет ли дефолтный .net core контейнер поддерживать required свойства\поля и автоматически собирать зависимости без конструктора с аргументами уже в net7 на старте ?

Просто вместо пачки зависимостей они превратились в одну и вынесены в другой файл.

Вот этого как раз лучше не делать: класс BaseDeps куда понятнее смотрится рядом с классом Base, в конце концов это буквально его зависимости.


Когда в FinalA и FinalB реализации интерфейсов используются различные то и объеденить все в один BaseDeps уже не получится.

А причём тут вообще FinalA и FinalB?..

interface IBase {}
interface IFinalA : IBase {}
interface IFinalB : IBase {}

sealed class FinalA : IFinalA {}
sealed class FinalB : IFinalB {}

abstract class BaseClass<T> where T: IBase
{
    privare readonly T _base;

    public Base(T base) => _base = base;
}

abstract class Middle<T>
{
  public Middle(T base) : base(base) {}
}

sealed class FinalClassA : Middle<IFinalA>
{
    public FinalClassA(IFinalA base) : base(base) {}
}

sealed class FinalClassB : Middle<IFinalB>
{
    public FinalClassA(IFinalB base) : base(base) {}
}

классов типа FinalClass сотни. Прописывать везде эти конструткоры порядком утомительно. Надежда на то что required поможет удалить конструкторы.

Ну, такие-то конструкторы автогенерируются IDE, пару кнопок прожать никогда трудно не было...

Вопрос не в том как я могу это автоматизировать. Вопрос в том смогу ли я от этого избавиться. Ответ на этот вопрос я пытаюсь получить весь этот тред.

Личная боль связана не с тем что мне приходиться набирать на 5 строчек больше. Боль связана с эстетическим неудовлетворением.

Ради интереса даже проверил на RC2 - ожидаемо, не сработало :)
Но это, конечно, и не главная мотивация для данной фичи. В конце концов, надо обязательно делать обязательные члены публичными - а публичные зависимости это что-то странное.
Да и если подумать, скрывать явные зависимости (пусть даже родительские) - это уже что-то в духе Service Locator-а. Если очень хочется - можно к нему прибегнуть, но быть готовым к его минусам.

Операторы with, ? ,?. по-прежнему нельзя в выражениях?

По итогу, реально полезен только required.

Спасибо за статью, сам совсем недавно столкнулся с проблемой невозможности объявить операторы в интрефейсах (реализовывал спецификацию), теперь это сделать гораздо проще, да и в использовании намного удобнее)

я что то не понял, microsoft считает что nullability готова? Оператор ! не имеет смысла без throwable... Где поддержка в CLR? Зато еще навалили сахару...

required спорное нововведение, имхо. Имеет смысл заменять им простые конструкторы с большим количеством параметров для того, чтобы повысить читаемость, ок. Но как только появится необходимость добавлять некоторую бизнес-логику при инициализации объекта, придется потом обратно возвращаться к конструктору и орудовать уже в нём. Так что полноценной замены конструктора тут нет, значительно

Это необходимое нововведение, которое наконец-то закрывает "дырку" с non-nullable свойствами — раньше нужно было либо такие свойства обязательно инициализировать либо адекватным значением по умолчанию, либо принимая значение в конструкторе.


А конструктор с параметрами не к любому типу можно добавить. Например, его не добавить в DTO для десериализации или в сущность EF.

А какой профит добавлять required в DTO? И почему не пользоваться record-типами?
Сущности EF - это, по сути, те же DTO, как бы создатели фреймворка ни пытались подогнать их под богатую модель, какой профит там в required, мне тоже не понятно.

Для DTO я бы предложил все-таки использовать прекрасные record. Мб я не вижу, конечно, какой-то кейс, который закрывается этим нововведением, тогда я был бы рад увидеть пример

Простейший сценарий: наследование, когда и у базового, и дочернего класса есть required поля.

Для DTO записи менее подходят с позиций контроля версий и совместимости. Одного только того, что для добавления свойства в конец в "стандартном" стиле кода придётся менять предыдущую строку, чтобы добавить туда запятую — уже достаточно чтобы их не использовать.


А ещё добавление свойства в позиционную часть записи нарушит бинарную совместимость, даже если это свойство необязательное. И с удалением то же самое. Очевидное решение — добавлять необязательные свойства как непозиционные — приводит к проблемам когда нужно изменить обязательность свойства, а в типичном бекенде это довольно частая операция.


Наконец, используемые инструменты могут просто не работать с записями. Например, EF Core с записями работать не может.

Модификатор required

Вот жеж. Когда читал про это, думал: круто! А только что на практике посмотрел как это работает. Как-то не очень. Он же раньше видел что вот ссылочное не нулабельное свойство, в конструкторе его нет, давай варнинг кинем. Почему сейчас, с модификатором required он не видит, что свойство есть в конструкторе? Я бы ещё предположил, что потому что конструкторов может быть несколько. И в каком-нибудь из них, свойства может не быть. Но это ведь не мешало в предыдущей схеме, когда в таком случае конструкторы без свойства также варнингом отмечались. То есть он видит, в каком конструкторе есть свойство, а в каком его нет. И какой конструктор вызван он тоже видит. Так что ему мешает увидеть что свойство проинициализировано в конструкторе и инициализатор требовать не нужено, без всяких атрибутов к конструктору?
В общем это атрибут [SetsRequiredMembers] выглядит каким-то костылём, причём кривым.

То есть нововведение конечно хорошее, если свосем от конструкторов отказываться, но если хочется и конструктор и required, то какая-то беда получается :(

Зарегистрируйтесь на Хабре, чтобы оставить комментарий