Может ли переменная, допускающая значение null в C#, с какого-то момента помечаться компилятором как ненулевая?

#c# #nullable #optional-parameters

#c# #обнуляется #необязательно-параметры

Вопрос:

Я использую null для обозначения того, что необязательный параметр A? a функции должен принимать ненулевое значение по умолчанию, вычисленное в функции. После ввода функции я проверяю, является ли переданный аргумент a null , и если да, то присваиваю a ненулевое значение. С этого момента можно с уверенностью предположить, что a это ненулевое значение. Проблема: компилятор не знает об этом, и теперь мне приходится ссылаться a.Value на остальную часть функции, а не на простую a .

Есть ли способ сообщить компилятору, что a с какого-то момента он на самом деле не равен нулю? Если нет, то каков наиболее четкий способ работы с такими необязательными параметрами?

Пример кода:

 using System;  namespace test {   public struct A { public int x; };   class Program  {   static void f(A? a = null)  {  // Assign the default value.  if (a == null) a = new A { x = 3 };  // Now 'a' is non-null for the rest of the function.  // What I do now:  Console.WriteLine(a.Value.x);  // What I'd like to do:  // * Mark 'a' as non-null somehow.  // Now can refer to 'a' directly:  // Console.WriteLine(a.x);  }   static void Main(string[] args)  {  f();  }  } }  

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

1. Введите новую ненулевую переменную: A notNull = a ?? new A { x = 3 }; и используйте ее в остальной части метода (предположительно с лучшим именем, чем notNull …)

2. Это другой подход, но я бы хотел избежать этого.

3. К сожалению , это не связано с компилятором: A? означает Nullablelt;Agt; и Nullablelt;Tgt; обладает свойством Value . Пока у вас есть переменная типа Nullablelt;Tgt; , вам все равно нужно получить доступ к ее Value свойству, чтобы получить содержащееся в ней значение

4. По крайней мере, в C# 10 компилятор уже понимает, что a это уже не null так, когда он доходит до Console.WriteLine инструкции. Если я закомментирую оператор if, я получу предупреждение, которое a может быть равно нулю, но этого предупреждения нет при наличии оператора if. но! a по-прежнему вводится как Nullablelt;Agt; , что означает, что вы не сможете уйти, a.Value если A на самом деле не извлекете экземпляр в отдельную переменную.

5. @LasseV.Karlsen Я совершил ту же ошибку — на самом деле, ОП хочет использовать Console.WriteLine(a.x) , что, конечно, не будет компилироваться.

Ответ №1:

Нет, нет — не для типа значения, допускающего значение null. Во время компиляции тип значения , допускающего значение null, остается Nullablelt;Tgt; неизменным, даже если компилятор знает, что он будет ненулевым.

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

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

Ответ №2:

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

Существует еще одно возможное решение для этого конкретного сценария:. Просто перегрузка f() , вот так:

 static void f() {  f(new A { x = 3 }); }  static void f(A a) {  Console.WriteLine(a.x); }  

Затем код может вызывать f() с параметром или без него, но разница в том, что в случае, когда он вызывает f(A a) с параметром, он не может быть равен нулю.


(В ответ на ваш комментарий ниже.)

Если вы хотите справиться с возможностью вызова с возможно нулевой структурой, вместо этого вы можете перегрузиться следующим образом:

 static void f(A? a = default) {  f(a ?? new A { x = 3 }); }  static void f(A a) {  Console.WriteLine(a.x); }  

Это означает, что вашей реализации не нужно иметь дело со значением, возможно, равным нулю, но вы все равно можете опустить аргумент на сайте вызова или вызвать его с нулевым значением.

Примечание: Вы можете упростить static void f(A? a = default) , чтобы:

 static void f(A? a = default) =gt; f(a ?? new A { x = 3 });  

если вы предпочитаете более короткий синтаксис.

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

1. Это тоже то, что я иногда делаю. Однако есть случаи, когда я пересылаю возможно нулевой параметр (так что я не знаю, является ли он нулевым), и в этом случае это не работает; т. Е. Тот, у которого необязательный параметр, является более общим подходом.

2. @kaba Смотрите мою правку.

3. Это последнее редактирование-интересный подход.

4. Этот ответ, похоже, в значительной степени попал в точку в моей проблеме; т. Е. Иметь необязательные параметры со значениями по умолчанию, обрабатывать их как ненулевые (потому что они теперь есть) и без необходимости изобретать посторонние имена переменных. Отличная работа!

Ответ №3:

Нет, это невозможно сделать. A? это короткий путь для Nullablelt;Agt; . Nullablelt;Agt; очевидно , что это другой тип A , и для того, чтобы получить значение, вам нужно его использовать Nullablelt;Agt;.Value .

Лучший вариант, который у вас есть, — это, как прокомментировал @MatthewWatson, ввести другую переменную:

 A aValue = a ?? new A { x = 3 };  

Что-то, что может помочь, — это правильно назвать используемый параметр и фактическое значение, чтобы помочь их различить:

 static void f(A? optionalA = null) {  // Use optionalA if it has a value, or the default value otherwise.  var a = optionalA ?? new A { x = 3 };  

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

1. Назвать сложно:( Если вызывающий абонент использует именованные параметры , как в f(optionalA : new A {x = 4}) , то необязательный префикс-это просто шум для вызывающего абонента. Поэтому , возможно, интерфейс должен сохранить ясное имя a , а реализация вместо этого должна принять удар. Иногда я называю новую переменную в реализации a_ , т. Е. подчеркиванием.

Ответ №4:

Для этого вы должны иметь возможность использовать свойства.

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

Подобный этому:

 int? a = null;  public int a2 {  get  {  return a.GetValueOrDefault(3);  } }  

Тогда вы можете использовать свойство a2 вместо a

Не совсем то же самое, что изменить поле с обнуляемого на ненулевое, но это может сделать то, что вы ожидали сделать.