Может ли свойство быть когда-либо нулевым или VS неправильный?

Имея следующий класс, я защитил его от null для Values.

public class Container
{
  public Guid Id { get; init; }
  public virtual IEnumerable<Measure> Measures { get; init; }
  public Dictionary<string, Measure[]> Measurement 
    => (Measures ?? Array.Empty<Measure>())
      .GroupBy(a => a.Type).ToDictionary(a => a.Key, a => a.ToArray());
}

public class Measure
{
  public string Name { get; set; }
  public string Type { get; set; }
}

По какой-то причине моя IDE помечает объединение как лишнее, утверждая, что свойство никогда не будет иметь значение null (т. е. Measures??Array.Empty<Measure>() может быть реорганизовано в Measures).

public class Container
{
  public Guid Id { get; init; }
  public virtual IEnumerable<Measure> Measures { get; init; }
  public Dictionary<string, Measure[]> Measurement 
    => Measures
      .GroupBy(a => a.Type).ToDictionary(a => a.Key, a => a.ToArray());
}

Когда я последовал этому совету, коллега указал, что он ошибочен. Я согласен с ним, но это ошибка в VS/R#, и это действительно реалистичное объяснение. Более реалистично: это мы ошибаемся. Так что я чувствую, что мы оба что-то упускаем и что IDE каким-то образом права.

Хотя я не могу объяснить как.

Я попытался удалить virtual, добавить = default! и внести несколько других изменений. Несмотря на это, выполнение this в скрипке выдает ArgumentNullException, если объединение удалено.

Не используйте init. Используйте правильный конструктор. init имеет на удивление мало законных вариантов использования.

Dai 25.06.2023 07:46

И никогда не используйте = default! или = null! (я знаю, что пример кода EF использует его для членов DbSet, но за пределами этой ситуации нет веской причины для этого, и даже тогда я чувствую, что примеры EF неверны, но дают людям глупый -быстрое исправление, чтобы образцы были короче).

Dai 25.06.2023 07:47

Но dotnetfiddle не считает объединение излишним, как и визуальная студия. Возможно, ваша IDE думает, что IEnumerable<Measure> никогда не будет нулевым, но IEnumerable<Measure>? будет.

shingo 25.06.2023 07:59

@shingo Это может быть VS или R # (например, Resharper). Оба надежны и, на мой взгляд, вероятно, «умнее» меня. Обычно. Пожалуйста, смотрите редактирование. Что касается IEnumerable<T>? - я думал, что по умолчанию он обнуляемый, и явный знак обнуляемости будет ненужным.

Konrad Viltersten 25.06.2023 08:07

@Dai Интересное предложение. Каким же тогда будет рекомендуемый синтаксис? EF требует пустой конструктор (в котором я мог бы назначать виртуальные свойства). Но затем EF устанавливает значения с помощью инициализатора, поэтому я не могу использовать только {get;}. Следовательно, я бы приземлился на {get;set;}, сделав значения изменяемыми. Разве это не осуждается или, по крайней мере, обескураживает?

Konrad Viltersten 25.06.2023 08:13

Вы говорите, что Measure — это тип объекта EF? (и EFCore 7 теперь (наконец-то) поддерживает использование ctors, кстати)

Dai 25.06.2023 08:22

У вас есть другие проблемы с дизайном: ваше Container.Measurement свойство не должно возвращать новый изменяемый Dictionary для каждого вызова, так как это дорого и расточительно (и нарушает договор о том, что получатели свойств дешевы в вычислительном отношении). Вместо этого измените его на метод или измените его, чтобы он возвращал ILookup<String,Measure> представление вашего Measures члена (через ToLookup() в Linq).

Dai 25.06.2023 08:25

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

Konrad Viltersten 25.06.2023 09:32

@KonradViltersten (Почему вы храните данные денормализованным способом?) - так что ваш тип Measurement на самом деле не сопоставляется с таблицей EF? (т. е. у вас нет DbSet<Measurement> в DbContext? В этом случае то, что я сказал, неприменимо, потому что EF не будет создавать экземпляр Measurement, поэтому нет необходимости идти на компромисс с дизайном ваших классов, чтобы удовлетворить потребность EF в ctor по умолчанию или изменяемые свойства).

Dai 25.06.2023 09:37

@ Дай, я понимаю, почему это удивительно. Это связано с требованием: мы храним набор мер, в то время как мы используем их подмножество, следовательно, разделенное как измерение, где каждый ключ обозначает тип подмножества. Извините, что не был достаточно ясен с самого начала. Теперь, с учетом сказанного, вы предлагаете мне изменить структуру классов и, что более важно, действительно ли слияние является излишним?

Konrad Viltersten 25.06.2023 09:57

Это должно быть resharper, потому что я не установил его. Поскольку были введены ссылочные типы, допускающие значение NULL, ссылочный тип без вопросительного знака следует считать ненулевым. Подсказка на снимке экрана также объясняет это: «в соответствии с аннотациями ссылочных типов, допускающих значение NULL». Вы можете отключить проверку nullable для проекта, если предпочитаете предыдущий стиль.

shingo 25.06.2023 10:02

@shingo Не могли бы вы опубликовать это как ответ, который будет принят, пожалуйста? Не подвергая сомнению ваше утверждение, мне интересно, есть ли у вас ссылка на утверждение о том, что ссылочные типы должны считаться ненулевыми, если это явно не указано как таковое. Для меня это имеет большой смысл.

Konrad Viltersten 25.06.2023 10:09

Это не ошибка в VS или ReSharper — это «ошибка» в дизайне языка C#: ваше свойство Measures возвращает null в нарушение контракта not-null для свойства (потому что нет модификатора ?), но язык C# доверяет вам убедиться, что свойства init установлены в инициализаторе объекта - если с new Container не используется инициализатор объекта, то свойство останется null, но C# не выдаст вам предупреждение об этом (если вы не используете C# 10 и не добавите модификатор required свойства).

Dai 25.06.2023 10:33
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
13
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это похоже на настройку проекта Nullable:

UPD Мне не удалось воспроизвести сообщение IDE независимо от настройки Nullable.

Сообщество Microsoft Visual Studio 2022 (64-разрядная версия) — Текущая версия Версия 17.6.0

С «Nullbale» стоит «Включить»:

1> ------- Начата сборка: Проект: WinFormsApp3, Конфигурация: Отладка любого процессора ------

1> C:\Users\TMSAdmin\source\WinFormsApp3\WinFormsApp3\Form1.cs(99,23,99,27): предупреждение CS8618: Ненулевое свойство «Имя» должно содержать ненулевое значение при выходе из конструктора. Рассмотрите возможность объявления свойства как обнуляемого.

1>C:\Users\TMSAdmin\source\WinFormsApp3\WinFormsApp3\Form1.cs(100,23,100,27): предупреждение CS8618: ненулевое свойство «Тип» должно содержать ненулевое значение при выходе из конструктора. Рассмотрите возможность объявления свойства как обнуляемого.

1> C:\Users\TMSAdmin\source\WinFormsApp3\WinFormsApp3\Form1.cs(92,45,92,53): предупреждение CS8618: ненулевое свойство «Меры» должно содержать ненулевое значение при выходе из конструктора. Рассмотрите возможность объявления свойства как обнуляемого.

1>WinFormsApp3 -> C:\Users\TMSAdmin\source\WinFormsApp3\WinFormsApp3\bin\Debug\net6.0-windows\WinFormsApp3.dll

1> Готовый проект сборки "WinFormsApp3.csproj".

========== Сборка: 1 успешно, 0 неудачно, 0 обновлено, 0 пропущено ==========

Вместо этого вам может быть проще показать полученные строки в CSPROJ. Графический интерфейс настроек может различаться в разных IDE. Что касается сообщения, оно может быть предоставлено R #, а не самим VS. Однако оба вполне надежны, поэтому я верю, что в этом что-то есть. Если не подтверждено иное, отсюда и мой вопрос. (NB. Не мое отрицательное голосование.)

Konrad Viltersten 25.06.2023 09:36
Ответ принят как подходящий

В документе NRT упоминается:

Любая переменная, в которой ? не добавлено к имени типа, является ссылочным типом, не допускающим значение NULL. Это включает в себя все переменные ссылочного типа в существующем коде, когда вы включили эту функцию.

Поэтому, если в проекте включено значение nullable, на основе этого стандарта анализатор должен предполагать, что Measures никогда не будет иметь значение null.

К вашему сведению, если вы не инициализировали свойство Measures, вы должны увидеть предупреждение CS8618, хотя вы можете его игнорировать. Если вы хотите избежать игнорирования этого предупреждения, вы можете добавить «nullable» к «Рассматривать определенные предупреждения как ошибки» в настройках проекта, чтобы сообщать об ошибках компиляции.

К сожалению, компилятор делает такое предположение, несмотря на то, что свойство никогда и нигде не устанавливалось из-за использования init.

Dai 25.06.2023 10:57

Этого не должно быть из-за использования init. Я проверил это, и даже если я использую set или использую только get, VS все равно скажет мне, что «Меры» здесь не равны нулю».

shingo 25.06.2023 11:01

Я имел в виду не это — я имел в виду, что C# не будет предупреждать о том, что свойство class ContainerMeasures не обязательно всегда будет установлено (т.е. что оно будет null, если свойство init никогда не установлено в момент построения).

Dai 25.06.2023 11:14

@Dai У меня такое ощущение, что эта концепция обнуляемости, может быть, и не нарушена, но в данный момент плохо поддерживается. Как будто они хотели сделать и и сделали на полпути. Затем пришло окно релиза, и PL сказал им, чтобы они согласились. Или я упускаю большую картину здесь?

Konrad Viltersten 25.06.2023 12:16

@KonradViltersten Еще когда они представили его в .NET Core 3.1 примерно в 2019 году, да, но с тех пор (и с C# 10.0) «обходной путь» для необнуляемых свойств init фактически-существует-null-из-за-никогда- set — добавить модификатор required (или просто использовать конструктор...).

Dai 25.06.2023 12:27

Другие вопросы по теме