Проверка на нуль не вызывает продвижение типа в Dart

Я обновляю персональный пакет, основанный на фреймворке Flutter. Я заметил здесь, в исходном коде виджета Flutter Text, что есть проверка на null:

if (textSpan != null) {
  properties.add(textSpan!.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition));
}

Тем не мение, textSpan! все еще использует !оператор. Не должен textSpan быть повышенным до типа, не допускающего значения NULL, без необходимости использования !оператор? Однако попытка удалить оператор дает следующую ошибку:

An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.

Вот самодостаточный пример:

class MyClass {
  String? _myString;
  
  String get myString {
    if (_myString == null) {
      return '';
    }
    
    return _myString; //   <-- error here
  }
}

Я получаю ошибку времени компиляции:

Ошибка: значение типа "Строка?" не может быть возвращен из функции myString, потому что она имеет тип возвращаемого значения String.

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

Мой вопрос был решен на GitHub, поэтому я публикую ответ ниже.

4 ответа

Дарт-инженер Эрик Эрнст говорит на GitHub:

Повышение типа применимо только к локальным переменным.... Продвижение переменной экземпляра не является правильным, потому что оно может быть переопределено геттером, который выполняет вычисление и возвращает другой объект при каждом его вызове. Ср. dart-lang / language #1188 для обсуждения механизма, который похож на продвижение типов, но основан на динамических проверках, с некоторыми ссылками на связанные обсуждения.

Итак, продвижение локального типа работает:

  String myMethod(String? myString) {
    if (myString == null) {
      return '';
    }
    
    return myString;
  }

Но переменные экземпляра не продвигаются. Для этого вам нужно вручную сообщить Dart, что вы уверены, что переменная экземпляра не равна нулю в этом случае, используя ! оператор:

class MyClass {
  String? _myString;
  
  String myMethod() {
    if (_myString == null) {
      return '';
    }
    
    return _myString!;
  }
}

Обычно есть 3 способа решить эту проблему.

  1. Использовать локальную переменную (рекомендуется)

    Используйте локальные переменные для продвижения типа (подробнее об этой концепции здесь )

            class Foo {
      int? i = 0;
    
      // Error
      double d1() => i.toDouble();
    
      // No error
      double d2() {
        var i = this.i; // Use of local variable. 
        if (i != null) return i.toDouble();
    
        return -1.0; // Some default value if 'i' happens to be 'null'
      }
    }
    

  1. Использовать ?. с участием ??

            class Foo {
      int? i = 0;
    
      // Error
      double d1() => i.toDouble();
    
      // No error
      double d2() => i?.toDouble() ?? -1.0; // Provide some default value.
    }
    

  1. Используйте оператор Bang (!)

    Вам следует использовать это решение, только если вы на 100% уверены, что переменная ( i) не будет никогда null.

            class Foo {
      int? i = 0;
    
      // Error
      double d1() => i.toDouble();
    
      // No error
      double d2() => i!.toDouble(); // Bang operator in play.
    }
    

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

                          Theme
                        .of(context)
                        .textTheme
                        .headline1?.fontSize?? 32,

style: Theme.of(контекст).textTheme.headline5!.copyWith(

      style: Theme.of(context).textTheme.headline5!.copyWith(
                        color: Colors.white

Попробуйте сделать вызов условным с помощью? или нулевую проверку безопасности -!

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