Atenção: esse artigo tem um vídeo complementar. Clique e assista!
Atenção: esse artigo tem uma palestra complementar. Clique e assista!
Curso de Delphi Prism – Parte 1
Curso de Delphi Prism – Parte 2
Curso de Delphi Prism – Parte 4
Lançado pela Embarcadero, empresa que adquiriu a CodeGear, o Delphi Prism é a nova forma de desenvolver aplicações para o .NET Framework e Mono utilizando o Object Pascal e o IDE do Visual Studio 2008. Nesta terceira parte do curso, apresento mais sobre os novos recursos da linguagem utilizada pelo Delphi Prism, que são inúmeros se compararmos com o Delphi for .NET tradicional ou mesmo Delphi Win32.
Para que serve
Comparar a sintaxe do antigo Pascal e Delphi tradicional com a moderna e poderosa linguagem suportada pelo Delphi Prism, conhecer os principais novos recursos da linguagem e vantagens.
Em que situação o tema é útil
O Delphi Prism abre uma porta incrível para os desenvolvedores Delphi, pois podem agora utilizar um IDE robusto, rápido e estável para construir aplicações para .NET, incluindo as novas tecnologias ASP.NET 3.5, ASP.NET AJAX, WPF, WCF, MONO etc. usando uma versão evoluída do Object Pascal, que mescla os melhores recursos do Delphi e do C# 3.0.
Resumo do DevMan
Neste artigo falaremos essencialmente de classes e como elas se comportam no Delphi Prism. O recurso de classes parciais permite que o código de uma mesma classe possa ser separado em diferentes arquivos de códigos-fontes. Tipos anônimos podem ser usados para, em uma mesma linha, declarar, instanciar e inicializar um objeto sem tipo. E classes / métodos de extensão permitem injetar código em classes pré-existentes, sem usar herança ou mesmo sem ter acesso ao código-fonte da classe alvo.
Classes, objetos, propriedades automáticas e Generics
As classes fazem parte da vida do desenvolvedor Delphi desde a sua primeira versão. Mas as coisas não eram simples no tempo do Pascal. Para quem lembra, tínhamos que fazer algo como na Listagem 1. Este fragmento de código foi retirado de um projeto que desenvolvi ainda nos tempos acadêmicos (1996), disponível com os fontes em no meu site no CodeCentral da Embarcadero (veja seção Links). Dados e funcionalidade ficavam separados, fazíamos um record para guardar os dados de uma entidade, depois uma variável file para gravar o record no disco e um buffer de memória para manipular o registro. E separadamente, os procedures para manipular esses dados.
Listagem 1. Records, variáveis globais e programação estruturada
TCliente = record
codigo: integer;
ai: char; // undelete à moda antiga
nome: string39;
...
var
Fclientes : file of TCliente;
Bclientes : TCliente;
...
Procedure open_Fclientes;
Procedure close_Fclientes;
Procedure Cria_Fclientes;
Procedure zera_cli;
Procedure menu_cli;
Procedure mostra_cli;
O suporte a objetos veio no Turbo Pascal 5.5, com a inclusão da palavra-chave object. Basicamente podíamos colocar os procedures que trabalhavam com os dados do record dentro do próprio record (que então virou object). Mais tarde, no Delphi 2, a palavra-chave foi renomeada para class, por questões óbvias, convenção que permanece até hoje, inclusive no Prism e C#. O record ainda existe, e evoluiu, podemos no Prism colocar procedures dentro de records. E em algumas versões do Delphi for .NET também. Ele é usado ainda hoje quando queremos criar estruturas que são passadas por valor, e não por referência, como o que acontece no caso de classes.
Tudo bem, mas o que isso tem a ver com esse curso? Novamente, estamos olhando para o passado para entender o presente e futuro. Vamos ver como seria minha antiga classe TCliente no Delphi Win32 mais próximo da atualidade (Listagem 2). Note alguns pontos importantes: a classe TCliente possui duas propriedades, Nome e Idade. Tudo bem, seria mais interessante guardar a data de nascimento, mas por questões didáticas preferi usar um tipo integer para simplificar o exemplo. As propriedades são amparadas por métodos get / set, que reforçam o princípio de encapsulamento no Object Pascal. Esses métodos são privados e nada mais fazem do que mapear os dados das propriedades públicas para campos privados. Na Listagem 3 temos um exemplo de uso da classe.
Listagem 2. Classes no Delphi Win32
unit uCliente;
interface
type
TCliente = class
private
FIdade: integer;
FNome: string;
function GetNome: string;
procedure SetNome(const Value: string);
function GetIdade(): integer;
procedure SetIdade(const Value: integer);
public
property Nome: string read GetNome write SetNome;
property Idade: integer read GetIdade write SetIdade;
end;
implementation
{ TCliente }
function TCliente.GetIdade(): integer;
begin
result := FIdade;
end;
function TCliente.GetNome(): string;
begin
result := FNome;
end;
procedure TCliente.SetIdade(const Value: integer);
begin
// Alguma validação
FIdade := Value;
end;
procedure TCliente.SetNome(const Value: string);
begin
// Alguma validação
FNome := Value;
end;
end.
Listagem 3. Instanciando uma classe no Delphi Win32
procedure TForm1.FormCreate(Sender: TObject);
var
Cliente: TCliente;
begin
Cliente := TCliente.Create();
Cliente.Nome := 'Ben Campbell';
Cliente.Idade := 21;
end;
E no Delphi Prism? Podemos fazer exatamente da mesma forma, igualzinho, o código compila sem problema algum. Porém, alguns novos recursos da linguagem, muitos oriundos do C# 2.0 e 3.0, permitem fazer a mesma coisa com código extremamente reduzido. Veja na Listagem 4 o mesmo exemplo anterior do Delphi Win32, agora adaptado para o .NET com Prism (como comentei no começo do curso, usaremos sempre aplicações ASP.NET – File>New Web Site). Aqui criei uma nova classe, no arquivo Cliente.pas (pasta App_Code), para isso, basta dar um duplo clique no nome do projeto no Solution Explorer e escolher Add New Item>Class. A classe se chama TCliente e tem duas propriedades. Note que, se não formos incluir alguma validação ou algum tratamento personalizado nos métodos get/set, podemos omiti-los. Também não precisamos declarar o campo privado. Esse recurso é conhecido como propriedades automáticas. A propósito, o C# tem o mesmo recurso, como vemos o código equivalente na Listagem 5. Esse tipo de classe é muito útil para criar DTO’s (Data Transfer Objects), para trafegar dados entre camadas em uma aplicação .NET (veja meu artigo da edição 98). Também usado em aplicações com frameworks de persistência e mapeamento objeto / relacional.
Listagem 4. CIasses no Delphi Prism, arquivo Cliente.pas
TCliente = public class
public
property Nome: String;
property Idade: Integer;
end;
Listagem 5. CIasses no C#
public class TCliente
{
public string Nome { get; set; }
public string Idade { get; set; }
}
Para usar a classe, podemos fazer algo como na Listagem 6. Para ilustrar o uso da classe, coloquei um GridView no Web Form. Criei três clientes, adicionei-os em uma coleção genérica (usando generics) e atribui essa lista à variável DataSource do GridView. O resultado é visto na Figura 1. Para fazer acesso à classe que está no arquivo cliente.pas, não precisamos colocar nada no uses do arquivo do Web Form. Isso porque aqui não existe o conceito de unit e sim namespace. Se não coloquei a classe em um namespace especial, então ele está no mesmo namespace default do projeto, logo, não precisa importar (que belo recurso!). Para instanciar clientes, usamos variáveis in-line, discutidas nas partes anteriores do curso (basicamente podemos agora declarar, instanciar e inicializar uma variável dentro do código, como no C#). Para instanciar objetos, usamos o novo operador new e não create. Usamos também construtores estendidos (também conhecido como object initializer), recurso que permite que passemos os valores das propriedades da classe sendo instanciada no parâmetro do construtor, sem a necessidade de criar overloads ou código extra após a criação do objeto (tipo o que fazíamos com with create do).
Listagem 6. Usando a classe
namespace;
interface
uses
System,
System.Web,
System.Web.UI,
System.Web.UI.WebControls,
System.Collections.Generic;
type
_Default = public partial class(System.Web.UI.Page)
protected
method Page_Load(sender: Object; e: EventArgs);
end;
implementation
method _Default.Page_Load(sender: Object; e: EventArgs);
begin
var Ben := new TCliente(Nome := "Ben Campbell", Idade := 21);
var Gui := new TCliente(Nome := "Guinther Pauli", Idade := 31);
var Rud := new TCliente(Nome := "Rudolfo Pauli", Idade := 30);
var Clientes := new List<TCliente>();
Clientes.Add(Ben);
Clientes.Add(Gui);
Clientes.Add(Rud);
GridView1.DataSource := Clientes;
GridView1.DataBind();
end;
end.
Figura 1. Objetos sendo exibidos no GridView
Agora vamos fazer um teste. Primeiro vamos gerar uma versão de build da aplicação ASP.NET, acessando o menu Build > Publish Web Site. Isso vai compilar o .pas e o .aspx (caso tenha desmarcado a opção para não gerar aspx) em um arquivo DLL, que é um assembly .NET. Depois, abrimos o Visual Studio 2008 Command Prompt na pasta do grupo de programas do VS no menu Iniciar. Digite então ILDASM (utilitário para fazer examinar o código IL descompilado da DLL) e abra a App_Code.dll, localizada no diretório informado no momento do Publish. Observe (Figura 2) que, mesmo usando propriedades automáticas, o Oxygene (o compilador do Prism) gerou nos bastidores as variáveis privadas e os métodos get e set. Você pode dar um dump para o disco do seu código descompilado em IL, usando o menu File.
Figura 2. Examinando o código IL – Compilador gerou classe completinha
O recurso Publish Web Site surgiu no Visual Studio 2005 (ASP.NET 2.0) e permite pré-compilar um site inteiro antes do seu deploy. Ele usa, nos bastidores, um utilitário de linha de comando chamado aspnet_compiler.exe.
IL (Intermediate Language) é um código intermediário gerado a partir da compilação de um código-fonte .NET, seja ele C#, Delphi Prism, VB.NET etc. Ou seja, não importa em qual linguagem você programar, o compilador vai gerar um IL que será praticamente o mesmo código gerado pelo compilador de outra linguagem. O IL permite assim que diferentes linguagens possam usar tipos, herança, polimorfismo entre si como se todas fossem escritas na mesma linguagem. Isso é possível graças a especificações definidas na CLS (Common Language Specification). Veja a sessão links.
ILDASM é um descompilador? Sim, porém ele não gera o código na linguagem original, e sim em IL. Mesmo assim, a IL é uma linguagem praticamente de alto nível, é possível examinar facilmente o código e lógica de uma aplicação compilada. Para evitar engenharia reversa do seu código e proteger a propriedade intelectual, use o Dotfuscator que acompanha o VS. Esse utilitário basicamente faz refactoring no assembly, renomeando métodos, variáveis (coloca nomes como “a”, “b”, “c’), sem no entanto alterar a funcionalidade.
Classes Parciais
Esse recurso surgiu no .NET Framework 2.0 / C# 2.0 e também é suportado pela versão atual do Prism. Em poucas palavras e indo direto ao ponto: o recurso de classes parciais permite que você codifique parte de uma classe em um arquivo, parte em outro, todas as classes tendo o mesmo nome. No momento da compilação, o compilador une todos os códigos e gera uma única a classe. Mas por que motivos eu precisaria disso? Veremos a seguir.
Em nosso exemplo, para ilustrar o recurso, dividi a classe Cliente em dois arquivos, cliente1.pas e cliente2.pas, como podemos ver na Listagem 7. Para ser possível usar o recurso, usamos a palavra-reservada partial class. Não se preocupe, você não receberá um Identifier redeclared. Se executar a aplicação, tudo funcionará normalmente, a Listagem 6 não precisa ser modificada.
Mesmo que não tivéssemos usado classes parciais, poderíamos ter classes com o mesmo nome, desde que estejam em namespaces diferentes. Por exemplo, temos um Button do ASP.NET e um Button no Windows Forms, classes com mesmo nome mas diferentes implementações.
Listagem 7. Classes parciais
[Arquivo Cliente1.pas]
TCliente = public partial class
public
property Nome: String;
end;
[Arquivo Cliente2.pas]
TCliente = public partial class
public
property Idade: Integer;
end;
Vantagens? O próprio .NET Framework, desde a versão 2, faz uso efetivo do recurso. Por exemplo, o código gerado para inicializar componentes do designer foi separado em aplicações Windows Forms, ou seja, duas classes, mesmo nome, mas você pode acessar os atributos (componentes) normalmente. No ASP.NET, algo similar, os controles não precisam ser mais declarados na classe de code-behind, devido ao uso de classes parciais nos bastidores (para comprovar, veja no código do nosso Web Form que o GridView não tem declaração, mas pode ser acessado via código, como se fosse um atributo normal da classe). Outro exemplo, o ASP.NET usa classes parciais para injetar propriedades no seu Web Form, com o recurso de Profiles, através da propriedade Profile.
Profiles é um recurso incluído no ASP.NET 2.0 (VS 2005) que tem relação com o gerenciamento de estado. Definimos uma estrutura de dados no Web.Config, como por exemplo, dados relativos ao usuário, e depois gravamos / lemos esses dados via código. As informações persistem entre diferentes sessões do usuário. Antes, para obter o mesmo efeito, era necessário gravar um cookie na máquina do usuário, gravar um ID e usar essa informação para recuperar informações do BD (como preferências) toda vez que o usuário entra em um Web Site. Profiles reduzem drasticamente o trabalho necessário para realizar essa tarefa. Mais adiante neste curso falaremos sobre isso.
Podemos ainda usar classes parciais para injetar código. É possível estender uma classe, desde que seja parcial e esteja no mesmo assembly, sem usar herança, class helpers ou extensão de métodos (visto a seguir). Isso dá uma maior flexibilidade à aplicação, facilitando testes, depuração, separação de lógica / interfaces etc.
Tipos anônimos
Agora vamos forçar um pouco e lembrar do desafio que eu propus na primeira parte desta série. O recurso de tipos anônimos surgiu originalmente no C# 3.0, disponível agora para Delphi Prism. Este recurso está intimamente relacionado com object initializers mostrado anteriormente. Um tipo anônimo é basicamente um bloco de código onde usamos um objeto, de uma classe, que não tem tipo, tudo é feito in-line. Estranho? Então veja na Listagem 8 o mesmo código da Listagem 6 adaptado para usar esse recurso.
Listagem 8. Anonymous Types em Delphi Prism
method _Default.Page_Load(sender: Object; e: EventArgs);
begin
// não existe a classe TCliente!
var Ben := new class(Nome := "Ben Campbell", Idade := 21);
var Gui := new class(Nome := "Guinther Pauli", Idade := 31);
var Rud := new class(Nome := "Rudolfo Pauli", Idade := 30);
var Clientes := new List<Object>();
Clientes.Add(Ben);
Clientes.Add(Gui);
Clientes.Add(Rud);
GridView1.DataSource := Clientes;
GridView1.DataBind();
end;
Observe atentamente a Listagem 8. Onde está a classe TCliente? Não estamos usando-a, aliás, você pode (e deve) remover o código que define a classe do projeto. O new não está instanciando um TCliente, estamos instanciando um tipo (class), sem nome. Mas e Nome e Idade? Nos bastidores, o compilador vai batizar o tipo no momento da compilação com um nome qualquer, nesse caso, f__AnonymousType1. Ele detecta também, pelos parâmetros passados ao construtor (que não são obviamente declarados), que o tipo anônimo tem duas propriedades, chamadas respectivamente de Nome e Idade. E por tabela, gera os atributos privados e os métodos get / set. Duvida? Então use o ILDASM para comprovar e veja a estrutura da classe gerada nos bastidores (Figura 3). Veja um detalhe interessante: nós criamos três objetos, cada um distinto do outro, por que não foram criados três tipos anônimos? O Oxygene detectou que os três tipos têm os mesmos atributos com os mesmos nomes e tipos (string e integer), logo, usa o mesmo tipo anônimo para os três. E mais, se você não remover a classe TCliente do projeto, verá que o Oxygene vai detectar que o tipo que estamos instanciando é um TCliente (é praticamente uma mágica!), e não cria uma classe anônima, ele detecta automaticamente pelos atributos que estamos instanciando um tipo já definido. Por esse motivo, para obter o resultado da Figura 3, você precisa apagar ou excluir do projeto (remover mas não apagar do disco) os arquivos que definem a classe TCliente. Se executar a aplicação, o resultado que obterá é o mesmo resultado da Figura 1. A propósito, o código equivalente em C# pode ser visto na Litagem 9.
Figura 3. Tipo anônimo é batizado na compilação
Listagem 9. Anonymous Types em C#
protected void Page_Load(object sender, EventArgs e)
{
var Ben = new {Nome = "Ben Campbell", Idade = 21};
var Gui = new {Nome = "Guinther Pauli", Idade = 31};
var Rud = new {Nome = "Rudolfo Pauli", Idade = 30};
var Clientes = new List<Object>();
Clientes.Add(Ben);
Clientes.Add(Gui);
Clientes.Add(Rud);
GridView1.DataSource = Clientes;
GridView1.DataBind();
}
Uma outra questão interessante: como nós vamos criar uma lista genérica (List<T>), que recebe um parâmetro Type, se não sabemos o tipo que estamos usando? Realmente complicado. Uma solução simples e mais correta é usar uma coleção não genérica. Ou simplesmente criar a List usando o tipo Object, já que, mesmo sendo anônima, nossa classe vai ter que herdar de alguma forma de Object (classe base de todos os objetos no .NET, equivalente ao TObject da VCL). Eu sei que com isso o Generics perde totalmente o sentido, mas pelo menos, foi preciso modificar apenas uma palavra em uma linha de código. Existem outras técnicas mais interessantes, como factories de tipos anônimos (veja seção Links). E finalmente, é perfeitamente possível usar tipos anônimos sem uma variável, por exemplo, adicionar o objeto sem tipo diretamente na collection, como:
Clientes.Add(new class(Nome := "Ben Campbell", Idade := 21));
Métodos de Extensão
Suponha que você queira implementar uma nova funcionalidade, por exemplo, a possibilidade de exportar os dados TClientDataSets para diferentes formatos além do XML e binário, como HTML, Excel, TXT, Word etc. (veja na seção Links como fazer isso). Usando boas práticas de orientação a objetos, poderíamos criar uma classe descendente de TClientDataSet, que implementa as novas funcionalidades, como no exemplo da Listagem 10. De cara já temos um problema, para usar a implementação em aplicações existentes, teremos que substituir todos os ClientDataSets por TClientDataSetEx. Outro problema: e se quisermos usar os métodos para outros tipos de TDataSets, como TSqlDataSet? Uma outra opção é criar uma util.pas e usar programação estruturada, parametrizando rotinas para receber um TDataSet (Listagem 11). Ou ainda, talvez a melhorzinha, criar uma classe (ex.: TExportDataSet) que tem uma relação de associação com um tipo TDataSet e possui os métodos.
Listagem 10. TClientDataSetEx
uses DBClient;
type
TClientDataSetEx = class(TCustomClientDataSet)
public
procedure ExpHTML(FileName: string);
procedure ExpTXT(FileName: string);
procedure ExpXLS(FileName: string);
procedure ExpDOC(FileName: string);
procedure ExpXML(FileName: string);
end;
…
Listagem 11. Util.pas
unit util.pas;
interface
uses DB;
procedure ExpHTML(DataSet: TDataSet; FileName: string);
procedure ExpTXT(DataSet: TDataSet; FileName: string);
procedure ExpXLS(DataSet: TDataSet; FileName: string);
procedure ExpDOC(DataSet: TDataSet; FileName: string);
procedure ExpXML(DataSet: TDataSet; FileName: string);
…
No Delphi Prism (e também C#) podemos usar um excelente recurso chamado extension methods para resolver o problema da forma mais elegante possível. Podemos injetar funcionalidade em uma classe existente, sem usar herança, nem class helpers (na verdade isso é muito semelhante a um class helper, do Delphi 8). A técnica é simples, criamos uma classe estática, decorada com o atributo Extension e colocamos um método também decorado com o mesmo atributo indicando qual classe queremos injetar o código. A partir daí, todos os locais que estiverem usando a classe alvo passam a enxergar o código injetado como se ele fosse feito na própria classe! É como se pudéssemos ter os fontes da classe e alterar ao nosso gosto.
Vamos a um exemplo real, que utilizei em um projeto recentemente. O ADO.NET contém a classe DataSet, que representa uma estrutura de dados em memória, semelhante ao TClientDataSet da VCL. O DataSet, assim como o CDS, pode ser representado em formato XML, porém, não usa a mesma estrutura especificada pelo DataSnap, os DataPackets. Então, criei um método que transforma estruturas DataSets em DataPackets do MIDAS, o que permite a troca de dados entre aplicações ASP.NET e servidores DataSnap, por exemplo, e vice-versa. A solução mais elegante que encontrei foi injetar na própria classe DataSet do .NET Framework o meu método personalizado. A partir daí, eu poderia usar o código em qualquer lugar, em qualquer projeto, acessando a própria classe DataSet. A Listagem 12 mostra como fazer isso. Note, no exemplo de uso ao final da listagem, que o método pertence à própria classe do framework, como se tivéssemos de posse dos seus fontes e pudéssemos alterá-los.
Listagem 12. Estendendo uma classe sem usar herança
[Código da classe de extensão]
namespace;
interface
uses
System,
System.Data,
System.Runtime.CompilerServices,
System.Xml;
type
[Extension]
ExtendDataSet = public static class
public
[Extension]
class method ToMidas(s: DataSet): XmlDocument;
end;
implementation
class method ExtendDataSet.ToMidas(s: DataSet): XmlDocument;
begin
// aqui vai a lógica da transformação
end;
end.
[Código no Web Form]
method _Default.TestandoDataSetEstendido();
begin
var ds := new DataSet();
// aqui você obtém dados para o DataSet
// Gera um DataPacket
// Repare que ToMidas é um método injetado
var MyXmlDataPacket := ds.ToMidas();
// faz algo com o DataPacket
end;
Conclusão
esde a primeira par/te do artigo, evoluímos muito os nossos conhecimentos em orientação a objetos com o Delphi Prism. Sem dúvida, a principal barreira é quebrar preconceitos. A rigidez do Pascal perdeu forças, no entanto, continuamos programando usando excelentes práticas e OO em alto nível. Lembre-se, novamente, do que disse logo no início deste curso: você precisa abrir a mente para aprender o Prism. Não veja as coisas como imutáveis e eternas.
Neste artigo, tocamos em um ponto delicado da OO: classes. Vimos que tipos podem nem mesmo ter um nome, podem ser totalmente declarados e inicializados em uma única linha. Classes parciais parecem coisa de outro mundo, ou POG. Mas não, como vimos, o próprio .NET Framework tira excelente proveito do recurso. E finalmente, vimos que injetar código em classes pré-existentes vai melhorar a legibilidade e tornar nosso código muito, mas muito mais OO, sem precisar recorrer à programação estruturada. Bem-vindos a um novo mundo e até a próxima edição.
How to create a generic List of anonymous
types?
kirillosenkov.blogspot.com/2008/01/how-to-create-generic-list-of-anonymous.html
Exportando
DataSets para XML, TXT, Word, Excel, HTML
www.devmedia.com.br/articles/viewcomp.asp?comp=13629
Página
do autor na Embarcadero com inúmeros projetos em Delphi e Turbo Pascal
cc.embarcadero.com/Author/222668
CLS
msdn.microsoft.com/en-us/library/12a7a7h3(VS.71).aspx
Blog
do autor
http://guintherpauli.blogspot.com
http://twitter.com/guintherpauli