StringInSet

Se você está migrando de versões antigas do Delphi (5,6,7 ou 2007) para o Delphi 2010 ou maior (XE, XE2, XE3 ou XE4) já deve ter passado pela mensagem “[DCC Warning] Unit1.pas(27): W1050 WideChar reduced to byte char in set expressions. Consider using ‘CharInSet’ function in ‘SysUtils’ unit.”, um Warning muito comum que foi adicionado indicando possíveis falhas de comparação quando utilizamos o operador in para fazer comparações de caracteres em um set de dados. Inspirado neste erro, e baseando-me em algumas necessidades diárias descobrimos a função perfeita para saber se uma String pertence a um conjunto de Strings.

O código a seguir, no Delphi 2010 ou acima, produz o Warning W1050, que avisa possível falha ao comparar um caracter no set.

if 'A' in ['X','Y','A'] then
  ShowMessage('A está na coleção');

Mas por que isso?
Versões mais antigas do Delphi, até a versão 2007, utilizam AnsiString e AnsiChar para trabalhar com strings. Porém com a evolução do sistema operacional Windows, houve a necessidade de ampliar a tabela ANSI (onde um caractere era representado por um byte) para permitir novos caracteres, então foi criado o Unicode (onde um caractere é representado por 2 bytes), sendo possível expandir cadeias de caracteres de 255 bytes para 2Gb aproximadamente.

O próprio compilador sugere o uso de uma função encontrada na unit SysUtils chamada CharInSet que nada mais faz do que “converter” o caracter procurado em um AnsiChar e o set de caracteres em um TSysCharSet que, internamente, é tratado como um set de AnsiChar. Assim a comparação sempre terá o resultado esperado.

Baseando-se apenas no nome da função, trazemos uma função que permite eliminar extensos “ifs” com vários “ands” que mais parecem monstros quando estamos olhando para eles. Considere que o código abaixo faz uma verificação para saber se o primeiro nome do usuário pertence ao grupo de nomes comuns para então dar-lhe uma mensagem. Se fôssemos fazer a condição com if e and, teríamos algo parecido com:

if (Edit1.Text = 'MARIA') or (Edit1.Text = 'JOSÉ') or
  (Edit1.Text = 'JOÃO') or (Edit1.Text = 'LUIZ') then
  ShowMessage('Seu nome foi encontrado na lista.');

Claro que o exemplo do nome na prática não é muito útil, mas pense naquelas condições quase que intermináveis, cheias de or que acabam deixando o programador maluco.

Agora veja que podemos escrever uma função onde passamos uma String no primeiro parâmetro e no segundo, um array de String. Ela se encarregaria em percorrer o set de Strings e verificar se algum dos elementos corresponde exatamente ao procurado, quase como a função Pos ou a função IndexOf da classe TStrings.

function StringInSet(const S: String;
  const StringSet: array of String): Boolean;
var
  I: Integer;
begin
  Result := False;
  for I := Low(StringSet) to High(StringSet) do
    if S = StringSet[I] then
    begin
      Result := True;
      Break;
    end;
end;

Uma solução simples para um problema simples, mas que pode ser aplicada a várias outras ocasiões. Você pode também já ter uma variável do tipo array of String que já esteja previamente preenchida com dados de uma tabela do banco de dados ou de qualquer outro lugar. O que importa é que assim eliminamos aqueles “ifs” gigantescamente incômodos.

if StringInSet(Edit1.Text, ['MARIA','JOSÉ','JOÃO','LUIZ']) then
  ShowMessage('Seu nome foi encontrado na lista.');

Conclusão
Com algumas modificações podemos criar funções muito úteis para utilizarmos principalmente com o uso de strings. A função CharInSet foi criada para que o tratamento dos caracteres de uma coleção fosse feito corretamente, porém aproveitamos a idéia para criar nossa StringInSet que trabalha com arrays de string.
Se compararmos a grande cadeia de “if or” com o nosso novo código, percebemos que o resultado acaba sendo mais limpo, compreensível e elegante.