La frase è di Tony Hoare, in un discorso a una conferenza software nel 1965, riferita all'introduzione del valore null come riferimento agli oggetti nel linguaggio Algol W.
Eppure quello stesso valore null fu poi adottato da molti altri linguaggi orientati agli oggetti, tra i quali C++, Java, C# e via dicendo. L'effetto è stato di avere sistemi software che generavano errori imprevisti quando cercavano di eseguire operazioni su questo valore.
In anni recenti, in vari linguaggi, è stato finalmente posto rimedio a questa lacuna, con una gestione dei tipi che distingue in modo chiaro tra variabili nullabili e non. La convenzione sintattica più utilizzata è l'apposizione di un ? a seguito del nome del tipo.
Per comodità, vengono di solito messi a disposizione anche due operatori, ? e ??, che permettono rispettivamente di propagare il valore null al risultato dell'operazione, e di sostituire il valore null con un valore di default.
Ecco un semplice spezzone di codice Dart, per illustrare come gestisce i valori null un linguaggio null-safe:
// This variable cannot be null:
String name = 'Alice';
name = null; // Compile-time error
// This variable can be null (note the '?'):
String? nickname;
nickname = null; // OK
String upper = nickname.toUpperCase(); // Compile-time error
String? upperOrNull = nickname?.toUpperCase(); // OK, result is null
String nicknameOrDefault = nickname ?? "No nickname"; // OK, result is "No nickname"
// Non-nullable integer:
int age = 30;
age = null; // Compile-time error
// Nullable integer:
int? luckyNumber;
luckyNumber = null; // OK
int doubled = (luckyNumber ?? 0) * 2; // OK, uses 0 if luckyNumber is null
In sostanza ci sono due regole:
Queste regole vengono verificate già al momento della stesura del codice, o come si usa dire a tempo di compilazione.
In C# la gestione dei tipi nullabili è del tutto analoga, con un'eccezione: si fa distinzione tra tipi scalari e oggetti. Quindi, benché questo fatto sia spesso nascosto dall'utilizzo dell'operatore ??, per un tipo scalare la nullabilità può necessitare l'utilizzo delle proprietà HasValue e Value della variabile:
int? luckyNumber = null;
if (luckyNumber.HasValue)
Console.WriteLine($"Your lucky number is {luckyNumber.Value}");
else
Console.WriteLine("You don't have a lucky number set.");
// Assign a value
luckyNumber = 7;
if (luckyNumber.HasValue)
Console.WriteLine($"Your lucky number is {luckyNumber.Value}");
Questo si riflette anche su come i tipi sono gestiti attraverso il meccanismo della reflection:
class MyClass
{
public string Name { get; set; } = "Alice";
public string? NullableName { get; set; } = null;
public int Age { get; set; } = 30;
public int? NullableAge { get; set; } = null;
}
static void TestNullable()
{
var myClassType = typeof(MyClass);
var properties = myClassType.GetProperties();
foreach (var property in properties)
Console.WriteLine($"{property.Name}: {property.PropertyType}");
}
Si ottengono risultati diversi per gli oggetti e gli scalari:
Name: System.String
NullableName: System.String
Age: System.Int32
NullableAge: System.Nullable`1[System.Int32]
L'esempio precedente, tradotto in Dart, utilizzando il meccanismo dell'introspection (package mirror), rivela una gestione dei tipi nullabili più elegante e simmetrica:
class MyClass {
String name = "Alice";
String? nullableName;
int age = 30;
int? nullableAge;
}
void testNullable() {
// Get the class type of MyClass
var myClassMirror = reflectClass(MyClass);
// Iterate over the fields (instance variables) of MyClass
myClassMirror.declarations.forEach((key, declaration) {
if (declaration is VariableMirror) {
var fieldName = MirrorSystem.getName(key);
var fieldType = MirrorSystem.getName(declaration.type.simpleName);
print("$fieldName: $fieldType");
}
});
}
Si ottengono risultati anagoghi per gli oggetti e per gli scalari:
name: String
nullableName: String
age: int
nullableAge: int
Giorgio Barchiesi
Albo degli Ingegneri Sez. A, N. 4027 della Prov. di Trento
P.IVA 02370260222, C.F. BRC GRG 58L26 C794R
Copyright © 2015-2024 Giorgio Barchiesi - Tutti i diritti riservati