This question already has answers here:
"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety
(3 answers)
Closed 2 months ago.
Even below code already check t.s!=null, dart still throws Error:
t.dart:7:26: Error: Property 'length' cannot be accessed on 'String?' because it is potentially null. Try accessing using ?. instead. if (t.s != null && t.s.length > 5) {}
class Test {
String? s;
}
void main() {
Test t = Test();
if (t.s != null && t.s.length > 5) {}
}
Add extra var would solve it as below:
void main() {
Test t = Test();
var s = t.s;
if (s != null && s.length > 5) {}
}
Why dart throws error even t.s!=null already checked?
Is there a way to do it without adding extra var?
Additionally, in Typescript, it won't throw error:
function main(t:{s?:string}){
if(t.s!==undefined && t.s.length > 5){
}
}
if you check t.s != null, after that you can safely convert it from String? to String.
( ! will convert you from String? to String )
void main() {
Test t = Test();
if (t.s != null && t.s!.length > 5) {}
}
https://dart.dev/null-safety/understanding-null-safety
“Casting away nullability” comes up often enough that we have a new shorthand syntax. A postfix exclamation mark (!) takes the expression on the left and casts it to its underlying non-nullable type."
Since s is nullable, a subclass might change s to a getter that randomly returns either a null or a string. Thus, the analyzer correctly cannot type-promote s to non-null upon seeing t.s != null, because it might be null in the next expression. This is why copying it to a local variable works, because there's no chance of that any more.
I have a video on this: https://youtu.be/rqS_Q34RcUk
There's nothing to fix. There is a discussion on allowing a library-private class to permit this type promotion since it could not be overridden by unknown code.
Related
Learning Dart and using dart_code_metrics to ensure that I write code that meets expectations. One of the rules that is active is avoid-non-null-assertion.
Note, the code below was created to recreate the problem encountered in a larger code base where the value of unitString is taken from a JSON file. As such the program cannot control what is specified in the JSON file.
From pubspec.yaml
environment:
sdk: '>=2.15.0 <3.0.0'
// ignore_for_file: avoid_print
import 'package:qty/qty.dart';
void main() {
const String unitString = 'in';
// unit.Width returns null if unitString is not a unit of Length.
if (Length().unitWith(symbol: unitString) == null) {
print('units $unitString not supported.');
} else {
// The following line triggers avoid-non-null-assertion with the use of !.
final Unit<Length> units = Length().unitWith(symbol: unitString)!;
final qty = Quantity(amount: 0.0, unit: units);
print('Qty = $qty');
}
}
If I don't use ! then I get the following type error:
A value of type 'Unit<Length>?' can't be assigned to a variable of type 'Unit<Length>'.
Try changing the type of the variable, or casting the right-hand type to 'Unit<Length>'.
Casting the right-hand side to
Unit<Length>
fixes the above error but cause a new error when instantiating Quantity() since the constructor expects
Unit<Length>
and not
Unit<Length>?
I assume there is an solution but I'm new to Dart and cannot formulate the correct search query to find the answer.
How can I modify the sample code to make Dart and dart_code_metrics happy?
Your idea of checking for null before using a value is good, it's just not implemented correctly. Dart does automatically promote nullable types to non-null ones when you check for null with an if, but in this case you need to use a temporary variable.
void main() {
const String unitString = 'in';
//Use a temp variable, you could specify the type instead of using just using final
final temp = Length().unitWith(symbol: unitString);
if (temp == null) {
print('units $unitString not supported.');
} else {
final Unit<Length> units = temp;
final qty = Quantity(amount: 0.0, unit: units);
print('Qty = $qty');
}
}
The basic reason for that when you call your unitWith function and see that it's not null the first time, there's no guarantee that the when you call it again that it will still return a non-null value. I think there's another SO question that details this better, but I can't seem to find.
In the code below, I'm casting away the nullability by using the bang operator.
void main() {
String? foo;
foo!.toLowerCase();
}
But when I use assert just before calling the function, I get an error.
void main() {
String? foo;
assert(foo != null);
foo.toLowerCase(); // Error
}
Is bang operator not doing the same thing behind the scene?
Note: I'm not looking for a solution to how to make it work, there are many ways, even the flow statement ones, if (foo == null) return;
(Adding this an answer instead of a comment.)
The point of asserts is that they can be disabled and that they will not incur any runtime penalty. It's been some matter of debate, but the current philosophy is that this means that asserts will not perform type promotion, so even if you do:
bool foo(Object object) {
assert(object is String);
// error: The getter 'isEmpty' isn't defined for the type 'Object'
return object.isEmpty;
}
For the same reason, assert(someLocalVariable != null) will not promote someLocalVariable to a non-nullable type:
bool bar(String? string) {
assert(string != null);
// error: The property 'isEmpty' can't be unconditionally accessed
// because the receiver can be null.
return string.isEmpty;
}
As of Dart 2.12 with null-safety enabled, however, you can get the desired effect by just performing the cast directly. That will promote the type and throw a runtime exception if the cast fails:
bool foo(Object object) {
object as String;
return object.isEmpty;
}
bool bar(String? string) {
string!;
return string.isEmpty;
}
I was following a PR for Flutter and came across this code:
if (chunkCallback != null) {
chunkCallback!(0, 100);
}
What does the exclamation mark mean after chunkCallback? Nothing I search on Google works.
"!" is a new dart operator for conversion from a nullable to a non-nullable type.
Read here and here about sound null safety.
Use it only if you are absolutely sure that the value will never be null and do not confuse it with the conditional property access operator.
chunkCallback is a nullable reference to a function.
If you are sure that chunkCallback can't be null at runtime you can "Cast away nullability" by adding ! to it to make compiler happy
typedef WebOnlyImageCodecChunkCallback = void Function(
int cumulativeBytesLoaded, int expectedTotalBytes);
...
class Class1 {
final WebOnlyImageCodecChunkCallback? chunkCallback;
Class1(this.chunkCallback);
void test() {
if (chunkCallback == null) {
throw Exception("chunkCallback is null");
}
chunkCallback!.call(0, 100);
}
}
Esentially, ! in this case is a syntactic sugar for
(chunkCallback as WebOnlyImageCodecChunkCallback).call(0, 100);
I think it is a shorthand syntax for “Casting away nullability”, as per the docs: https://dart.dev/null-safety/understanding-null-safety#null-assertion-operator
The variable chunkCallback must be able to accept null and you cannot call a function on a nullable type without either using ! or ?. This is part of Darts sound null safety
Some great videos on this:
Flutter vid
YouTube vid
Even though the conditional statement checks for null, Dart still requires the exclamation mark before the function call. The difference between using ! over ? is that it will throw an exception instead of using the variable if the value is null.
Some examples:
class Car {
String? make; // String or null type
Car([this.make]); // parameter is optional
}
main() {
Car test = Car('Ford'); // initialised with a value
Car test2 = Car(); // no value given so null is default
// returns 4
if (test.make != null) {
print(test.make!.length); // ! still needed even though !=null condition stated
} else {
print('The value is null');
}
// returns The value is null
if (test2.make != null) {
print(test2.make!.length);
} else {
print('The value is null');
}
}
Above example shows that conditional check for null is not enough.
And choosing between ? and !
class Customer {
String? name;
String? surname;
Customer(this.name, [this.surname]); // constructor with optional parameter []
}
main() {
Customer ford = Customer('John'); //only name is given a value
// calling a method on a nullable type doesn't work
// so ? and ! used here after variable name and before method
print(ford.name!.length); // operation executed as usual => 4
print(ford.surname?.length); // ? on null value returns null => null
print(ford.surname!.length); // Exception is thrown => TypeError
}
What should IEquatable<T>.Equals(T obj) do when this == null and obj == null?
1) This code is generated by F# compiler when implementing IEquatable<T>. You can see that it returns true when both objects are null:
public sealed override bool Equals(T obj)
{
if (this == null)
{
return obj == null;
}
if (obj == null)
{
return false;
}
// Code when both this and obj are not null.
}
2) Similar code can be found in the question "in IEquatable implementation is reference check necessary" or in the question "Is there a complete IEquatable implementation reference?". This code returns false when both objects are null.
public sealed override bool Equals(T obj)
{
if (obj == null)
{
return false;
}
// Code when obj is not null.
}
3) The last option is to say that the behaviour of the method is not defined when this == null.
leppie is right. Just to elaborate on his answer (and confirm his suspicion that F# doesn't guarantee this != null): discriminated unions may be marked with the attribute [<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>] allowing cases to be represented by the value null. Option<'T> is such a type. The None case is represented by null at run-time. (None : option<int>).Equals(None) is syntactically valid. Here's a fun example:
[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
type Maybe<'T> =
| Just of 'T
| Nothing
[<CompilationRepresentation(CompilationRepresentationFlags.Instance)>]
member this.ThisIsNull() = match this with Nothing -> true | _ -> false
Decompiling ThisIsNull with Reflector shows
public bool ThisIsNull()
{
return (this == null);
}
And the result:
Nothing.ThisIsNull() //true
The reason F# does this (I suspect) to optimize empty lists as null.
By adding this check, it allows one to call an instance method on a null instance without any problems.
See my blog post from a while back.
In C#, this is irrelevant.
To answer the question:
It should return true as both instances are null and deemed equal.
If this is null, the code can't be called, so that case needn't be considered (in C# anyway, there are cases where languages allow a null object to have a method dereferenced though obviously if it internally examines any of its non-existent fields it will error. Consider:
return x.Equals(y);
If x is null, we don't even get to call into Equals for the null check to count.
Hence we need only consider:
public bool Equals(T obj)
{
if(obj == null)
return false;
//logic defining equality here.
}
Where the possibility of both objects being null does come up, is when we are examining them from a static == operator override or from an IEqualityComparer<T> implementation:
public bool Equals(T x, T y)
{
if(x == null)
return y == null;
if(y == null)
return false;
//logic defining equality here.
}
Note that a useful shortcut here if equality can be lengthy to determine (e.g. comparing long strings), then we may take advantage of the fact that identity entails equality - that is something is always equal to itself, even Ayn Rand could figure that out ;) There are also algorithms that make comparing an item with itself quite common, making this shortcut well worth including. In this case the identity comparison already includes the check for both being null, so we leave it out again:
public bool Equals(T x, T y)
{
if(ReferenceEquals(x, y))
return true;
if(x == null || y == null)
return false;
//logic defining equality here.
}
For most methods I assume undefined behavior when called with this==null. That's because most programmers write their code under the assumption that this!=null, which is guaranteed by the C# specification if the calling code is written in C#.
That's why every sane caller of x.Equals(y) should either know for sure that that x is not null, or add a manual null check.
In most cases I wouldn't call Equals directly at all, but instead use EqualityComparer<T>.Default.
I would definitelly go with option 1:
if (this == null)
{
return obj == null;
}
if (obj == null)
{
return false;
}
null object always equals null object.
Sample code is in the MSDN: http://msdn.microsoft.com/en-us/library/ms131190.aspx?ppud=4
If this==null you will get a runtime exception calling Equals() on that object.
In Dart, checking for a value to be == null seems similar to checking if it is Null. Why is the former more preferable?
It is the type of comparison you are doing. In == null, you are comparing an object/primative to null whereas in the latter, is Null, null is an object. It makes no difference in the compiler.
== null is more favourable as the two being compared could be both primitives, which saves memory in the system.
== null is familiar to developers coming from other popular languages.
Using this type check in Dart not a quite correct.
if(value is Null) {
}
In Dart the static type of null is bottom type.
An as we know, the bottom type is a subtype of all types.
Now we test subtypes in Dart (including value of bottom type, null)
void main() {
var subtype = new Subtype();
var typeOfBase = new TypeOf<Base>();
test(subtype, typeOfBase);
test(null, typeOfBase);
}
void test(value, TypeOf typeOf) {
var type = typeOf.type;
var runtimeType = value.runtimeType;
if(typeOf.isSubtypeOf(value)) {
print("Value [$value] of type [$runtimeType] is a subtype of [$type[");
} else {
print("Value [$value] of type [$runtimeType] is NOT a subtype of [$type]");
}
}
class Base {
}
class Subtype implements Base {
String toString() => "subtype";
}
class TypeOf<T> {
Type get type => T;
bool isSubtypeOf(value) => value is T;
}
Results:
Value [subtype] of type [Subtype] is a subtype of [Base[
Value [null] of type [Null] is NOT a subtype of [Base]
So, here we can see that performing this test is not quite correct in Dart because null has not bottom type but a value of regular type Null which are not a bottom type and, of course, not a subtype of Base type.
So, I not recommend using is Null in Dart because in the future the class Null can be considered as deprecated and replaced by some internal implemented type as void or dynamic.