Имея следующий класс, я защитил его от 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, если объединение удалено.
И никогда не используйте = default! или = null! (я знаю, что пример кода EF использует его для членов DbSet, но за пределами этой ситуации нет веской причины для этого, и даже тогда я чувствую, что примеры EF неверны, но дают людям глупый -быстрое исправление, чтобы образцы были короче).
Но dotnetfiddle не считает объединение излишним, как и визуальная студия. Возможно, ваша IDE думает, что IEnumerable<Measure> никогда не будет нулевым, но IEnumerable<Measure>? будет.
@shingo Это может быть VS или R # (например, Resharper). Оба надежны и, на мой взгляд, вероятно, «умнее» меня. Обычно. Пожалуйста, смотрите редактирование. Что касается IEnumerable<T>? - я думал, что по умолчанию он обнуляемый, и явный знак обнуляемости будет ненужным.
@Dai Интересное предложение. Каким же тогда будет рекомендуемый синтаксис? EF требует пустой конструктор (в котором я мог бы назначать виртуальные свойства). Но затем EF устанавливает значения с помощью инициализатора, поэтому я не могу использовать только {get;}. Следовательно, я бы приземлился на {get;set;}, сделав значения изменяемыми. Разве это не осуждается или, по крайней мере, обескураживает?
Вы говорите, что Measure — это тип объекта EF? (и EFCore 7 теперь (наконец-то) поддерживает использование ctors, кстати)
У вас есть другие проблемы с дизайном: ваше Container.Measurement свойство не должно возвращать новый изменяемый Dictionary для каждого вызова, так как это дорого и расточительно (и нарушает договор о том, что получатели свойств дешевы в вычислительном отношении). Вместо этого измените его на метод или измените его, чтобы он возвращал ILookup<String,Measure> представление вашего Measures члена (через ToLookup() в Linq).
@Dai Да, показатели хранятся в базе данных и загружаются в виде длинного составного списка данных. Во время использования они разбиваются на группы (что происходит только один раз и только для одного типа, хотя заранее неизвестно, какой именно).
@KonradViltersten (Почему вы храните данные денормализованным способом?) - так что ваш тип Measurement на самом деле не сопоставляется с таблицей EF? (т. е. у вас нет DbSet<Measurement> в DbContext? В этом случае то, что я сказал, неприменимо, потому что EF не будет создавать экземпляр Measurement, поэтому нет необходимости идти на компромисс с дизайном ваших классов, чтобы удовлетворить потребность EF в ctor по умолчанию или изменяемые свойства).
@ Дай, я понимаю, почему это удивительно. Это связано с требованием: мы храним набор мер, в то время как мы используем их подмножество, следовательно, разделенное как измерение, где каждый ключ обозначает тип подмножества. Извините, что не был достаточно ясен с самого начала. Теперь, с учетом сказанного, вы предлагаете мне изменить структуру классов и, что более важно, действительно ли слияние является излишним?
Это должно быть resharper, потому что я не установил его. Поскольку были введены ссылочные типы, допускающие значение NULL, ссылочный тип без вопросительного знака следует считать ненулевым. Подсказка на снимке экрана также объясняет это: «в соответствии с аннотациями ссылочных типов, допускающих значение NULL». Вы можете отключить проверку nullable для проекта, если предпочитаете предыдущий стиль.
@shingo Не могли бы вы опубликовать это как ответ, который будет принят, пожалуйста? Не подвергая сомнению ваше утверждение, мне интересно, есть ли у вас ссылка на утверждение о том, что ссылочные типы должны считаться ненулевыми, если это явно не указано как таковое. Для меня это имеет большой смысл.
Это не ошибка в VS или ReSharper — это «ошибка» в дизайне языка C#: ваше свойство Measures возвращает null в нарушение контракта not-null для свойства (потому что нет модификатора ?), но язык C# доверяет вам убедиться, что свойства init установлены в инициализаторе объекта - если с new Container не используется инициализатор объекта, то свойство останется null, но C# не выдаст вам предупреждение об этом (если вы не используете C# 10 и не добавите модификатор required свойства).





Это похоже на настройку проекта 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. Не мое отрицательное голосование.)
В документе NRT упоминается:
Любая переменная, в которой
?не добавлено к имени типа, является ссылочным типом, не допускающим значение NULL. Это включает в себя все переменные ссылочного типа в существующем коде, когда вы включили эту функцию.
Поэтому, если в проекте включено значение nullable, на основе этого стандарта анализатор должен предполагать, что Measures никогда не будет иметь значение null.
К вашему сведению, если вы не инициализировали свойство Measures, вы должны увидеть предупреждение CS8618, хотя вы можете его игнорировать. Если вы хотите избежать игнорирования этого предупреждения, вы можете добавить «nullable» к «Рассматривать определенные предупреждения как ошибки» в настройках проекта, чтобы сообщать об ошибках компиляции.
К сожалению, компилятор делает такое предположение, несмотря на то, что свойство никогда и нигде не устанавливалось из-за использования init.
Этого не должно быть из-за использования init. Я проверил это, и даже если я использую set или использую только get, VS все равно скажет мне, что «Меры» здесь не равны нулю».
Я имел в виду не это — я имел в виду, что C# не будет предупреждать о том, что свойство class ContainerMeasures не обязательно всегда будет установлено (т.е. что оно будет null, если свойство init никогда не установлено в момент построения).
@Dai У меня такое ощущение, что эта концепция обнуляемости, может быть, и не нарушена, но в данный момент плохо поддерживается. Как будто они хотели сделать и и сделали на полпути. Затем пришло окно релиза, и PL сказал им, чтобы они согласились. Или я упускаю большую картину здесь?
@KonradViltersten Еще когда они представили его в .NET Core 3.1 примерно в 2019 году, да, но с тех пор (и с C# 10.0) «обходной путь» для необнуляемых свойств init фактически-существует-null-из-за-никогда- set — добавить модификатор required (или просто использовать конструктор...).
Не используйте
init. Используйте правильный конструктор.initимеет на удивление мало законных вариантов использования.