Почему Visual Studio вводит недавно созданный массив как допускающий значение NULL?

Я пишу функцию с универсальным типом TVal. Я написал такую ​​строчку:

var zeroBased = new TVal[size];

А затем в Visual Studio (VS) я использовал alt + enter, чтобы заменить var явным типом. Вот что у меня получилось:

TVal[]? zeroBased = new TVal[size];

Я был удивлен, обнаружив оператор ?, указывающий на то, что тип может иметь значение NULL. Я думал, что буду в безопасности, если предположить, что тип никогда не будет нулевым при создании с помощью new, и мог бы только что сделать:

TVal[] zeroBased = new TVal[size];

Есть ли сценарий, при котором создание экземпляра нового массива в C # может вернуть значение null?

Примечание: код, похоже, отлично компилируется без ?, я просто заинтригован предложением VS ...

Минимальный проверяемый пример

Откройте Visual Studio той же версии, что указана ниже, создайте новый проект, включите типы, допускающие значение NULL, в соответствии с содержимым файла проекта VS ниже, создайте новый класс и вставьте эту функцию:

public void Test<T>(int size)
{
  var tArr = new T[size];
}

Выберите var, нажмите alt+enter и выберите замену var на явный тип. Если поведение будет таким же, как у меня, вы получите:

public void Test<T>(int size)
{
  T[]? tArr = new T[size];
}

Содержимое файла проекта Visual Studio

Мы используем C # 8 для этого проекта и включили Nullables:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Nullable>enable</Nullable>
    <LangVersion>8.0</LangVersion>
    <WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
    <TargetFramework>netstandard2.0</TargetFramework>
    <OutputType>Library</OutputType>
    <Version>1.0.0.9</Version>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
    <PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
  </ItemGroup>

</Project>

Информация о версии Visual Studio (только те части, которые казались важными для этого вопроса)

Microsoft Visual Studio Community 2019 версии 16.6.1 VisualStudio.16.Release / 16.6.1 + 30128.74 Microsoft .NET Framework версии 4.7.03062

Установленная версия: Сообщество

Инструменты C # 3.6.0-4.20251.5 + 910223b64f108fcf039012e0849befb46ace6e66 Компоненты C #, используемые в среде IDE. В зависимости от типа и настроек вашего проекта может использоваться другая версия компилятора.


person Colm Bhandal    schedule 08.06.2020    source источник
comment
Какие версии вы используете? (Visual Studio, C #, .NET и т. д.) Также не могли бы вы добавить окружающий код для контекста? то есть: если есть код, мы могли бы скопировать / вставить в LinqPad, чтобы помочь найти ответ   -  person Shai Cohen    schedule 08.06.2020
comment
Не могу воспроизвести, получил T[] с указанным кодом   -  person Pavel Anikhouski    schedule 08.06.2020
comment
Вы инициализируете его ненулевым значением, но вы можете установить его на null позже, насколько компилятор может видеть   -  person Hans Kesting    schedule 08.06.2020


Ответы (3)


Visual Studio с C # 8 позволяет использовать типы, допускающие значение NULL, в соответствии с контекстами, которые вы установили в своем проекте. Вы можете найти документы здесь.

Один из способов включить его - указать <Nullable>enable</Nullable> в файле проекта. Если он у вас есть, он выберет тип, допускающий значение NULL, при преобразовании в явную переменную.

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

person MikeJ    schedule 08.06.2020

Я хотел бы расширить существующий ответ, добавив несколько ссылок

В предложении спецификации C # говорится:

неявно типизированные локальные переменные, допускающие значение NULL

var определяет аннотированный тип для ссылочных типов. Например, в var s = ""; var выводится как string?.

Это означает, что var для ссылочных типов подразумевает ссылочный тип, допускающий значение NULL. Это работает, если nullable context включен либо с помощью файла проекта, либо #nullable pragma.

Это поведение обсуждалось в этом LDM и реализован в этой проблеме.

Это причина для того, чтобы var вывести ссылочный тип, допускающий значение NULL:

На этом этапе мы видели большой объем кода, который требует, чтобы люди указывали тип вместо использования var, потому что код может присвоить null позже.

person Iliar Turdushev    schedule 09.06.2020

Я хотел бы расширить существующие ответы своей собственной интерпретацией. Ответ MikeJ разрешил эту проблему и дошел до ее сути. Все сводится к включению возможности нулевого значения, что у нас было для этого проекта (но не для других, что меня оттолкнуло!).

Ответ Илиара Турдушева затем добавил несколько явных ссылок в поддержку исходного ответа. В частности, нам указали на недавнее обсуждение команды C # на Github. В этом документе и в существующем ответе цитируется следующее:

На этом этапе мы видели большой объем кода, который требует, чтобы люди указывали тип вместо использования var, потому что код может присвоить null позже.

Мне было трудно понять это без контекста. Итак, вот контекст, объясняющий сказанное выше:

var current = myLinkedList.Head; // annotated not null
while (current is object)
{
    ...
    current = current.Next; // warning, Next is annotated nullable, but current is non-null
}

Давайте посмотрим на первую строчку:

var current = myLinkedList.Head; // annotated not null

Поскольку свойство Head аннотировано как ненулевое, для компилятора было бы совершенно нормально интерпретировать переменную как не допускающую значения NULL. Тем не менее, эта не допускающая обнуления возможность навсегда останется с переменной, даже если в какой-то момент программы мы захотим сделать ее нулевой, например. в этой строке:

 current = current.Next; // warning, Next is annotated nullable, but current is non-null

Команда C # сказала: «Хорошо, у нас есть два варианта. Мы можем либо интерпретировать var как всегда допускающий значение NULL, либо мы можем интерпретировать его как не допускающий значения NULL, когда он выводится из контекста, и разрешить пользователям указывать var?, чтобы явно заявить, что они хотят иметь тип, допускающий значение NULL. Но я понимаю их документ, что var? как бы нарушает весь принцип var, то есть удобства. Если бы нам приходилось наклеивать лишний ? в конец var каждый раз, когда мы его использовали, вероятно, пришло время просто четко указать тип и прекратить использование var.

Таким образом, команда C # делает вывод:

Сделайте var аннотированный тип, допускающий значение NULL, и сделайте вывод о типе потока как обычно.

Это означает, что если вы назначаете var не допускающее значение NULL, вы можете безопасно назначить null этой же ссылке позже в коде без каких-либо предупреждений. Ханс тоже прокомментировал это. Так, например, вернув его к исходному Q, я мог бы сделать:

public void Test<T>(int size)
{
  var tArr = new T[size];
  //Some code
  tArr = null; //This is OK, because var is treated as T[]?, not T[]
}

И я бы не получил никаких предупреждений. Таким образом, VS ведет себя здесь правильно - он уважает поведение компилятора, рассматривая var как допускающий значение NULL, как и задумано, даже когда эта переменная инициализируется значением, не допускающим значения NULL. Смысл, и это ключевой момент для меня и суть моего вопроса:

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

И как раз то, что я сделаю в этом случае!

person Colm Bhandal    schedule 09.06.2020