Case of String
Como você sabe, a instrução case é uma alternativa mais legível para uma série de condições if aninhadas. Diferente de outras linguagens populares, a linguagem Delphi não permite usar case diretamente com strings, uma necessidade bem comum.
Pode-se até tentar explicar (mas não justificar) a limitação considerando as otimizações que o compilador realiza no código de um case. Limitando case aos tipos ordinais (numerais, enumeradores, Char e Boolean), o compilador Delphi ordena os valores de opções do para gerar um código mais eficiente.
O código abaixo exemplifica o uso regular do case:
procedure ExemploCase(i: Integer); begin case i of 1: { faz uma coisa }; 2: { faz outra coisa }; 3..5: { faz ainda outra }; else { senão faz isso mesmo } end; end;
Vamos às alternativas para implementar o equivalente a um case com strings. A primeira idéia é fazer mesmo uma seqüência de ifs aninhados.
procedure ExemploStr1(S: String); begin if S = 'banana' then {...} else if S = 'pera' then {...} else if (S = 'abacaxi') or (S = 'limao') then {...} else {...} end;
É ruim de ler, difícil de manter, aumenta a complexidade (ciclomática) do código e é propenso a erros de sintaxe. Ainda por cima, se o programador não se cuidar com os begins e ends, pode até produzir alguns bugs difíceis de encontrar.
Uma solução é declarar um array de strings constante, fazer uma busca sequencial simples para encontrar a posição da string e depois usar o índice da string em um case:
procedure ExemploStr2(S: String); const NumFrutas = 4; Frutas: array[1..NumFrutas] of String = ('banana', 'pera', 'abacaxi', 'limao'); var Index: Integer; begin for Index := 1 to NumFrutas do if S = Frutas[Index] then break; case Index of 1: {...}; 2: {...}; 3..4: {...} else {...} end; end;
Um problema comum às duas alternativas acima é a comparação de strings, que diferença de maiúsculas e minúsculas. Para resolver isso, basta substitutir
if (S = Frutas[Index]) then ...
pela função
if SameText(S, Frutas[Index]) then ...
Uma terceira alternativa, mais prática e legível, é usar a função StrIndex abaixo para obter o índice de um valor string em um array dinâmico de strings. StrIndex foi adaptada da unit JclStrings da JCL.
function StrIndex(const S: string; const List: array of string): Integer; var I: Integer; begin Result := -1; for I := Low(List) to High(List) do begin if AnsiSameText(S, List[I]) then begin Result := I + 1; Break; end; end; end;
Usando essa função fica muito fácil implementar um case de strings eficiente e legível. Por exemplo, a mesma procedure utilizando StrIndex ficaria:
procedure ExemploStr3(S: String); begin case StrIndex(S, ['banana', 'pera', 'abacaxi', 'limao']) of 1: {...}; 2: {...}; 3..4: {...} else {...} end; end;
A rotina StrIndex acima pode ser usada independente da JCL e tem a diferença de retornar um índice baseado em 1, não em 0, como a função da JCL. No meu caso, eu prefiro usar a própria função da JclStrings, já que uso com freqüência também outras funções da JCL.
Class helpers
Uma situação comum para quem usa a arquitetura DataSnap é tratar manualmente valores de campos no evento OnBeforeUpdate do componente TDataSetProvider. O evento tem um parâmetro DeltaDS que é um dataset. Para pegar o valor atual de um campo você usa a propriedade NewValue, certo? Bem, na verdade isso só é valido se o campo tiver sido editado pelo usuário (ou se for um Insert). Se, por outro lado, for um Update ou um Delete e o valor anterior do campo tiver sido mantido, você deve pegar o valor de OldValue. Não é muito intuitivo, certo?
Seria bom se existisse uma propriedade CurrentValue que sempre retornasse o valor atual do TField, seja qual for o estado do dataset. Read more
DBHelpers
DBHelpers é a minha unit de class helpers para TField e TDataSet. Você pode fazer o download do código fonte de dbhelpers.zip.
Class helper é um novo recurso da linguagem Delphi (salvo engano, disponível a partir do BDS2006) para estender a funcionalidade de uma classe existente com novos métodos ou propriedades.
Diferente do uso de herança, ao invés de criar uma classe descendente você declara um tipo “class helper for (AClass)”, onde AClass é a classe que você quer estender. O compilador entende que os seus métodos são válidos no escopo da classe associada (AClass) e de seus descendentes. Por exemplo, os métodos de TDataSetHelper podem ser usados como se pertencessem às classes TClientDataSet, TSQLDataSet ou qualquer outro desdente de TDataSet.
Um class helper não tem dados, mas pode acessar todos os campos, propriedades e métodos protegidos ou públicos da classe associada. É a versão “limpinha” do hack de fazer um typecast forçado de uma classe para outra classe descendente local. Em outras palavras, escrever um class helper é como legalizar na Prefeitura o “puxadinho” que você queria construir sobre a laje do barraco.
Essa é a interface da versão inicial de DBHelpers, que tem vários métodos úteis para uso em aplicações clientes (TDataSetHelper e TFieldHelper.AsStringTrim) e servidores DataSnap (CurrentValue, IsEmpty e OldIsNull de TFieldHelper) :
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | type TForEachRow = procedure (Sender: TDataSet; var Abort: Boolean) of object; TDataSetHelper = class helper for TDataSet public procedure DeleteAll; procedure ForEach(AMethod: TForEachRow; const AFilter: String = ''); procedure GetFieldValues(Strings: TStrings; Field: TField; Distinct: Boolean = True); overload; procedure GetFieldValues(Strings: TStrings; const FieldName: String; Distinct: Boolean = True); overload; procedure GetParamValues(Params: TParams); end; TFieldHelper = class helper for TField private function GetAsStringTrim: String; procedure SetAsStringTrim(const Value: String); function GetCurrentValue: Variant; procedure SetCurrentValue(const Value: Variant); public property AsStringTrim: String read GetAsStringTrim write SetAsStringTrim; property CurrentValue: Variant read GetCurrentValue write SetCurrentValue; function IsEmpty: Boolean; function OldIsNull: Boolean; end; |
Não tenho por enquanto mais documentação além do próprio código fonte, que é bem simples, mas sintam-se a vontade para perguntar se tiverem dúvidas.
