"Acesso condicional ?. e ??="
A?.B?.C sem erro quando um elo é nil, ?? para o valor de reserva e ??= para atribuir só se vazio.
O que é e por que existe#
Uma cadeia como LHead.Next.Next.Value falha com violação de acesso se qualquer elo for nil. Escrever um if por elo polui o código. O acesso condicional do C# (?.) interrompe a cadeia ao primeiro elo vazio e devolve "vazio" em vez de falhar. A página Nulos e exceções apresenta o tipo anulável ?T; aqui está o resto.
padrão (off) | NullConditional on | |
|---|---|---|
F := X?.Idade; com X = nil | a linha não acontece | X?.Idade é "vazio"; converter para Int32 falha em execução |
X?.M(a); (comando) | erro | nada acontece se X = nil (nem a é avaliado) |
X?[i] | dois tokens | indexação condicional |
A ?? B | igual | igual |
Semântica#
A?.B,A?.M(args),A?[i]: seAé nulo, o resto da cadeia não corre, inclusive os argumentos.A?.B.Cnão avalia.CcomAnulo;A?.B?.Ctesta os dois elos.Aé avaliado uma vez.- Em comando,
A?.M;não faz nada se um elo é nulo;A?.Free;zeraA. A?.B := Xé erro, como no C# (seAfor nulo não há onde gravar).- Operandos aceitos: classe, interface, ponteiro,
reference to, vetor dinâmico e anulável. Cadeia, inteiro e record sem Nullable dãoFE2080. - Tipo do resultado: referências ficam do mesmo tipo (vazio =
nil); qualquer outro tipo (Int32,Bool8…) vira o Nullable dele (NullableInt32…), procurado pelo nome.
?? e ??=#
A ?? Bé o valor ou a reserva; só avaliaBse preciso.A?.B ?? 0éInt32.X ??= DatribuiDsó seXé nulo, eDsó é avaliado nesse caso:LHead.Next.Next ??= TNode.Create(3, nil). Vale para Nullable, classe, interface, ponteiro e vetor dinâmico;Xnão pode conter chamadas (FE2089). Sem a chave,??=não existe.
program Nullable;
// Demo 31: valores que podem não existir - Nullable (?T), "?.", "??" e "??=".
//
// ?Int32 é um Int32 que também pode estar "vazio" (Null).
// "A?.B" lê B só se A não for nil; senão o resultado é "vazio" e o resto
// da cadeia nem executa. "??" dá um valor de reserva. "??=" atribui só se vazio.
{$config NullConditional on} // liga o "?." no estilo C# (desligado por padrão)
type
TNode = class
public
Value: Int32;
Next: TNode;
constructor Create(AValue: Int32; ANext: TNode);
end class;
constructor TNode.Create(AValue: Int32; ANext: TNode);
begin
Value := AValue;
Next := ANext;
end constructor;
var
LAge: ?Int32; // anulável: começa vazio
LHead, LNone: TNode;
LValue: Int32;
begin
// --- Nullable ---
WriteLn('tem valor? ', ?LAge);
WriteLn('com reserva: ', LAge ?? -1); // "??": vazio vira -1
LAge := 42;
WriteLn('tem valor? ', ?LAge, ' valor ', LAge!); // "!" extrai o valor
// --- ?. em cadeias de objetos ---
LHead := TNode.Create(1, TNode.Create(2, nil)); // 1 -> 2 -> fim
LNone := nil;
// sem "?." LNone.Value daria erro de acesso; com ele, vira vazio
LValue := LNone?.Value ?? 0;
WriteLn('de nil: ', LValue);
LValue := LHead?.Value ?? 0;
WriteLn('da cabeça: ', LValue);
LValue := LHead?.Next?.Value ?? 0;
WriteLn('segundo nó: ', LValue);
// o terceiro não existe: o "?." interrompe a cadeia sem erro
LValue := LHead?.Next?.Next?.Value ?? -1;
WriteLn('terceiro nó: ', LValue);
// --- ??= : atribui só se estiver vazio ---
LHead.Next.Next ??= TNode.Create(3, nil); // estava nil: cria
LHead.Next.Next ??= TNode.Create(99, nil); // já existe: nem avalia o lado direito
WriteLn('terceiro agora: ', LHead.Next.Next.Value);
end.tem valor? FALSE
com reserva: -1
tem valor? TRUE valor 42
de nil: 0
da cabeça: 1
segundo nó: 2
terceiro nó: -1
terceiro agora: 3Demo 31. Nullable, ??, cadeia que se interrompe sem erro e ??= que cria o nó só quando falta.
Comparações e contas "lifted"#
Com a chave ligada, um anulável vindo de ?. compara com o valor de dentro: A?.B > 5 é False se vazio; A?.B <> 5 é True; A?.B = nil pergunta só se há valor. Aritmética (+ - * / div mod and or xor shl shr, - e not unários) com operando vazio dá o Nullable vazio, e os dois operandos são avaliados, como no C#.
Armadilhas#
CastErrorem execução:F := A?.FValor;comF: Int32compila, mas levanta exceção se vazio. O compilador avisa (FW0227); escrevaA?.FValor ?? 0(ouA?.FValor!).C ? [1, 2] : [3]com a chave ligada exige o espaço depois do?: colado,C?[é o índice condicional.?.seguido de dígito deixa de ser o token (C?.5 : 1).- Fora do escopo:
A?.Bsobreprocedure … of objectcomo operando, e??=sobre property.
Veja também#
- Nulos e exceções, Fenner.Result para erros como valor.
- Demos
19-nullablee31-nullable-nullcond; manualnullcond.md.