Variant Open Array Parameters

Qualidade: 

Estrela ativaEstrela ativaEstrela ativaEstrela ativaEstrela ativa
 

Programadores x Curiosidade

Eu já falei outras vezes que uma das maiores características que um programador precisa ter é a curiosidade. Praticamente todo e qualquer sistema que você fez ou fará, foi ou será baseado em algum outro sistema que você já tenha visto ou no comportamento de alguma "entidade" da vida real, para a qual você pretenda fazer alguma automação programática. Essa observação e esta tomada de decisão só são possíveis se você for uma pessoa curiosa. A curiosidade te motiva a tentar imitar fórmulas de sucesso, claro! Ninguém em sã consciência se espelha em maus hábitos e na programação, como tudo na vida, isso não é diferente.

Sempre que eu usava a rotina Format eu achava curioso o seu segundo parâmetro. Ele era passado como um Open Array, no entanto era possível passar valores de tipos diferentes, logo, um array heterogêneo! Depois de algum tempo eu descobri que esse parâmetro tinha um nome oficial. Tratava-se do Variant Open Array Parameter, ou V.O.A.P. para resumir.

Está gostando do que está lendo? Ajude nosso site visitando nossos patrocinadores. Obrigado! :)

Open Array Parameter

Antes de falar a respeito do V.O.A.P. vou explicar rapidamente o que é um Open Array Parameter (O.A.P.). Um O.A.P. nada mais é do que um array homogêneo usado como parâmetro de uma rotina (procedure ou function) e tem a seguinte forma:

procedure MeuProcedure(const PMeusParametros: array of integer);

Um O.A.P. é bem semelhante a um array dinâmico, mas ele não é um array dinâmico. Observe o seguinte trecho de pseudocódigo (comentários no próprio código):

{ Abaixo, temos a declaração de um tipo que é um alias para 
um array de inteiros }

type
  TIntegerArray = array of integer;

{O procedure abaixo tem um parâmetro Open Array}

procedure OpenArray(const PIntegerArray: array of integer);
begin

end;

{ O procedure abaixo tem um parâmetro que é um array dinâmico }

procedure DynamicArray(PIntegerArray: TIntegerArray);
begin

end;

{ Declara três variáveis: um array dinâmico de inteiros, 
um array estático de inteiros e um TIntegerArray (alias
para um array de inteiros). A última variável é simplesmente 
uma variável do tipo integer }

var
  ArrayDinamico: array of integer;
  ArrayEstatico: array [0..9] of integer;
  ArrayNomeado: TIntegerArray;
  ValorInteiro: integer;

begin
  { O procedure que tem um parâmetro Open Array aceita todas 
  as variáveis que representam de alguma forma um array de 
  inteiros, incluindo o array estático. Veja abaixo }

  OpenArray(ArrayDinamico);
  OpenArray(ArrayEstatico);
  OpenArray(ArrayNomeado);

  { Adicionalmente o parâmetro Open Array pode receber valores 
  diretamente, por meio do chamado Open Array Constructor.
  Passar valores desta forma torna a declaração de variáveis
  do tipo array desnecessária. Note que é possível informar 
  variáveis no Open Array Constructor. Constantes também são válidas! }

  OpenArray([1,2,ValorInteiro,4]);

  { O procedure que tem um parâmetro que é um array dinâmico, por outro
  lado, somente aceita parâmetros que são arrays dinâmicos propriamente
  ditos do tipo que foi declarado para o parâmetro, no caso apenas 
  TIntegerArray }

  DynamicArray(ArrayDinamico); // Erro de compilação
  DynamicArray(ArrayEstatico); // Erro de compilação
  DynamicArray([1,3]);         // Erro de compilação 
  DynamicArray(ArrayNomeado);  // Sucesso!
end;

Como se pode observar, a diferença entre parâmetros Open Array e parâmetros que são arrays dinâmicos simples é bem mais evidente do que parece. Se nota também o porquê do nome ser "Open Array", é porque é possível passar qualquer tipo de representação de arrays, sejam eles estáticos, dinâmicos, aliases ou mesmo construídos on the fly.

Está gostando do que está lendo? Ajude nosso site visitando nossos patrocinadores. Obrigado! :)

Open Array Parameter com valores heterogêneos

Entendido o que são os O.A.P. chegou a hora de conhecer o próximo nível: como permitir a passagem de valores de tipos diversos. Já falei no início do artigo que isso é possível usando-se V.O.A.P. que nada mais são do que O.A.P. formados por elementos do tipo TVarRec. Sendo assim os V.O.A.P. podem ser definidos da seguinte forma:

procedure VariantOpenArray1(VOAP: array of TVarRec);
procedure VariantOpenArray2(VOAP: array of const);

Ambas as formas definem um método que pode aceitar uma quantidade ilimitada de elementos de qualquer tipo1. Escrever array of const ou array of TVarRec é exatamente a mesma coisa, mas a segunda forma deve ser usada preferencialmente com o intuito de favorecer a legibilidade do seu código.

Abaixo está um exemplo de uma aplicação de console que define uma função (ConcatenarTudo) para concatenar vários valores em uma string:

program VOAP;

{$APPTYPE CONSOLE}

uses
  SysUtils, DateUtils, Windows;

function ConcatenarTudo(PParts: array of const): String;
var
  i: Byte;
  FS: TFormatSettings;
begin
  Result := '';
  if High(PParts) > -1 then
  begin
    GetLocaleFormatSettings(1033,FS);

    for i := 0 to High(PParts) do
      case PParts[i].vType of
        vtInteger: Result := Result + IntToStr(PParts[i].VInteger);
        vtBoolean: Result := Result + BoolToStr(PParts[i].VBoolean);
        vtChar: Result := Result + String(PParts[i].VChar);
        vtExtended: Result := Result + FloatToStr(PParts[i].VExtended^,FS);
        vtString: Result := Result + String(PParts[i].VString^);
        vtWideChar: Result := Result + PParts[i].VWideChar;
        vtAnsiString: Result := Result + String(PAnsiChar(PParts[i].VAnsiString));
        vtCurrency: Result := Result + CurrToStr(PParts[i].VCurrency^,FS);
        vtWideString: Result := Result + PWideChar(PParts[i].VWideString);
        vtInt64: Result := Result + IntToStr(PParts[i].VInt64^);
      end;
  end;
end;

const
  INTEGER_VALUE: Integer = High(Integer);
  BOOLEAN_VALUE: Boolean = True;
  CHAR_VALUE: Char = 'Ñ';
  EXTENDED_VALUE: Extended = PI;
  DATETIME_VALUE: TDateTime = 28837.64605324074000000000; // 13/12/1978 15:30:19
  SHORTSTRING_VALUE: ShortString = 'Zetta-Ømnis Soluções Tecnológicas';
  WIDECHAR_VALUE: WideChar = 'Ð' ;
  ANSISTRING_VALUE: AnsiString = 'Carlos Barreto Feitoza Filho';
  CURRENCY_VALUE: Currency = 1234567.8901;
  WIDESTRING_VALUE: WideString = 'Delphi Experts Consortium';
  INT64_VALUE: Int64 = High(Int64);
var
  Resultado: String;
begin
  {$IFNDEF UNICODE}
  SetConsoleOutputCP(CP_UTF8);
  {$ENDIF}

  Resultado := UTF8Encode(ConcatenarTudo([INTEGER_VALUE
                                         ,BOOLEAN_VALUE
                                         ,CHAR_VALUE
                                         ,EXTENDED_VALUE
                                         ,DATETIME_VALUE
                                         ,SHORTSTRING_VALUE
                                         ,WIDECHAR_VALUE
                                         ,ANSISTRING_VALUE
                                         ,CURRENCY_VALUE
                                         ,WIDESTRING_VALUE
                                         ,INT64_VALUE]));

  WriteLn(Resultado);

  Readln;
end.

A linha 14 faz uma verificação para saber se existem valores passados no V.O.A.P. A verificação consiste em consultar se o retorno da pseudo-função High é maior ou igual a zero.

A linha 16 carrega a variável FS do tipo TFormatSettings com as configurações regionais referentes aos Estados Unidos (código 1033). Isso é opcional, mas eu resolvi fazer deste modo de forma que as conversões de números com casas decimais (Extended e Currency) em String sejam consistentes em qualquer idioma. Fazendo deste modo eu garanto que a representação em forma de string de tais números vai conter um ponto como separador de decimais.

As linhas 18 a 30 varrem o V.O.A.P. e, de acordo com o tipo especificado no membro vType, realizam as conversões necessárias para gerar a representação textual de cada tipo. O valor é concatenado no retorno da função (Result).

As linhas 49 a 51 configuram o console para que ele exiba corretamente caracteres acentuados. Essa configuração só deve ser realizada em Delphis ANSI, por isso foi usada a diretiva de compilação para detectar se o Delphi em questão é Unicode ou não.

As linhas 53 a 63 executam a função de concatenação (ConcatenarTudo) e guardam o resultado na variável Resultado, a qual será exibida no console na linha 65. O uso da função UTF8Encode só é necessária para exibição correta de caracteres acentuados no console e só faz sentido para este exemplo.

O resultado da execução deste programa de console é exibido na imagem abaixo:

Conclusão

Provavelmente você nunca utilizou algo assim em seus programas, mas garanto que agora que você tomou conhecimento desta técnica, ficará tentado a aproveitar esta vantagem de poder trabalhar com uma quantidade variável de parâmetros de qualquer tipo. Eu quero então alertar para uma coisa, caso não tenha ficado claro: a vantagem de se ter uma função assim é inversamente proporcional à quantidade de vezes que você a implementa.

O que eu quero dizer é que você não deve criar funções com V.O.A.P. sem qualquer critério, sob o pretexto de que eles são o supra-sumo da flexibilidade. Lembre-se que o Delphi é uma linguagem fortemente tipada e formal. Rotinas com vários parâmetros são totalmente aceitáveis e até mais eficientes do que o uso de V.O.A.P. Lembre-se que ao usar V.O.A.P. você precisará lidar com um array, circulando por entre seus membros. Fica claro então que você deve usar esta técnica com parcimônia.

Está gostando do que está lendo? Ajude nosso site visitando nossos patrocinadores. Obrigado! :)
e-max.it: your social media marketing partner
1Na verdade, um V.O.A.P. pode aceitar todos os tipos primitivos de dados, mais objetos, classes e ponteiros. O exemplo apresentado neste artigo só mostra como lidar com tipos primitivos de dados. Para uma lista completa de todos os tipos de dados que podem ser passados em um V.O.A.P. abra a unit System e procure pelo record TVarRec