Nulos e exceções
O valor que pode não existir (?T, ??, ?., !), try/except/finally, raise e o que acontece com erros de hardware.
O valor que pode não existir#
program Nullable;
type
TCustomer = class(TObject)
public
Age: Int32;
end class;
var
Age: ?Int32; // o valor que pode não existir
Customer: TCustomer;
N: Int32;
begin
WriteLn('tem valor? ', ?Age);
WriteLn('com padrão: ', Age ?? -1); // vazio vira -1
Age := 42;
WriteLn('tem valor? ', ?Age);
N := Age!; // exige valor
WriteLn('N = ', N);
Age := Null;
Customer := nil;
N := 7;
N := Customer?.Age; // Customer é nil: esta linha não acontece
WriteLn('N continua ', N);
Customer := TCustomer.Create;
Customer.Age := 30;
N := Customer?.Age;
WriteLn('N agora ', N);
// if como valor, nas duas grafias
WriteLn(if ?Customer then Customer.Age else 0);
WriteLn(?Customer ? Customer.Age : 0);
end.tem valor? FALSE
com padrão: -1
tem valor? TRUE
N = 42
N continua 7
N agora 30
30
30?T é o NullableT da RTL escrito curto. A RTL o declara a partir de um genérico (TNullable<T>: um IsValid e um Data); o compilador não fabrica o tipo, ele o procura, e reconhece qualquer record com essa forma, venha de onde vier.
| Forma | O que faz |
|---|---|
X: ?Int32 | declara o anulável; Null é o vazio, e 42 o atribui |
?X | pergunta se há valor. Num anulável é o IsValid; num ponteiro, objeto, interface ou tipo procedural é o <> nil. Num tipo que sempre tem valor é erro de compilação (Int32 sempre tem valor) |
A ?? B | o valor de A, ou B se A estiver vazio. Só o lado necessário é avaliado |
E! | exige o valor; se não houver, a linha não acontece |
X?.M | acesso guardado: se X for nil, a linha não acontece |
Atribuir um anulável a um Int32 direto (F := I;) compila, mas é uma conversão que falha: se I estiver vazio, o programa levanta CastError em execução. Para dizer o que fazer com o vazio, escolha: F := I ?? 0 (usa um padrão) ou F := I! (a linha não acontece).
"A linha não acontece"#
E! e X?.M dizem a mesma coisa, e isso não pode ser respondido dentro da expressão (um Int32 não tem como valer "nada"). Então a pergunta sobe e o comando inteiro fica sob um if:
F := X?.Idade;
// vira: $nz1 := X; if $nz1 <> nil then F := $nz1.Idade;O objeto oculto existe para o objeto não ser lido duas vezes, e é fraco (um apelido de um instante, não posse). Várias guardas na mesma linha entram todas, ligadas por and. Dentro de um laço, cada comando do corpo tem a sua guarda; a do cabeçalho envolve o laço inteiro.
Compare com o acesso comum: F := X.Idade com X igual a nil estoura, como sempre estourou.
O if que dá um valor#
F := if ?X then X.Idade else 0;
F := ?X ? X.Idade : 0; // o mesmo, na outra grafiaSó o ramo escolhido é avaliado. Tipo gerenciado nos ramos ainda não: só um dos lados roda, e a contagem de referências precisaria de um caminho próprio.
Exceções#
program Exceptions;
type
EValidation = class(Exception)
end class;
procedure Check(const AValue: Int32);
begin
if AValue < 0 then
raise EValidation.Create('valor negativo: ' + IntToUStr(AValue));
end procedure;
var
Zero: Int32;
begin
try
Check(5);
Check(-1);
except
on E: EValidation do
WriteLn('validação: ', E.Message);
on E: Exception do
WriteLn('outra: ', E.Message);
end try;
// atalho: raise com texto levanta uma Exception com essa mensagem
try
raise 'só o texto';
except
on E: Exception do
WriteLn(E.Message);
end try;
// erros de hardware viram exceções que o try..except pega
Zero := 0;
try
WriteLn(10 div Zero);
except
on E: EDivByZero do
WriteLn('divisão por zero capturada');
end try;
// finally roda de qualquer jeito
try
try
Check(-7);
finally
WriteLn('finally executou');
end try;
except
on E: Exception do
WriteLn('chegou ao except: ', E.Message);
end try;
end.validação: valor negativo: -1
só o texto
divisão por zero capturada
finally executou
chegou ao except: valor negativo: -7try
Check(5);
except
on E: EValidation do
WriteLn('validação: ', E.Message);
on E: Exception do
WriteLn('outra: ', E.Message);
end try;try … except … endcom um tratadoron E: Classe dopor tipo. O primeiro que casa responde.try … finally … endroda o blocofinallyde qualquer jeito, inclusive comExit,Breakou uma exceção.raise Classe.Create('mensagem')levanta;raise;dentro de um tratador repropaga.raise 'texto'é o atalho: levanta umaExceptioncom essa mensagem.- A mensagem de uma exceção é
UString.
Erros de hardware#
A RTL instala um tratador para falhas de hardware, e algumas viram exceções que o try … except pega, como EDivByZero (divisão inteira por zero). A divisão é checada explicitamente antes do sdiv/udiv, com {$config divcheck on|off} (padrão ligado), porque sem isso o otimizador devolvia 0 em silêncio a partir do -O1.
Verificações de faixa e de transbordo#
Duas verificações em execução existem e vêm desligadas por padrão, porque custam:
{$config rangechecks on}
{$config overflowchecks on}A grafia clássica {$RANGECHECKS ON} e {$OVERFLOWCHECKS ON} também vale.