Variáveis para Números Inteiros
byte = Comporta de 0 até 255 byte idade = 45;
short = Comporta de -32.768 até 32.767 short idade2 = 50;
int = Comporta de -2.147.483.648 até 2.147.483.647 int idade_planeta = 234982347;
long = Comporta de -9.223.372.036.854.775.808 até 9.223.372.036.854.775.807 long valor_enorme = 3245683746354;
Variáveis para Números Decimais (Utilizadas em Operações Financeiras)
float = Comporta até 7 casas depois da vírgula ex: 0,0000000 float valor1 = 10.5f;
double = Comporta até 15 casas depois da vírgula ex: 0,000000000000000 double valor2 = 20.3d;
Variáveis Alfanuméricas
*** Aceitam todos os tipos de caracteres (números, letras, símbolos) que serão todos lidos no formato texto. *** Números serão lidos como texto, sem função numérica. *** Estas variáveis podem ser concatenadas com outras variáveis ex: caixa_texto.Text = "O meu nome é " + nome + " " + sobrenome + " e a minha idade é " + idade.ToString() + " anos.";
continuação de Variáveis do tipo Alfanumérico...
string texto = “Wagner da Paixão Santos”;
char caracter1 = ‘a’; char caracter2 = ‘5’;
Variável do tipo Booleana (verdadeiro ou falso)
bool decisao1 = true; bool decisao2 = false;
Introdução a Variáveis no C# Facebook Twitter (18) (0)
Veja neste artigo dicas de como criar variáveis no C#, além de entender os principais tipos de variáveis. No desenvolvimento de software um recurso muito utilizado é a variável e este artigo pretende demonstrar como criá-las, os tipos e algumas regras para criação das mesmas. As variáveis são utilizadas para armazenar informações na memória em tempo de execução da aplicação, isso significa que essas informações estarão disponíveis enquanto a aplicação estiver em execução e, mais precisamente, enquanto a classe ou método em que ela foi criada estiver em execução. No C# toda variável deve ter: modificador de acesso, tipo de dados e nome.
A regra para nomear uma variável é que o nome dela sempre comece por uma letra ou _. No meio do nome podem-se usar números, mas não se devem usar caracteres especiais e também não pode ser uma palavra reservada. Entende-se por palavras reservadas os comandos do C# e que são facilmente identificadas quando digitadas, por ficarem da cor azul. Exemplos de palavras que não podem ser utilizadas são: if, for, while, string e etc. Exemplo de variável: int numero;
No exemplo, int é o tipo e numero é o nome da variável. Mas, e o modificador? Como dito anteriormente, toda variável deve ter o modificador de acesso. No exemplo acima não tem um modificador porque no C# quando não há um modificador em uma variável e é atribuído a ele o modificador padrão private.
Modificadores No caso das varáveis, os modificadores definem a visibilidade delas, se elas poderão ser acessadas por outras classes que não seja a sua própria, se serão acessadas somente por classes derivadas a classe que ela está e assim por diante, como mostra a Listagem 1. Listagem 1.Utilização de modificadores. Public int numero;
No C# existem os seguintes modificadores:
Modificador
Funcionamento
public
O acesso não é restrito.
protected
O acesso é limitado às classes ou tipos derivados da classe que a variável está.
Internal
O acesso é limitado ao conjunto de módulos (assembly) corrente.
protected internal
O acesso é limitado ao conjunto corrente ou tipos derivados da classe recipiente.
private
O acesso é limitado à classe que a variável está.
Tabela 1: Modificadores de acesso -Fonte: Adaptado de Microsoft
Tipos de dados C# é uma linguagem de programação fortemente tipada, isso significa que todas as variáveis e objetos devem ter um tipo declarado. Os tipos de dados são divididos em value types e reference types. Os value types são derivados de System.ValueType e reference types são derivados de System.Object.
Tipo de dados
Intervalo
byte
0 ..255
sbyte
-128 ..127
short
-32,768 ..32,767
ushort
0 ..65,535
int
-2,147,483,648 ..2,147,483,647
uint
0 ..4,294,967,295
long
-9,223,372,036,854,775,808 ..9,223,372,036,854,775,807
ulong
0 ..18,446,744,073,709,551,615
float
-3.402823e38 ..3.402823e38
double
-1.79769313486232e308 ..1.79769313486232e308
decimal
-79228162514264337593543950335 ..79228162514264337593543950335
char
U+0000 .. U+ffff
float
-3.402823e38 ..3.402823e38
double
-1.79769313486232e308 ..1.79769313486232e308
Variáveis Value Type As variáveis value type contém dentro delas um valor, enquanto as reference type contém uma referência. Isso significa que se copiar uma variável do tipo value type para dentro de outra o valor é copiado e, se o mesmo for feito com uma do tipo reference type será copiado apenas a referência do objeto. Dentro de Value Type existem duas categorias: struct e enum. a) Struct: é dividida em tipos numéricos, bool e estruturas personalizadas pelo usuário. Os tipos numéricos são os presentes na tabela abaixo: Tipo bool representa um valor que pode ser verdadeiro ou falso, o seu valor padrão é false. b) Enum: permite criar um tipo que é formado por várias constantes. Normalmente é usada quando em algum momento existe a necessidade de um atributo que pode ter múltiplos valores, como por exemplo, em uma aplicação de venda que tem a tela de pedidos: cada pedido tem a sua situação que pode ser Aberto, Faturado e Cancelado. Para criar a classe Pedido, o tipo do atributo situação será int, conforme mostra a Listagem 2. Listagem 2. Classe pedido. class Pedido
{
public int numero;
public DateTime dataHora;
public int situacao;
}
Agora é necessária a criação do Enum Situacao. Um Enum é criado da mesma porque se cria uma classe, mas ao invés de utilizar a palavra class usa-se enum, conforme mostra a Listagem 3. Listagem 3. Enum Situacao. enum Situacao
{
Aberto,
Faturado,
Cancelado
}
}
Na Listagem 4 contém o código da utilização deste Enum que foi criado. Listagem 4. Utilizando o Enum criado. class Program
{
static void Main(string[] args)
{
Pedido pedido = new Pedido();
pedido.numero = 1;
pedido.dataHora = DateTime.Now;
pedido.situacao = (int) Situacao.Faturado;
Console.WriteLine("Número do pedido: "
+ pedido.numero.ToString());
Console.WriteLine("Número do pedido: "
+ pedido.dataHora.ToString());
Console.WriteLine("Número do pedido: "
+ pedido.situacao.ToString());
Console.ReadLine();
}
}
O Enum por padrão retorna int e neste exemplo temos três opções, onde cada uma possui um índice. Então quando o valor de um Enum é atribuído a um outro atributo ,o que é atribuído é seu índice, no caso do exemplo da Listagem 4 é atribuído o valor 1.
Variáveis Reference Types As variáveis reference type armazenam apenas a referência do objeto. Os tipos de referência são: class, interface, delegate, object, string e Array. a) Tipo object: todos os tipos são derivados da classe Object, sendo assim é possível converter qualquer tipo para object. b) Tipo string: é utilizado para se armazenar caracteres e uma string deve estar entre aspas, como mostra a Listagem 5.
Listagem 5. Utilização de string string nome = "DevMedia";
Para concatenar (juntar) uma ou mais strings é usando o sinal de +, como mostra a Listagem 6. Listagem 6. Concatenando strings. string a = "C#";
string b = ".net";
string c = a + b;
Outro recurso também é a possibilidade de se obter um determinado caractere de uma string, como mostra a Listagem 7. Listagem 7. Pegando um caractere de uma string. string a = "C#";
string b = ".net";
string c = a + b;
char d = c[3];
A variável d vai ficar com o valor da letra n que é a letra que está no índice 3, conforme informado no código. Também é possível fazer a mesma coisa com uma palavra, como mostra a Listagem 8. Listagem 8. Pegando um caractere de uma palavra. char d = "C#.net"[3];
Existem muitas outras operações para fazer com variáveis, mas isso é assunto para um próximo artigo. O que foi mostrado nesse artigo são passos iniciais para entender como funcionam as variáveis no C#.
Tipos de Conversões de Variáveis: (Implícito, Explícito, Boxing, Unboxing e Var) Uma situação comum para o desenvolvedor é a conversão de valores em uma aplicação, por exemplo, quando possui um formulário onde o usuário digita as informações e as mesmas são passadas para variáveis ou atributos de objetos para depois serem gravadas no banco de dados ou realizar alguma outra operação. No nosso exemplo, os dados vindos do formulário serão do tipo String e no caso de um campo que contenha número precisará ser convertido para inteiro para poder ser armazenado na variável. O conteúdo poderá ser convertido para inteiro utilizando o código da Listagem 1. Listagem 1. Conversão de valor string para inteiro. int numero; numero = Convert.ToInt32(textBox1.Text);
Tipos de conversão No C# existem vários tipos de conversões de dados. Vejamos alguns deles. 1. Conversão implícita: é aquela em que não é necessário nenhum código especial, pois se trata de uma conversão segura, sem risco de perda de dados. Observe a Listagem 2. Listagem 2. Conversão implícita int numero = 1234; decimal numero2 = numero;
A Tabela 1 contém os tipos que permitem conversões implícitas. Tabela 1. Tabela de conversões numéricas implícitas - Fonte: Adaptado de Microsoft
De
Para
sbyte
short,int,long,float,double, ou decimal
Byte
short,ushort,int,uint,long,ulong,float,double, ou decimal
short
int,long,float,double, oudecimal
ushort
int,uint,long,ulong,float,double, ou decimal
int
long,float,double, oudecimal
uint
long,ulong,float,double, oudecimal
Long
float,double, oudecimal
char
ushort,int,uint,long,ulong,float,double, ou decimal
float
double
ulong
float,double, ou decimal 2.Conversão explícita (casts): esse tipo de conversão necessita de um operador. É realizado quando há a necessidade de se converter um valor e pode ocorrer perda de informações. Para fazer um cast, deve ser informado o tipo entre parênteses na frente da variável que será convertida, como é demonstrado na Listagem 3. Listagem 3. Exemplo de cast. double num = 123.4; int num2;
De
Para
sbyte
byte,ushort,uint,ulong, ouchar
Byte
Sbyteou char
short
sbyte,byte,ushort,uint,ulong, ouchar
ushort
sbyte,byte,short, ouchar
int
sbyte,byte,short,ushort,uint,ulong ou char
uint
sbyte,byte,short,ushort,int ouchar
Long
sbyte,byte,short,ushort,int,uint,ulong ouchar
ulong
sbyte,byte,short,ushort,int,uint,long ouchar
char
sbyte,byte oushort
float
sbyte,byte,short,ushort,int,uint,long,ulong,char oudecimal
double
sbyte,byte,short,ushort,int,uint,long,ulong,char,float ou decimal
decimal
sbyte,byte,short,ushort,int,uint,long,ulong,char,float oudouble
num2 = (int)num;
A Tabela 2 contém as possíveis conversões explicitas que podem ser realizadas. Tabela 2. Possíveis conversões explicitas - Fonte: Adaptado de Microsoft. É preciso cuidado e atenção ao utilizar conversões explicitas devidos as possíveis perdas de informações e arredondamentos. 3.Conversões definidas pelo usuário: são conversões realizadas por métodos criados pelos desenvolvedores que contém o código que converta as informações para a maneira desejada.
4.Conversões com classes auxiliares: são as conversões em que, por exemplo, o desenvolvedor utiliza a classe System.Convert para converter para o tipo desejado ou o método Parse dos tipos de dados. Neste link: http://msdn.microsoft.com/pt-br/library/system.convert.aspx, há uma tabela que contém todos os métodos e mostra as possíveis conversões utilizando a classe System.Convert. A Listagem 4 demonstra como utilizar a classe Convert. Listagem 4. Exemplo de uso da classe Convert. int num; num = Convert.ToInt32("123");
A Listagem 5 demonstra como realizar uma conversão utilizando o método Parse. Listagem 5. Exemplo de uso do método parse. int num; num = Int32.Parse("123");
No caso da Listagem 4 foi realizada uma conversão para inteiro, mas o método Parse está presente em outros tipos de dados. Também existe o método TryParse que é como o método Parse. A diferença é que ele lança uma exceção se algum erro ocorrer. Outra conversão que é bastante útil é a conversão para String. No caso do código da Listagem 4, se for necessário exibir o número que está na variável num através de um MessageBox, será necessário converte-lo para String. Isso é possível utilizando o método ToString, conforme mostra a Listagem 6. Listagem 6. Exemplo de uso do método ToString. int num; num = Int32.Parse("123");
MessageBox.Show(num.ToString());
5. Boxing : é uma conversão implícita que converte um valor de tipo de valor para um tipo de objeto, ou seja, de um Value Type para Reference Type. Na Listagem 7 temos uma demonstração de um boxing. Listagem 7. Exemplo de boxing. int num; num = 123;
object o; o = num; 6.Unboxing é quando acontece o inverso, ou seja, quando há uma conversão de um tipo de objeto para um tipo de valor, de Reference Type para Value Type. A Listagem 8 contém o código que demonstra um unboxing. Listagem 8. Exemplo de unboxing. int num; num = 123;
object o; o = num;
int num2;
num2 = (int)o;
7.Var: essa palavra reservada pode ser usada como um tipo de dados para declaração de variáveis. Quando ela é usada, o compilador irá escolher qual é o tipo de dados mais apropriado para a variável. A Listagem 9 demonstra o seu uso. Listagem 9. Exemplo de uso de var. var num = 123; var texto = "Olá"; Vale ressaltar que o var não significa variant, apenas significa que o compilador é que vai decidir o tipo apropriado. Há também algumas considerações que devem ser levadas em conta:
Var só pode ser usada em variáveis locais e devem ser inicializadas; Não podem ser inicializadas com valor nulo; Não podem ser utilizadas em campos no escopo de classe; Não pode ser usada em expressões de inicializações; Múltiplas variáveis não podem ser inicializadas em uma única instrução; Se existir um tipo chamado Var no escopo, isso será resolvido e esse nome de tipo não será tratado como parte de uma declaração de variável local implícita.
Com isso, neste artigo foi apresentado como realizar conversão de dados entre tipos. A conversão de tipos é uma operação muito útil e frequentemente utilizada no desenvolvimento de uma aplicação, mas deve-se sempre tomar cuidado para não ocorrer perda de dados e também é preciso se atentar sobre os arredondamentos que ocorrem em algumas conversões com casas decimais. Também foi explicado o conceito de boxing e unboxing e apresentado o uso a palavra reservada var.
Documentação: C#: if/else e o operador ternário Facebook Twitter (7) (0)
Nesta documentação você irá aprender a utilizar as estruturas de condição if/else e o operador ternário, os quais possibilitam a criação de diferentes fluxos de execução para atender a lógica de um programa em C#. Aprender a criar estruturas de condição é fundamental para programar a lógica de negócio de um sistema. Essas estruturas permitem a execução de diferentes linhas de código (diferentes fluxos de execução) a partir da avaliação de uma condição. Na linguagem C# temos dois recursos com esse propósito: if/elsee switch/case. Neste documento apresentamos a opção if/else e o operador ternário. Tópicos if/else else if Operador ternário Exemplo prático
if/else A estrutura condicional if/else é um recurso que indica quais instruções o sistema deve processar de acordo com uma expressão booleana. Assim, o sistema testa se uma condição é verdadeira e então executa comandos de acordo com esse resultado. Sintaxe do if/else:
if (expressão booleana) { // código 1 } else { // código 2 }
Caso a expressão booleana seja verdadeira, as instruções entre chaves presentes no código 1 serão executadas; caso contrário, serão executadas as instruções presentes no código 2. As chaves, ou delimitadores de bloco, têm a função de agrupar um conjunto de instruções. O uso desses delimitadores é opcional caso haja apenas uma linha de código. Ainda assim, seu uso é recomendado, pois essa prática facilita a leitura e manutenção do código, tornando-o mais legível. Nota: A declaração do else não é obrigatória. Para muitas situações apenas o if é suficiente para a lógica sendo implementada.
else if Complementar ao if/else temos o operador else if que traz uma nova condição a ser testada no caso de falha no teste da condição anterior. Sintaxe do else if: if (expressão booleana 1) { // código 1 } else if (expressão booleana 2) { // código 2 } else {
// código 3 }
Dessa forma, é testada a expressão booleana 1. Caso ela seja atendida, o bloco de código 1 é executado. Caso não seja, testamos a expressão 2. Sendo atendida, o bloco de código 2 é executado. Não sendo atendida, o programa executa o bloco de código 3. Saiba que podemos criar vários else if, o que nos possibilita atender a cenários com três ou mais condições a serem avaliadas. Nota: Em casos com muitos else if, considere o uso da estrutura switch/case, ou mesmo padrões de projeto.
Operador ternário O operador ternário é um recurso para tomada de decisões com objetivo similar ao do if/else mas que é codificado em apenas uma linha. Sintaxe do operador ternário: expressão booleana ? código 1 : código 2;
Caso a expressão booleana retorne verdadeiro, é executado o código declarado logo após o ponto de interrogação (?); do contrário, o sistema irá executar o código que vem após os dois pontos (:). As principais diferenças entre a estrutura condicional if/else e o operador ternário é que esse último, como já mencionado, é descrito em apenas uma linha de código, executando apenas uma instrução, e geralmente está contido em uma instrução maior.
Exemplo prático Para demonstrar a estrutura condicional if/else, considere um programa que verifica se um aluno foi aprovado, se está em recuperação ou se foi reprovado. Para ser aprovado, o aluno precisa ter média maior ou igual (>=) a 7. Para a recuperação, ele precisa ter média menor (<) que 7 e (&&) maior ou igual (>=) a 5. Por fim, para ser reprovado, precisa ter média menor (<) do que 5. Note que temos três condições a serem verificadas, o que nos leva à necessidade de uso do if, else if, else. Exemplo de uso:
double media = 8; if (media >= 7) { Console.WriteLine("Aluno aprovado!"); } else if (media < 7 && media >=5) { Console.WriteLine("Aluno em recuperação!"); } else { Console.WriteLine("Aluno reprovado!"); }
Run
Para demonstrar um exemplo de uso do operador ternário, suponha que precisamos apresentar ao aluno o resultado com base na sua média. Para isso, criamos uma variável que recebe um texto padrão e que depois será incrementado para informar se o aluno está aprovado ou reprovado. Essa concatenação será feita a partir do resultado obtido com o operador ternário, que, neste caso, também nos devolve um texto. Exemplo de uso: double media = 8; string resultado = "Olá aluno, você foi "; resultado += media >= 7 ? "aprovado." : "reprovado."; Console.WriteLine(resultado);
Run
Documentação: C#: Switch/Case Facebook Twitter (6) (0)
Nesta documentação você irá aprender a utilizar a estrutura condicional Switch/Case da linguagem C#. Aprender a criar estruturas de condição é fundamental para programar a lógica de um sistema. Essas estruturas permitem a execução de diferentes linhas de código (diferentes fluxos de execução) a partir da avaliação de uma condição. Na linguagem C# temos dois recursos com esse propósito: if/else e switch/case. Curso relacionado: O que é C#?
Como utilizar a estrutura condicional Switch/Case do C# Neste documento apresentamos como criar estruturas de condição utilizando o switch/case. Tópicos Switch/Case Default Exemplo prático
Switch/Case Switch/case é uma estrutura de condição que define o código a ser executado com base em uma comparação de valores.
Para que isso fique mais claro, vejamos a sintaxe do switch/case: switch (variável ou valor) { case valor1: // código 1 break; case valor2: // código 2 break; }
Na linha 1, em switch (variável ou valor), definimos a variável ou valor que desejamos comparar. Na linha 3, informamos que se o valor declarado neste case for igual ao contido no switch, código 1 será executado. O mesmo comportamento se aplica ao segundo case. Ademais, caso o valor contido no switch não seja atendido em uma das condições, nenhum bloco será executado. E o comando break? O comando break é utilizado para especificar a última linha de código a ser executada dentro da condição. Se não declarado, os códigos implementados dentro dos cases subsequentes serão executados. Caso deseje executar o mesmo código para valores diferentes, você pode programar o switch/case da seguinte forma: switch (variável ou valor) { case valor1: case valor2: case valor3: // código 1 break; case valor4: case valor5: case valor6: // código 2 break; }
Neste caso, declaramos dois blocos de código. O primeiro (código 1) será executado caso o valor do switch seja igual a valor1, valor2 ou valor3; e o segundo (código 2), caso o valor seja igual a valor4, valor5 ou valor6.
Default O operador default é utilizado quando precisamos definir um fluxo alternativo para as situações em que o valor contido no switch não seja atendido por nenhum dos cases especificados. switch (variável ou valor) { case valor1: // código 1 break; case valor2: // código 2 break; default: // código 3 break; }
Caso o valor do switch não seja igual a um dos valores contidos nos cases, o sistema irá executar o código implementado no bloco default; neste exemplo, o código 3. Nota: O operador default é opcional e o código nele implementado também deve ser finalizado com a instrução break.
Exemplo prático Para demonstrar um exemplo de switch/case, considere um programa que informa ao usuário a quantidade de dias de um mês a partir do nome de um mês por ele informado. Com esse intuito, a partir de uma variável do tipo string (mes) implementamos o código abaixo:
switch (mes) { case "Janeiro": case "Março": case "Maio": case "Julho": case "Agosto": case "Outubro": case "Dezembro": Console.WriteLine("Este mês tem 31 dias"); break; case "Fevereiro": Console.WriteLine("Este mês tem 28 ou 29 dias"); break; default: Console.WriteLine("Este mês tem 30 dias"); break; }
Run
Com esse código informamos ao sistema para comparar o valor da variável mes aos valores contidos nos cases. Se o valor da variável mes for igual a “Janeiro”, “Março”, “Maio”, “Julho”, “Agosto”, “Outubro” ou “Dezembro”, o sistema irá informar que o mês tem 31 dias; se o valor presente em mes for “Fevereiro”, que o mês tem 28 ou 29 dias; e, para qualquer outro valor, que o mês possui 30 dias.
Documentação: C#: Estrutura de repetição While Facebook Twitter (6) (0)
Nesta documentação você irá aprender a utilizar a estrutura de repetição WHILE da linguagem C#.
Utilizando a estrutura de repetição WHILE com C# Quando precisamos executar um bloco de código repetidas vezes devemos recorrer às estruturas de repetição. Assim, conseguimos programar o código desejado sem que para isso criemos cópias desse mesmo conjunto de instruções. Com a linguagem C# temos três opções para implementar estruturas de repetição: For, Foreach e While. Curso relacionado: Curso de C# Avançado
Neste documento apresentamos como utilizar o While e o do while. Tópicos While Do-while Break Continue Exemplo prático
While O while trata-se da estrutura de repetição mais utilizada quando programamos com C#. Com ela, enquanto a condição for verdadeira o bloco de código será executado. Primeiramente o sistema testa essa condição. Caso verdadeira, executa as linhas declaradas dentro do while; do contrário, sai do loop. Sintaxe da estrutura de repetição while: while (condição) { //bloco de código }
A sintaxe consiste em declarar a instrução while e, entre parênteses, a condição a ser testada. Em seguida, entre chaves, o bloco de código a ser executado a cada iteração. Nota: No bloco de código deve ser implementada alguma lógica que torne a condição falsa. Caso isso não seja feita criaremos um loop infinito, que deve ser evitado por “travar” a aplicação.
Do-while Esta estrutura de repetição funciona de forma semelhante ao while, porém, ela garante que o código dentro do loop seja executado pelo menos uma vez. Para isso, a condição é declarada após o bloco de código. Sintaxe da estrutura de repetição do-while: do { //bloco de código } while (condição);
A sintaxe consiste na declaração da instrução do seguida do bloco de código. Por fim, tem-se a instrução while, que traz entre parênteses o código a ser testado. Nota: Conforme demonstrado, essa estrutura deve ser finalizada com um ponto e vírgula (;). Além disso, lembre-se de inserir no bloco de código a lógica necessária para que a condição se torne falsa em alguma execução do loop.
Break Por padrão um loop só é finalizado quando a condição de parada é atendida. Porém é possível interromper a execução de um laço de repetição utilizando o comando break. Esse comando não espera nenhum parâmetro, portanto basta informar a palavra reservada break na linha em que o loop deve ser interrompido. No código abaixo o laço deveria ser executado enquanto a variável i fosse menor que 10. Porém, nas linhas 4 e 5 usamos o comando break para interromper o loop caso i seja igual a 5. Neste caso, as instruções das linhas 6 e 7 não serão executadas, então o resultado desse código será a impressão dos valores 1 a 4. 01 int i = 1; 02 while (i < 10) 03 { 04
if(i == 5)
05
break;
06
Console.WriteLine(i);
07
i++;
08 }
Run
Continue Além do break há outro comando capaz de modificar o fluxo natural de execução de loop: o continue. Esse comando faz com que a execução da iteração/repetição seja finalizada e o fluxo seja direcionado novamente para o início do loop. Dessa forma a condição de parada será novamente avaliada e o loop será executado novamente. No código abaixo o laço deveria ser executado enquanto a variável i fosse menor que 10, imprimindo todos os valores entre 1 e 10. Porém, nas linhas 4 e 5 fazemos com que o loop seja desviado caso o valor de i seja divisível por 2 (número par). Quando isso ocorrer a linha 6 não será executada e o fluxo do código será enviado novamente para a linha 2, executando o while mais uma vez. O resultado desse código será então a impressão dos valores entre 1 e 10 que não são múltiplos de 2 (números ímpares). 01 int i = 0; 02 while(i < 10){
03
i++;
04
if(i %2 == 0)
05
continue;
06
Console.WriteLine(i);
07 }
Run
Exemplo prático Para demonstrar o funcionamento do while, criamos um loop que imprimirá no console os valores de 1 a 10. Exemplo de uso: int i = 1; while (i <= 10) { Console.WriteLine(i); i++; }
Run
Analisando o código: Linha 01: Declaramos a variável i, cujo valor será testado a cada iteração; Linha 02: Declaramos o while e a condição a ser testada (i ser menor ou igual a 10); Linha 04: Informamos ao sistema para imprimir no console o valor de i; Linha 05: Modificamos o valor de i. Essa é a linha responsável por alterar o valor dessa variável e fazer com que a condição declarada no while se torne falsa, interrompendo a execução do loop.
Documentação: C#: Estrutura de repetição Foreach Facebook Twitter (6) (0)
Nesta documentação você aprenderá a utilizar a estrutura de repetição Foreach da linguagem C#. As estruturas de repetição possibilitam executar o mesmo código diversas vezes sem que seja necessário repetir instruções. Assim, sempre que você precisar programar uma lógica que necessita ser processada mais de uma vez, considere o uso desse recurso. A linguagem C# nos fornece três estruturas de repetição: For, Foreach e While. Curso relacionado: Curso Básico de C#
Neste documento apresentamos como utilizar o foreach. Tópicos Foreach Exemplo prático
Foreach O foreach é um recurso do C# que possibilita executar um conjunto de comandos para cada elemento presente em uma coleção (Array, List, Stack, Queue e outras). Portanto, diferentemente do while e do for, não precisamos definir uma condição de parada. Isso é definido de forma implícita, pelo tamanho da coleção. Sintaxe da estrutura de repetição foreach: foreach (tipo elemento in coleção)
{ //bloco de código }
Na declaração do foreach, entre parênteses criamos um elemento do tipo utilizado na coleção e, com o operador in, informamos a coleção a ser percorrida. Assim, a cada loop os dados presentes em uma posição da coleção são atribuídos ao elemento. Por fim, entre chaves, inserimos o código a ser executado no loop.
Exemplo prático Considere que desejamos imprimir na tela todos os nomes presentes em um array. Para isso, em vez de criar um while e nos preocuparmos com a condição de parada, podemos fazer uso do foreach. Exemplo de uso: string[] nomes = { “André”, “Bruna”, “Carla”, “Daniel” };
foreach (string nome in nomes) { Console.WriteLine(nome); }
Run
Analisando o código: Linha 01: Criamos um array, chamado nomes, do tipo string, onde armazenamos os nomes de algumas pessoas; Linha 03: Declaramos a estrutura foreach, que traz entre parênteses o tipo (string) e o nome (nome) do elemento que irá receber os dados contidos na coleção a cada iteração. Ainda nessa linha, após o operador in informamos que a estrutura irá iterar pelo array de strings (nomes);
Linhas 04 a 06: Declaramos o bloco de código que será executado a cada loop - neste caso, a instrução Console.WriteLine(nome).
Documentação: C#: Trabalhando com arrays Facebook Twitter (4) (0)
Os arrays representam uma das coleções mais utilizadas em qualquer linguagem de programação e no C# não é diferente. Neste documento veremos como declarar e utilizar arrays na linguagem C#. Arrays são coleções de dados extremamente importantes em qualquer linguagem de programação. Sua grande vantagem está no fato de serem estruturas indexadas, ou seja, os itens dos arrays podem ser acessados através de índices, o que garante grande velocidade para essas operações. Neste documento veremos como declarar e utilizar arrays na linguagem C#. Tópicos Declaração de arrays Inserção de dados no array Acesso aos dados do array Iterações sobre arrays Exemplo prático
Declaração de arrays
A declaração de arrays é algo relativamente simples no C#. No entanto, é importante destacar que é essencial sabermos exatamente o tamanho que o array terá, o que acaba sendo uma limitação em alguns casos. A declaração padrão para um array qualquer obedece a seguinte estrutura: tipo[] nomeDoArray = new tipo[tamanho_do_array];
Por exemplo, para um array do tipo inteiro (int), com 10 posições, a declaração seria da seguinte forma: int[] array = new int[10];
Inserção de dados no array Existem duas formas básicas para inserirmos dados nos arrays em C#. A primeira é realizarmos isso durante a declaração do mesmo. Dessa forma, poderíamos ter a declaração das seguintes formas: int[] array1 = new int[5] { 1, 3, 7, 12, 8 };
int[] array2 = { 1, 3, 2, 7, 6 };
No primeiro caso temos uma declaração explícita de um array que conterá cinco números inteiros. Aqui, se passarmos menos ou mais de cinco números entre as chaves haverá um erro de compilação, uma vez que sabemos que o array tem cinco posições. No segundo caso, a declaração da quantidade de elementos no array é implícita e depende de quantos itens temos entre as chaves. Nesse exemplo passamos cinco números; assim, array2 possui cinco posições e não há possibilidade de erro de compilação. Outra forma de inserirmos os dados no array é acessando o item através de seu índice. Por exemplo, podemos fazer uma declaração simples e então iterarmos sobre todos os índices do array, adicionando os valores específicos para cada um deles, como a seguir: 01 int[] array = new int[50];
02 for (int i = 0; i < 50; i++)
03 {
04
array[i] = i + 1;
05 } Linha 01: declaração de um array inteiro com 50 posições; Linha 02: definição de um loop for com 50 iterações, entre 0 e 49, com incremento unitário; Linha 04: inserção de dados no array. Para cada iteração, o array, na posição i, receberá o valor de i + 1, ou seja, array[0] == 1, array[1] == 2, e assim por diante.
Acesso aos dados do array O acesso aos dados do array é feito de forma extremamente simples, uma vez que estamos lidando com uma coleção indexada. Assim, basta acessarmos o índice específico e podemos obter o valor do elemento, como podemos ver abaixo: Console.WriteLine(array[10]);
É importante notarmos, entretanto, que esse tipo de acesso pode gerar problemas. Se, no caso anterior, tivermos um array com menos que 11 posições (o índice 10 é o 11º elemento da coleção (0 a 10)), o código irá levantar uma exceção do tipo IndexOutOfRangeException. Portanto, é fundamental assegurar que estamos acessando um índice válido.
Iterações sobre os arrays Uma das principais características das coleções, em qualquer linguagem de programação, são as iterações que podem ser realizadas sobre elas. No caso dos arrays em C# isso não é diferente. Assim, para iterarmos sobre os arrays existem basicamente duas opções: loops em que acessamos os índices no array diretamente (while ou for) ou o loop foreach, que faz isso internamente para nós. No código abaixo temos ambas as opções: 01 for (int i = 0; i < 10; i++) 02 { 03
Console.WriteLine(array[i]);
04 } 05 06 int i = 0;
07 while (i < 10) 08 { 09
Console.WriteLine(array[i]);
10
i++;
11 } 12 13 foreach (int x in array) 14 { 15
Console.WriteLine(x);
16 }
Linhas 01 a 04: acesso via índices do array utilizando um loop for, no qual o iterador é o inteiro “i”. Esse tipo de acesso era bem comum em linguagens como C e C++; Linhas 06 a 11: acesso via índices do array utilizando um loop while, no qual o iterador é o inteiro “i”. Esse tipo de acesso é muito similar ao que vimos no loop for. Entretanto, existem algumas operações que precisam ser realizadas manualmente, como a declaração da variável inteira “i” (linha 06) e o incremento do mesmo (linha 10); Linhas 13 a 16: acesso através do loop foreach, que itera sobre todos os elementos da coleção. Esse tipo de utilização só é possível porque no C# todas as coleções implementam a interface IEnumerable, ou sua forma genérica IEnumerable
, que oferecem a base do suporte às iterações via foreach.
Exemplo prático A seguir temos um exemplo prático (mostrado em execução na Figura 1) de como utilizar os arrays em nossas aplicações C#: 01 string[] pilotos = new string[4] { "Ayrton Senna", "Michael Schumacher", "Lewis Hamilton", "Alain Prost" }; 02 Console.WriteLine(pilotos[3]); 03 Console.WriteLine(); 04 pilotos[3] = "Rubens Barrichello"; 05 foreach (string piloto in pilotos) 06 { 07 08 }
RUN
Console.WriteLine(piloto);
Linha 01: declaração do array “pilotos”, contendo quatro strings pré-definidas; Linha 02: acesso ao índice “3” do array, mostrando na tela o nome “Alain Prost”; Linha 03: inserção de linha em branco; Linha 04: substituição do elemento no índice “3” do array (“Alain Prost”), que agora recebe o nome de “Rubens Barrichello”; Linhas 05 a 08: utilização do loop foreach para iterar sobre o array. Nesse caso, como o array implementa IEnumerable, podemos garantir que os quarto pilotos serão mostrados na tela com o código da linha 06.
Figura 1. Execução do exemplo C# Arrays
Receba nossas novidades Receber Newsletter! por Henrique Machado (4) (0)
Ficou com alguma dúvida?
Elio Junior
Boa noite Gostaria de saber se por exemplo uma array guardaria um objeto há 3 meses
Joel Rodrigues há 3 meses DEVMEDIA
Olá, Elio. Tudo bem? Você pode criar arrays de qualquer tipo. Veja abaixo algumas possibilidade de criação de arrays de objetos (aqui usei uma classe Aluno que tem apenas Nome):
//Instanciando um array vazio
Aluno[] alunos1 = new Aluno[5];
//Instanciando um array com tamanho e itens predefinidos
Aluno[] alunos2 = new Aluno[5]
{
new Aluno { Nome = "João" },
new Aluno { Nome = "Maria" },
new Aluno { Nome = "Pedro" },
new Aluno { Nome = "Carlos" },
new Aluno { Nome = "Tereza" }
};
//Instanciando um array com itens predefinidos
Aluno[] alunos3 =
{
new Aluno { Nome = "Carla" },
new Aluno { Nome = "Antonio" },
new Aluno { Nome = "Marcia" }
};
Documentação: Nullable Types em C# Facebook Twitter (6) (0)
Nesta documentação você aprenderá o que é e como utilizar Nullable Types, recurso da linguagem C# para manipulação de valores nulos em tipos não anuláveis. Nullable Types é um recurso do C# que nos permite atribuir o valor null a um tipo de dado que, por padrão, não aceita valores nulos: os tipos primitivos.
Esse recurso é útil quando precisamos realizar operações em banco de dados e desejamos armazenar um valor nulo em um campo que, posteriormente, receberá um valor de tipo primitivo, como int, float, bool, etc. Curso relacionado: Curso de Introdução ao C#
Tópicos
Utilizando nullable types Exemplo prático
Utilizando nullable types Para fazer a conversão de um tipo em um Nullable Type, basta adicionarmos um ponto de interrogação (?) logo após a declaração do tipo. int? x = null;
Com esse código, atribuímos o valor null à variável x, de tipo int. Para verificarmos se a variável x recebeu algum valor, podemos utilizar uma estrutura condicional que testa se o seu valor é diferente de null, conforme o código abaixo: if (x != null) { // x possui um valor inteiro } else { // x possui um valor nulo }
Run
Ou podemos simplificar esse código com o uso de um operador ternário: Console.WriteLine("O valor de x é " + ((x != null) ? "um inteiro" : "nulo"));
Exemplo prático Para demonstrar esse recurso na prática, criamos uma tabela Clientes com as colunas IdCliente, Nome e NumeroConta. Neste exemplo, suponha que a coluna NumeroConta permite a atribuição de valores nulos para que eles sejam preenchidos posteriormente. No código abaixo, criamos uma variável do tipo string e uma do tipo int?, que recebe null e, logo após, solicitamos a inserção desses dados no banco: string nome = "João"; int? numeroConta = null; ClienteRepositorio repositorio = new ClienteRepositorio(); repositorio.Inserir(nome, numeroConta);
Após a execução, teríamos uma tabela conforme a Figura 1, com a coluna NumeroConta com o valor null:
Figura 1. Coluna NumeroContaNota: Para gravação no banco de dados é necessário que o Nullable Type seja tratado na classe de persistência. Caso ele tenha valor nulo, será convertido para o tipo nulo do banco de dados em uso.
Manipulação de Strings em C# Facebook Twitter (2) (0)
A manipulação de textos é uma tarefa comum em uma aplicação. Ao desenvolver um software, em determinado momento pode ser necessário conhecer mais a fundo como manipular strings.
Atenção: esse artigo tem um vídeo complementar. Clique e assista! De que se trata o artigo
Este artigo apresenta as principais rotinas para manipulação de textos do tipo String utilizando o C#, tarefas do dia a dia como criar, formatar, substituir, copiar, verificar tamanho e conteúdo nulo, remover caracteres etc. Para que serve Conhecer como manipular strings é essencial para que o desenvolvedor possa criar aplicações em uma determinada linguagem, com C# não é diferente, desde uma simples apresentação de texto na tela a até inclusão ou remoção de valores dentro da string, essas tarefas fazem parte do dia a dia do desenvolvedor. Em que situação o tema é útil Para manipular strings evitando recriar métodos que já existam, o desenvolvedor precisa conhecer o que a plataforma de desenvolvimento pode oferecer, pois assim saberá como lidar com a manipulação de textos da melhor forma e aproveitando os recursos existentes. Strings O desenvolvedor, durante a criação de aplicativos em qualquer linguagem de programação, lida com algumas tarefas consideradas básicas e essenciais, uma delas é a manipulação de textos. A linguagem C# juntamente com o framework .net possui diversos recursos que visam prover suporte a essas tarefas, veremos no artigo como utilizar alguns desses recursos. A plataforma .net da Microsoft atualmente é uma das mais poderosas tecnologias para o desenvolvimento de aplicações, podendo criar aplicativos para diversas finalidades. O framework é maduro e com tecnologia capaz de proporcionar ao desenvolvedor a possibilidade de aplicar as melhores práticas de desenvolvimento do mercado. Desde o início do Framework .net e do Visual C#, diversos recursos dessas tecnologias foram disponibilizadas para a manipulação de strings, a cada nova versão, novos recursos são adicionados e melhorias são implementadas visando uma melhor performance e a inclusão de suporte a tecnologias mais recentes. Tudo isso faz da plataforma .net uma das principais tecnologias na área de desenvolvimento de software. Juntamente com o Framework .net e do Visual C#, diversas ferramentas de apoio ou tecnologias compatíveis foram se integrando a plataforma. Um destaque em relação aos recursos e produtividade é o Visual Studio, o IDE da Microsoft para a criação de softwares é parte fundamental do sucesso da plataforma, ele cresce rapidamente acompanhando o que existe de mais novo no mundo de desenvolvimento de aplicativos. Atualmente a versão do Framework .net é a 4.0, o Visual Studio está na versão 2010 e possui diversas divisões, tendo uma versão gratuita chamada Express que é ideal para o
desenvolvedor iniciante na plataforma, esta versão é compatível com os exemplos deste artigo. A manipulação de textos é uma tarefa comum em uma aplicação. Ao desenvolver um software, em determinado momento pode ser necessário conhecer mais a fundo como manipular strings. Por mais básica que a tarefa possa parecer, ela tem muitos detalhes e o desenvolvedor que está começando com a sua jornada de aprendizado em uma linguagem, precisa conhecer como realizar a manipulação de textos em uma aplicação. Em C# uma string é uma coleção de caracteres, derivada da classe System.String. Quando utilizamos um objeto do tipo String, nós estamos utilizando um objeto imutável, ou seja, ele não sofre alterações. Por exemplo, vamos supor que tenhamos uma variável chamada nome, do tipo string e que contém o valor "alexandre". Se utilizarmos o método ToUpper() da string, com o objetivo de transformar o conteúdo da variável em maiúsculo, o objeto string não é alterado, na realidade, é criado um novo e passado como retorno. O objeto original fica imutável, sendo assim, nenhuma operação com este objeto poderá modificá-lo. Outra classe da plataforma .net oferece a possibilidade de mudar o objeto, a classe StringBuilder. As operações que incidem sobre esse objeto modificam o elemento originalmente criado, como ele não cria um novo retorno, esse tipo de classe é mais utilizada em operações onde uma string será incrementada em etapas, como por exemplo, a criação de uma string dentro de um loop. Conforme o sistema percorre o loop, a string é criada através do StringBuilder, consumindo menos memória e aumentando a performance. "
Coleções O .NET Framework é um conjunto de ferramentas e classes que suportam o desenvolvimento de aplicações com a linguagem C# (além de outras linguagens). Entre as várias classes do framework que são de uso frequente em nosso dia a dia estão as coleções, que implementam diferentes estruturas de dados, tais como listas, filas, pilhas, tabelas hash. Nos artigos a seguir você poderá conhecer algumas das principais classes de coleções que você pode usar em suas aplicações com C#:
SortedList Dominando as classes do .NET Facebook Twitter (3) (0)
Veja neste artigo como e por que utilizar a classe SortedList do namespace System.Collections, seus principais métodos e propriedades.
Sobre a classe A classe SortedList funciona, em parte, de forma semelhante ao Hashtable, onde os elementos são compostos por pares chave/valor. Porém, a principal diferença é que, como o nome sugere, o SortedList é uma lista ordenada. Os elementos são automaticamente ordenados pelas chaves no momento em que são inseridos na coleção. Vale observar que, como no Hashtable, as chaves devem ser valores únicos. Quando as chaves são de tipos básicos como int ou string, a ordenação é feita sem que precisemos intervir. Porém, caso se deseje usar chaves de tipos complexos como classes customizadas, é necessário fornecer, no construtor do SortedList, um objeto IComparer específico.
Principais propriedades
Capacity: Um valor inteiro que define a capacidade da lista, ou seja, o número máximo de itens que podem ser inseridos na coleção. Count: Também é um valor do tipo int que retorna (não pode ser alterada diretamente) a quantidade de itens atualmente constantes na lista.
IsFixedSize: É um valor booleano que, quando verdadeiro, indica que a lista não pode ter itens adicionados ou removidos, apenas é permitido alterar os itens existentes. IsReadOnly: Também booleano, esta propriedade indica se a coleção pode ser alterada. Keys: Apenas para leitura, retorna uma coleção com as chaves contidas na lista. Values: Assim como a anterior, é uma lista do tipo ICollection que contém os valores da lista (lembrando que a coleção é formada por pares chave/valor)
Principais métodos
Add: Recebe dois parâmetros do tipo object que devem ser inseridos na coleção, formando um par chave/valor. Clear: Como se pode imaginar, esse método limpa a lista, removendo todos os itens existentes. Contains: Recebe como parâmetro um objeto a ser localizado na lista de chaves e retorna true caso a chave exista. Caso contrário, o retorno é false. ContainsKey: Funciona exatamente como o método anterior, retornando um valor booleano indicando se uma chave (passada como parâmetro) existe na lista. ContainsValue: Semelhante aos dois métodos anteriores, com a diferença de que o objeto passado como parâmetro é procurado entre a lista de valores e não de chaves. CopyTo: Copia os valores da coleção para um vetor unidimensional (primeiro parâmetro), iniciando a partir de um certo índice (segundo parâmetro). GetByIndex: Recebe um parâmetro do tipo inteiro e retorna um objeto representando o valor contido na posição indicada. GetKey: Retorna a chave contida na posição passada como parâmetro (valor inteiro). GetKeyList: Este método retorna uma coleção do tipo IList contendo todas as chaves da lista. GetValueList: Também retorna uma coleção de objetos do tipo IList, mas contendo os valores e não as chaves, como o anterior. IndexOfKey: Através desse método, é possível obter o índice do item na lista a partir de sua chave, a qual é passada como parâmetro. IndexOfValue: Semelhante ao anterior, o retorno desse método é o índice na lista do item cujo valor é passado como parâmetro. Remove: Recebe como argumento uma chave e remove da lista o item correspondente. RemoveAt: Remove da lista o item cujo índice é informado como argumento do método. SetByIndex: Este método recebe dois argumentos: um índice (do tipo inteiro) e um objeto (object). O valor do item na posição indicada passa a ser o objeto passado como segundo argumento.
Exemplo prático
A seguir é exibido um exemplo de uso da classe SortedList. São inseridos alguns itens de forma desordenada e, em seguida, toda a lista é exibida na tela. Listagem 1: Exemplo de uso da classe SortedList static void Main(string[] args)
{
SortedList lista = new SortedList();
lista.Add(4, "Quarto");
lista.Add(1, "Primeiro");
lista.Add(2, "Segundo");
lista.Add(3, "Terceiro");
foreach (object chave in lista.Keys)
{
Console.WriteLine("{0} - {1}", chave.ToString(), lista[chave].ToString());
}
Console.Read();
}
O resultado é exibido a Figura 1.
Figura 1: Resultado do código da Listagem 1 Como se vê, os itens são automaticamente ordenados segundo sua chave.
Conclusão A classe SortedList, assim como as demais do namespace System.Collections facilita o trabalho com coleções de objetos, tornando prática a adição, exclusão, localização e, neste caso, a ordenação.
Queue e Stack – Trabalhando com os modelos FIFO e LIFO em .NET Facebook Twitter (4) (0)
Veja neste artigo como trabalhar com os modelos FIFO (First-In, First-Out) e LIFO (Last-In, First-Out) no .NET Framework, utilizando as classes Queue e Stack contidas no namespace System.Collections.
Introdução Na computação, duas das estruturas de dados mais conhecidas e comumente utilizadas são a FILA e a PILHA. Muitas vezes, é necessário desenvolver classes específicas para se trabalhar com essas estruturas, mas no .NET Framework já estão presentes nativamente as classes Queue e Stack cujo funcionamento é baseado no conceito de fila e pilha, respectivamente. Neste artigo, conheceremos de forma detalhada essas duas classes. Serão apresentados seus principais métodos e propriedades, bem como exemplos práticos de uso. Porém, antes vamos entender melhor o funcionamento dessas estruturas.
Fila (First-in, First-out) A fila implementa o conceito “First-in, First-out”, ou “Primeiro a entrar, Primeiro a sair”. Ou seja, o primeiro elemento inserido na lista é também o primeiro a ser removido. Podemos pensar, por exemplo, em uma fila de banco. A primeira pessoa a entrar na fila será a primeira a ser atendida, logo, a primeira a sair da fila. Também é muito comum se usar a expressão FIFO (abreviação de first-in, first-out) para definir a pilha.
Pilha (Last-in, First-out) O conceito implementado pela pilha é o de “Last-in, First-out” (também chamado LIFO ou FILO, de first-in, last-out, que tem o mesmo sentido prático) ou “Último a entrar, Primeiro a sair”. Imagine, por exemplo, que você esteja organizando vários pratos. Inicialmente você organiza todos uns sobre os outros, formando uma pilha de pratos. Logo após, você irá retirar um a um para organizar no seu devido local. Perceba que o primeiro prato removido foi o último a ser inserido na pilha, aquele que ficou na parte superior. Enquanto isso, o primeiro prato inserido na pilha, aquele que está abaixo de todos, será o último removido.
Agora que os conceitos teóricos já foram apresentados, vejamos na prática como aplicálos no .NET Framework. Observação 1: aqui serão apresentadas as propriedades e métodos relevantes ao contexto do artigo. Métodos como ToString e GetHashCode, por exemplo, não serão abordados por serem herdados e não estarem diretamente relacionados com as estruturas citadas.
Queue A classe Queue encontra-se no namespace System.Collections e implementa o conceito de FIFO. A única propriedade relevante dessa classe nesse contexto é o Count que retorna a quantidade de elementos atualmente existente na lista. Por outro lado, mais de um método merece destaque, principalmente o Enqueue e o Dequeue, como veremos abaixo.
Enqueue: recebe como parâmetro um objeto a ser inserido na lista, nesse caso, no final da fila. Dequeue: este método não recebe parâmetros, mas retorna o primeiro objeto da fila, aquele que, pela ordem, é o próximo a ser removido. Após a chamada desse método, o objeto retornado é também removido da fila. Peek: semelhante ao Dequeue, retorna o primeiro objeto da lista, mas não o remove. Pode ser usado quando se deseja apenas conhecer o valor do primeiro elemento. TrimToSize: altera a capacidade da lista para a quantidade atual de elementos. Com isso, memória é poupada, pois a classe Queue reserva memória para armazenar uma quantidade de elementos, se nem todos forem usados, parte da memória reservada fica sem uso. Usando o TrimToSize essa memória é liberada. Construtor: a classe Queue possui três sobrecargas do construtor original, que não recebe nenhum parâmetro. A primeira delas recebe um valor inteiro que define capacidade inicial da lista. A segunda recebe uma coleção (ICollection) da qual os itens são copiados para a lista. A terceira recebe um valor inteiro para a capacidade inicial e um fator de expansão do tipo float. Esse fator de expansão é utilizado para aumentar a capacidade da fila quando for necessário. Originalmente esse valor é pequeno para que não seja utilizada memória desnecessariamente.
Caso se conheça previamente o número de elementos a ser inserido da lista, é interessante definir a capacidade inicial (no constructor), evitando que memória extra seja reservada. Caso a quantidade de itens ultrapasse a capacidade inicialmente definida, esta é automaticamente expandida.
Stack
Também contida no namespace Sytem.Collections, a classe Stack implementa o conceito de LIFO, onde o último elemento inserido é o primeiro a ser removido. A propriedade Count, também nesse caso, é a única com relevância nesse contexto e também retorna a quantidade de elementos contidos na lista. Os métodos que merecem destaque são:
Push: insere um objeto (recebido como parâmetro) no fim da lista. Pop: retorna e remove o elemento do topo da pilha, ou seja, o último que foi inserido. Peek: retorna o elemento do topo da pilha, porém sem que este seja removido. Construtor: além do construtor original, sem parâmetros, existem duas sobrecargas. A primeira recebe uma coleção do tipo ICollection da qual os itens são copiados para a pilha. A segunda recebe um valor inteiro que define a capacidade inicial da lista. Sobre a capacidade inicial, valem os mesmos comentários feitos a respeito da outra classe.
Métodos em comum As duas classes possuem métodos em comum que vale a pena citar aqui:
Clear: remove todos os itens da lista, não sendo possível recuperá-los. Contains: recebe como parâmetro um objeto a ser localizado na lista. Caso esse objeto esteja contido na fila, o retorno desse método é true, caso contrário, o retorno é false.
Exemplos práticos Para os exemplos a seguir, foi utilizada uma aplicação console. No exemplo a seguir, são inseridos três elementos em uma fila e, em seguida, todos são removidos e exibidos na ordem. Listagem 1: Exemplo de uso da classe Queue em C# Queue q = new Queue();
//Inserindo três elementos
q.Enqueue(1);
q.Enqueue(2);
q.Enqueue(3);
Console.WriteLine("Listando elementos da fila:");
//Enquanto houver elementos na lista, exibir e remover o primeiro
while (q.Count > 0)
{
Console.WriteLine(q.Dequeue());
}
//Exibe a quantidade de elementos restantes, ou seja, zero
Console.WriteLine("A lista agora possui " + q.Count.ToString() + " elementos.");
Console.Read();
Listagem 2: Exemplo de uso da classe Queue em VB.NET Dim q As System.Collections.Queue = New System.Collections.Queue()
'Inserindo três elementos
q.Enqueue(1)
q.Enqueue(2)
q.Enqueue(3)
Console.WriteLine("Listando elementos da lista:")
'Enquanto houver elementos na lista, exibir e remover o primeiro
While q.Count > 0
Console.WriteLine(q.Dequeue())
End While
'Exibe a quantidade de elementos restantes, ou seja, zero
Console.WriteLine("A lista possui agora " + q.Count.ToString() + " elementos.")
Console.Read()
O resultado desse código é mostrado na Figura 1.
Figura 1: Resultado do exemplo com a classe Queue
Note que os itens foram removidos na mesma ordem em que foram inseridos. O elemento 1 foi o primeiro a ser adicionado e também o primeiro a ser retirado da fila. Ao fim, não resta nenhum item. A seguir temos um exemplo semelhante, mas utilizando a classe Stack. Nesse caso, os itens devem ser exibidos na ordem inversa da adição. Listagem 3: Exemplo de uso da classe Stack em C# Stack s = new Stack();
//Inserindo três elementos
s.Push(1);
s.Push(2);
s.Push(3);
Console.WriteLine("Listando elementos da lista:");
//Enquanto houver elementos na lista, exibir e remover o primeiro
while (s.Count > 0)
{
Console.WriteLine(s.Pop());
}
//Exibe a quantidade de elementos restantes, ou seja, zero
Console.WriteLine("A lista agora possui " + s.Count.ToString() + " elementos.");
Console.Read();
Listagem 4: Exemplo de uso da classe Stack em VB.NET Dim s As System.Collections.Stack = New System.Collections.Stack()
'Inserindo três elementos
s.Push(1)
s.Push(2)
s.Push(3)
Console.WriteLine("Listando elementos da lista:")
'Enquanto houver elementos na lista, exibir e remover o primeiro
While s.Count > 0
Console.WriteLine(s.Pop())
End While
'Exibe a quantidade de elementos restantes, ou seja, zero
Console.WriteLine("A lista possui agora " + s.Count.ToString() + " elementos.")
Console.Read()
O resultado é exibido na Figura 2.
Figura 2: Resultado do exemplo com a classe Stack Como era esperado, os itens foram exibidos na ordem contrária a da inserção. O elemento 3 foi o último a ser inserido, mas foi o primeiro listado. Novamente, ao final do código não restam itens da pilha.
Conclusão Na informática, são muitas as aplicações dos modelos FIFO e LIFO e, como vimos, o .NET Framework facilita bastante o trabalho com estruturas desse tipo, oferecendo duas classes eficientes e de fácil manipulação. Sugiro que o leitor busque conhecer os demais métodos e propriedades dessas classes que, em geral, são comuns às classes que representam coleções nesse namespace.
ArrayList, Hashtable e BitArray em C# Facebook Twitter (4) (0)
Veja nesse artigo uma descrição sobre as classes ArrayList, Hashtable e BitArray na linguagem C#.
ArrayList A classe ArrayList é uma solução alternativa ao uso de arrays em .NET, tendo como principal diferencial a possibilidade de se expandir quando se adiciona novos elementos, não sendo necessário definir sua capacidade suportada na inicialização. De forma semelhante, quando um elemento é removido, o tamanho da lista é reduzido, poupando memória. Esta classe funciona como uma lista de objetos (System.Object), ou seja, pode-se adicionar elementos de qualquer tipo a um ArrayList e, mesmo assim, é possível localizar itens, ordenar a lista, identificar quais elementos são de determinado tipo, entre outras funcionalidades. Vejamos os principais métodos da classe:
Add(object): adiciona um elemento no final da lista. AddRange(ICollection): adiciona à lista os elementos de uma coleção pasada como parâmetro. AsParallel(): retorna uma ParallelQuery que utiliza vários processadores, caso existam, para efetuar consulta aos elementos com Linq (inclusive com lambda expressions). AsQueryable(): retorna um objeto IQueryable, no qual é possível efetuar consultas com Linq, inclusive usando expressões lambda. BinarySearch(object): busca pelo objeto passado como parâmetro na lista, retornando seu índice, caso exista. BinarySearch(object, IComparer): busca pelo objeto parâmetro na lista, considerando a comparação feita pelo IComparer. No caso de classes próprias, é preciso definir um IComparer adequado. Cast(): converte os elementos da lista para o Tipo definido, retornando uma coleção com os novos itens.
Clear(): remove todos os elementos da lista. Clone(): retorna uma cópia do ArrayList. Contains(object): retorna true se o objeto passado como parâmetro existe na lista, caso contrário, retorna false. CopyTo(int, Array, int, int): copia uma certa quantidade(quarto parâmetro) de elementos da lista, a partir de um índice (primeiro parâmetro), para um Array(segundo parâmetro) a partir de um outro índice nesse array(terceiro parâmetro). GetEnumerator(): retorna uma coleção IEnumerator com os itens da lista, porém, no IEnumerator, os elementos não podem ser alterados, apenas lidos. GetRange(int, int): retorna um ArrayList contendo uma certa quantidade (segundo parâmetro) de elementos a partir do índice especificado (primeiro parâmetro). IndexOf(object): esta função retorna o índice (zero-based) do objeto procurado na lista, caso este exista. Insert(int, object): o método Insert adiciona um elemento na lista em uma determinada posição. Os itens após este ponto são deslocados uma posição adiante. InsertRange(int, ICollection): semelhante ao Insert, porém insere uma coleção de itens ao invés de um único elemento. LastIndexOf(object): retorna o índice da última ocorrência do objeto procurado na lista, caso o objeto não seja encontrado, o resultado é -1 (assim como na maioria dos métodos de busca). OfType(): retorna uma coleção com os itens da lista que são do tipo especificado. Essa lista resultante, porém, é apenas para visualização, seus itens não podem ser alterados. Remove(object): caso o objeto passado como parâmetro seja localizado, ele é removido da lista. RemoveAt(int): remove da lista o elemento da posição indicada no parâmetro. RemoveRange(int, int): remove uma quantidade de elementos (segundo parâmetro) a partir do índice indicado (primeiro parâmetro). Reverse(): inverte a ordem dos elementos. SetRange(int, ICollection): insere os itens de uma coleção passada como parâmetro para o ArrayList a partir de uma determinada posição. Os elementos existentes inicialmente nessa faixa de valores são substituídos pelo que foram adicionados. Sort(): ordena os elementos em ordem crescente. Caso os elementos sejam de uma classe específica cuja comparação não é explícita, é necessário usar uma sobrecarga desse método que recebe um objeto IComparer. ToArray(): retorna um Array com os objetos contidos na lista.
É fácil perceber que se torna muito mais prático trabalhar com ArrayList em comparação com arrays, principalmente pelo fato de não precisar se preocupar com o tamanho da lista e o posicionamento dos elementos.
Hashtable A classe Hashtable localizada no namespace System.Collections, como o próprio namespace sugere, é uma coleção de objetos onde cada elemento é composto por uma chave e um valor. Assim como a classe ArrayList, o Hashtable é flexível, ou seja, podese adicionar objetos de qualquer tipo (pois é uma coleção de object) e seu tamanho se adapta à quantidade de itens que contém, expandindo e contraindo suas dimensões. Os elementos podem ser acessados pela respectiva chave e essa chave deve ser única para cada item, facilitando a localização de um objeto específico. Em comparação com o acesso aos elementos de um array, o hashtable adapta-se mais facilmente a diversas situações, pois a chave não necessariamente é um número, pelo contrário, trata-se de um object. Por exemplo, podemos usar um hashtable para representar um determinado objeto, quando não é preciso criar um struct ou uma classe para isso. Vejamos a Listagem 1 onde usamos uma coleção desse tipo para representar as configurações de um sistema, armazenando informações de vários tipos diferentes. Como foi dito, as chaves são valores únicos. Ao tentar inserir uma chave duplicada, é gerada uma exceção do tipo ArgumentException, informando que a chave que se está tentando inserir já existe. Listagem 1: Exemplo de utilização do Hashtable Hashtable config = new Hashtable();
//Valor do tipo Int32
config[“VERSAO”] = 1;
//Valor do tipo String
config[“DIRETORIO”] = “C:\\Sistema”;
//Valor do tipo DateTime
config[“VALIDADE”] = DateTime.Today.AddMonths(1);
Principais propriedades
Count: Quantidade de elementos contidos na lista. Keys: Coleção de objetos usados como chaves na lista.
Values: Coleção de objetos contidos na lista (os valores e não as chaves).
Principais métodos
Add(object key, object value): Adiciona um objeto à lista, juntamente com uma chave que o identifica. AsQueryable(): Retorna uma coleção IQueryable, na qual se pode executar consultas com LINQ (inclusive com Lambda Expressions). Cast(): Converte os elementos da coleção para o tipo especificado, os quais são retornados em uma coleção IEnumerable. Clear(): Exclui todos os elementos da lista. Clonte(): Cria uma cópia do objeto (da lista). Contains(object key) e ContainsKey(object key): funcionam igualmente, retornando true se a chave especificada nos parâmetros existe na lista, ou false em caso contário. ContainsValue(object value): Retorna true se o valor informado está contido na lista (nos valores e não nas chaves). Remove(object key): Exclui da lista o valor relativo à chave informada nos parâmetros.
BitArray A classe BitArray, como o nome sugere, é uma coleção de bits, que porém são tratados como bool. Ou seja, os elementos possuem valor true se o respectivo bit vale 1, ou false se o bit vale 0. De modo geral, o BitArray funciona como um vetor de bool (bool[]), a principal diferença é a possibilidade de inverter todos os valores de uma única vez e comparar duas listas (comparando cada elemento e retornando um BitArray com os resultados).
Principais propriedades
Count: Quantidade de elementos contidos na coleção (apenas leitura). Length: Semelhante ao count, com a diferença de que pode ser alterado.
Principais métodos
And(BitArray value): Compara dois objetos BitArray, item a item, e retorna um terceiro BitArray com o resultado da operação AND entre os itens dos dois primeiros.
Get(int index): Retorna true ou false, de acordo com o valor do elemento na posição indicada. Not(): Inverte o valor de todos os elementos, ou seja, os que forem true passam a ser false e vice-versa. Or(BitArray value): Semelhante ao método And, porém a operação realizada entre os itens é a operação booleana OR. Set(int index, bool value): Define o valor do elemento na posição index conforme o parâmetro informado. SetAll(bool value): Define o valor de todos os elementos da lista como true ou false de acordo com o parâmetro value. Xor(BitArray value): Funciona da mesma forma que as funções And e Or, porém, a operação booleana é a XOR (OU Exclusivo).
As demais operações como Clone, AsQueryable e CopyTo funcionam da mesma forma que nas demais coleções.
Conclusão Ficamos por aqui com esse artigo sobre as classes ArrayList, Hashtable e BitArray na linguagem C#, caso tenham alguma dúvida podem ficar a vontade em me perguntar nos comentários. Com o conteúdo desse artigo eu acredito que seja possível qualquer pessoa começar a entender melhor o funcionamento de classes em C#.
Essas e outras classes de coleções implementam várias interfaces que padronizam seu funcionamento e que você mesmo pode implementar em suas classes próprias. Essas interfaces encontram-se no namespace System.Collections, que você pode conhecer em maiores detalhes no artigo abaixo:
System.Collection: Conheça as interfaces Facebook Twitter (3) (0)
Temos neste artigo as principais interfaces, genéricas ou não, e estruturas de dados presentes no namespace System.Collections do .NET Framework, que permitem a implementação de diversos tipos de coleções de dados.
Fique por dentro A utilização de interfaces é uma das principais alternativas de herança do C# e outras linguagens orientadas a objetos.
Ao longo desse artigo traremos as principais interfaces do namespace System.Collections, bem como a relação delas com algumas estruturas de dados importantes no desenvolvimento de software. Mostraremos a importância da interface ICollection, bem como o funcionamento básico de Generics, além da comparação entre alguns dos elementos relacionados mais importantes que enxergamos no desenvolvimento C#. O conceito de herança é um dos mais importantes da orientação a objetos. A ideia é que as classes filhas sejam capazes de reaproveitar o código das classes pai. No caso do C#, por exemplo, quando pensamos em herança, pensamos na classe Object, da qual todas as demais herdam, direta ou indiretamente. Mas na maioria dos casos a herança é um recurso para os desenvolvedores utilizarem e criarem um código mais conciso, de fácil reutilização e manutenção. Algumas linguagens orientadas a objetos utilizam um conceito conhecido como herança múltipla. Esse conceito aparece, por exemplo, em C++, e consiste em permitir que uma única classe herde de várias (tenha vários “pais”). Isso pode gerar problemas, especialmente em classes da terceira geração, como mostra a Figura 1. Esse tipo de comportamento impede que a classe de hierarquia mais baixa (ClasseFilha3) saiba de onde estão vindo cada uma das propriedades que utiliza, caso tenham o mesmo nome. Isso gera uma dose extra de cuidado para que a aplicação possa funcionar corretamente.
Figura 1. Exemplo herança múltipla O problema da herança múltipla foi resolvido em C# e outras linguagens mais modernas como Java com o acréscimo das interfaces. Esses são elementos abstratos que precisam ser implementados por alguma classe para funcionarem. Eles podem trazer assinaturas de métodos e propriedades que serão compartilhadas por todas as classes que os implementam. Em outras palavras, eles formam uma base de representação para classes com características semelhantes. Ao longo desse artigo, veremos como funcionam algumas das interfaces padrão do C# .NET, porque elas existem e o que podem fazer por nós, além da relação que as interfaces têm com algumas estruturas de dados importantes para a aplicação. Além disso, veremos como é importante a utilização desse tipo de artifício para a criação de um sistema de qualidade.
Conhecendo as principais Interfaces do C# O C# possui uma série de namespaces que podem ser utilizados diretamente. Um deles é o System.Collections, que traz uma série de interfaces, classes e estruturas de dados de uso comum no desenvolvimento de aplicações.
Essas interfaces são muito úteis, formando uma base de representação para futuras classes que as implementarão. Veremos as principais delas ao longo do artigo em detalhes. Vamos começar apresentando as interfaces. A principal delas é a ICollection, que define alguns atributos essenciais das coleções não-genéricas (as entenderemos a seguir). Essa interface fornece a base necessária às demais, que formam representações de dados, trazendo diversas opções para os desenvolvedores. São as características que as definem, e precisamos entender qual delas é mais interessante para nós de acordo com o aplicativo que estamos desenvolvendo. As opções são muitas, como mostra a Tabela 1, que não traz todas as interfaces presentes no namespace System.Colletions, apenas as mais importantes, que entenderemos ao longo desse artigo.
Interface
Descrição
ICollection
Tamanho, Enumeradores e métodos de sincronização para coleções não-ge
IDictionary
Coleção não-genérica de par chave/valor
IEnumerable
Enumerador que oferece suporte a iterações simples sobre uma coleção nã genérica
IEnumerator
Oferece suporte a iterações simples sobre uma coleção não-genérica
IList
Coleção simples de objetos não-genéricos
Tabela 1. Principais interfaces do namespace System.Collections O entendimento das interfaces fica mais fácil quando analisamos as implementações das mesmas. Por exemplo, o namespace fornece a classe ArrayList, que implementa a interface IList através de uma matriz cujo tamanho é aumentado dinamicamente quando necessário. A partir desse exemplo, podemos concluir que a interface IList é, verdadeiramente, uma representação básica de uma coleção simples de objetos não-genéricos. Em outras palavras, ela traz uma base a ser seguida, com alguns atributos essenciais para a criação de estruturas de dados do tipo Lista. Outras implementações que podemos destacar são: · Queue: Implementa ICollection, IEnumerable e ICloneable, esta última não pertencente ao namespace System.Collections (e sim ao System). Representa uma fila
de dados, baseado no padrão FIFO (First in, First out): primeiro a entrar, primeiro a sair. · CollectionBase: Implementa IList, ICollection e IEnumerable. É a classe abstrata básica para criação de coleções de dados fortemente tipadas. · DictionaryBase: Implementa IDictionary, ICollection, IEnumerable. É a classe abstrata básica para a criação de um dicionário de dados, ou seja, uma estrutura fortemente tipada com pares chave/valor. Quando analisamos estas implement" [...]
Tratamento de exceções Exceções ocorrem com frequência no código e saber lidar com elas é fundamental para garantir que as aplicações se comportem adequadamente nessas situações, sem travar ou parar de funcionar. Nesse sentido o tratamento de exceções é parte fundamental de qualquer linguagem, pois permite identificar os problemas que ocorreram e definir fluxos alternativos para o programa. Observe a Figura 1.
Figura 1. Tratamento de exceções
Para aprender a tratar exceções em C#, sugerimos os seguintes conteúdos:
INTRODUÇÃO ÀS EXCEÇÕES CONTEÚDO DA AULA GUIAS RELACIONADOS
Iniciando nosso curso, entenderemos o que são exceções e como as aplicações se comportam quando exceções são lançadas e não há tratamento para elas.
Ir para o código
Conteúdo de apoio
Nenhum sistema está totalmente imune a erros e falha em algum momento seja por falta de internet, ausência de um periférico, por um erro no código ou em alguma biblioteca. A partir de um programa que calcula o total dos itens em uma venda, veremos neste vídeo que situações imprevisíveis acontecem e é preciso saber como se preparar para elas. Faremos isso a partir de um mecanismo totalmente orientado a objetos, que permite construir código coeso para prevenção e detecção de falhas.
O que são exceções?
Uma exceção é qualquer evento encontrado durante a execução do programa que interrompe o fluxo de processamento esperado. Elas podem ser lançadas por que o código possui um erro, quando um recurso está indisponível ou por muitas outras condições imprevistas. A fim de garantir sua integridade e também dos dados processados, um sistema de qualidade deve ser capaz de capturar exceções de forma consistente. Por que aprender sobre exceções?
Exceções estão presentes em todas as aplicações, portanto seu conhecimento é fundamental para o desenvolvedor. Além disso, o mecanismo de tratamento de exceções oferece diversas vantagens: o
Permite uma separação coesa entre as rotinas que descrevem o que fazer quando algo sai do controle e aquelas para as quais a aplicação foi planejada;
o
Uma vez que todas exceções lançadas em um programa são objetos, podemos agrupar e diferenciar entre tipos diferentes de exceções, aproveitando recursos da orientação a objetos;
o
A partir do mecanismo de captura de exceções, um método pode tratar internamente o erro ou delegar que ele seja tratado um nível acima na pilha de execução, sem que para isso seja necessário verificar seu retorno.
.NET Exceptions: Tratamento de Exceções em .NET Facebook Twitter (3) (0)
Veja nesse artigo uma abordagem básica de como tratar os tipos de erros e exceções possíveis em .NET
Fique por dentro Este artigo será útil principalmente para desmistificar o tratamento de exceções somente como base nos erros ocorridos pelo framework. Um programa bem formulado no tratamento de possíveis exceções pode se tornar, de uma maneira mais complexa, um programa de mais qualidade por não interromper o seu fluxo quando uma dessas exceções ocorrerem.
Entende-se por exceções não somente os erros providos da ferramenta, mas também os desvios do fluxo principal da sua aplicação. Neste artigo detalharemos uma forma padrão para esse tipo de tratamento, usando exemplos de códigos para evitar justamente que, ao ocorrer um desvio, o usuário seja pego com uma mensagem sem tratamento adequado e fique sem saber o motivo da paralisação do seu sistema.
Hoje em dia, com o desenvolvimento de softwares cada vez mais complexos e integrados com outros sistemas e plataformas, temos um alto índice de informações requisitadas ao usuário. Quando estas informações são transferidas através destas integrações, surge uma preocupação a mais quando se trata da programação dos recursos, pois os erros que podem ocorrer (ou podemos dizer exceções) são decorrentes, muitas vezes, da falta de algum recurso ou de algum argumento passado de forma inválida. Não se pode sair desenvolvendo linhas e linhas de códigos sem levar em conta os desvios que possam ocorrer ao longo da execução do sistema. Desenvolver sistemas com certas características sem nos atentar ao tratamento de exceções pode ser motivo de dor de cabeça futuramente para a equipe de desenvolvimento. Como podemos garantir a integridade do uso de uma aplicação? Afinal, ninguém gosta de ver o seu sistema “travado” durante sua execução. Quando ocorre uma exceção, o que acontece durante a execução do programa é que o fluxo do mesmo é redirecionado para uma rotina de tratamento dessa exceção. Caso esta não seja tratada, provavelmente surgirão aquelas mensagens na tela do usuário, praticamente incompreensíveis pela maioria deles. Por isso, é muito importante que os desvios do curso principal do sistema sejam tratados de forma efetiva, afim de que os usuários possam entender realmente o erro na utilização do programa, com uma mensagem mais agradável e efetiva, informando qual foi à regra não cumprida que acarretou a exceção lançada. Para ficar bem claro, um erro é quando o programa faz uma solicitação e a mesma não é retornada ou o sistema não consegue encontrar o componente ou página solicitada. Já uma exceção, por exemplo, pode se dar quando o usuário digita um valor inválido para um determinado campo. O tratamento dessas ocorrências é importante para conseguirmos nos comunicar com o usuário, afim de que o mesmo entenda o motivo do lançamento de uma exceção no sistema em determinada ocasião. Porém, de nada adiantaria executar o desvio de uma possível exceção se a mesmo não fosse tratada de forma conveniente, ou seja, sempre que for feito um desvio, mensagens devem aparecer bem escritas, de forma que a informação do motivo da ocorrência seja de fácil interpretação por parte do usuário do sistema. Podemos verificar casos em que temos o retorno de uma exceção específica, porém, fora do contexto. Para isso, pode ser feito o tratamento de uma ou mais exceções no mesmo bloco de comando, sempre levando em consideração a ordenação deste tratamento, da exceção mais específica para aquela menos específica. Caso contrário, se colocarmos uma instrução de tratamento genérica antes de tratarmos as mais específicas, estas jamais serão executadas.
Erros e Exceções Existem três grandes grupos de erros que podem ocorrer num sistema: 1. Erros de sintaxe: Fazem com que o programa nem execute, ou seja, o erro é retornado na hora da compilação do programa. Estes erros não são passíveis de tratamento via rotina, porém, são descriminado na hora de executar. 2. Erros em " [...]
Debug de aplicações .NET e a propriedade InnerException Facebook Twitter (3) (0)
Veja neste artigo como o acesso à propriedade InnerException de uma exceção pode auxiliar no debug de aplicações .NET, esclarecendo informações sobre um erro que à primeira vista podem não ser tão claras. Durante o desenvolvimento de aplicações ou, mesmo, com um sistema já em operação, não é um fato incomum que ocorram erros. As prováveis causas disto podem ser as mais variadas possíveis: descuidos por parte de programadores envolvidos em um projeto, uma funcionalidade não concluída conforme o esperado ou ainda, falhas decorrentes de algum elemento externo ao próprio software (servidores de banco de dados inoperantes, problemas em uma rede corporativa, falha no acesso a Web Services etc.) são apenas alguns dos fatores que podem conduzir a situações inesperadas e, em muitos casos, indesejáveis.
Considerando as principais plataformas de desenvolvimento atuais (com destaque para .NET e Java), erros são representados através de objetos conhecidos como exceções. A gravação de dados relativos a exceções geradas por uma aplicação se revela como um instrumento de extrema importância, visto que a partir destas informações desenvolvedores terão meios para tentar reproduzir falhas que afetaram um sistema. No .NET Framework informações sobre exceções estão associadas a objetos baseados no tipo Exception (namespace System). O comum é que existam diferentes classes que herdam dessa construção básica, com cada uma das mesmas estando normalmente vinculadas a um contexto bem específico. Sobre a estrutura da classe Exception, esta última conta com algumas propriedades que detalham um erro gerado dentro de uma aplicação:
Message: mensagem que descreve o erro que levou ao lançamento da exceção que se está considerando; InnerException: instância do tipo Exception que corresponde à falha original, estando associada a uma nova exceção. O preenchimento desta propriedade é opcional (logo, o valor desse elemento poderá ser “nul” em muitos casos); StackTrace: string em que são listados os diferentes pontos pelos quais passou a aplicação antes de um erro. Graças a esta informação, é possível se rastrear a origem, bem como compreender melhor o que levou a esta situação anormal. Uma única ressalva deve ser feita quanto a disponibilizar essas informações a usuários, já que existirão tanto aqueles incapazes de entender o conteúdo desta propriedade, assim como outros que com conhecimentos mais avançados podem vir a explorar vulnerabilidades do sistema.
A seguir estão listados alguns exemplos de exceções bastante comuns em aplicações construídas sob o .NET Framework:
InvalidOperationException (namespace System): exceção que acontece quando a invocação de um método a partir de um objeto for inválida, normalmente devido a problemas com o estado atual em que este se encontra; SqlException (namespace System.Data.SqlClient): geralmente lançada quando da ocorrência de erros em instruções enviadas a um banco de dados do SQL Server; CommunicationException (namespace System.ServiceModel): representa erros em processos de comunicação envolvendo serviços, sendo bastante comum em soluções baseadas na tecnologia WCF (incluindo nisto aplicações-cliente que consomem funcionalidades de serviços deste tipo); HttpUnhandledException (namespace System.Web): exceções deste tipo são lançadas quando erros não são tratados de modo apropriado dentro de aplicações Web; ArithmeticException (namespace System): falha resultante de um erro durante o processamento de uma operação aritmética; DivideByZeroException (namespace System): exceção derivada do tipo ArithmeticException, sendo disparada em casos que envolvem a tentativa de divisão de um número por zero; OutOfMemoryException (namespace System): erro associado à falta de memória e que impossibilita a um programa de prosseguir com sua execução normal; SecurityException (namespace System.Security): falha geralmente relacionada à detecção de problemas de segurança;
FileNotFoundException (namespace System.IO): corresponde a falhas ao se tentar realizar uma operação que acesse um arquivo; DirectoryNotFoundException (namespace System.IO): exceção disparada quando um diretório que se está tentando acessar não existir.
A forma como estes problemas são registrados (técnica esta conhecida como "logging") é também bastante diversa, podendo envolver desde a persistência dos dados em tabelas de bases relacionais ou em mecanismos próprios de um sistema operacional (como o Event Viewer do Windows), passando até mesmo pela gravação em arquivos (no formato texto ou XML, por exemplo). Existem inclusive frameworks específicos que simplificam a implementação deste tipo de funcionalidade, sendo possível citar no caso do .NET Framework a geração de arquivos XML por meio do log4net (http://logging.apache.org/log4net/). Independentemente da maneira como informações sobre exceções venham a ser gravadas, não será raro que programadores precisem se debruçar sobre o código-fonte, a fim de identificar em que situação uma falha poderá ocorrer. Muitas ferramentas visuais para desenvolvimento de software contam com mecanismos que facilitam tarefas desse gênero, sendo que a atividade relacionada ao uso de funcionalidades de execução de instruções e checagem de erros é conhecida como depuração ou, simplesmente, debug. O Visual Studio oferece um amplo suporte para a depuração de soluções .NET. A partir do menu Debug está disponível uma série de opções para a execução passo a passo de trechos de uma aplicação, incluindo nisto as estruturas conhecidas como breakpoints. Profissionais da área de software estão mais do que familiarizados com o poder e a flexibilidade que o uso de breakpoints oferece. Graças a esses recursos, é possível interromper o fluxo de execução de um sistema a partir de uma IDE de desenvolvimento, de maneira que se consiga inclusive executar instruções uma a uma, avaliando os dados e os resultados produzidos pelas mesmas. Trata-se, portanto, de um instrumento bastante importante para a simulação de situações que resultem em exceções. Ainda sobre a interrupção no fluxo de execução de aplicações, o Visual Studio costuma indicar o ponto em que uma falha ocorreu, pausando assim o processamento atual. Quando isto acontece, a própria IDE permite a visualização da instância que corresponde à exceção gerada, facilitando assim o trabalho de análise a ser desempenhado por um desenvolvedor. Na Figura 1 é demonstrado um exemplo disto, em que a tentativa de se acessar uma base de dados resultou em um erro do tipo SqlException.
Figura 1: Visualizando informações sobre uma SqlException a partir do Visual Studio Soluções construídas sob a tecnologia ASP.NET contam com algumas características peculiares no que se refere à geração de exceções. Conforme já mencionado anteriormente, um erro do tipo HttpUnhandledException será gerado caso uma falha não venha a ser tratada em aplicações Web. Esse comportamento pode vir a causar algumas dificuldades na depuração de uma aplicação, principalmente se um programador não se atentar a detalhes como a propriedade InnerException de uma exceção. Supondo uma aplicação em que o evento Application_Error do arquivo Global.asax será responsável pela gravação de informações sobre erros, utilizando para isto uma classe de nome LogHelper (Listagem 1). O método Application_Error será acionado sempre que uma exceção não for tratada adequadamente dentro desta aplicação Web. Por meio do método GetLastError do objeto Server é possível se obter a instância da exceção que corresponde a tal erro. Listagem 1: Evento Application_Error
...
void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
LogHelper.RegistrarErro(ex);
}
...
Caso uma falha desconhecida esteja acontecendo na aplicação que se está considerando como exemplo, uma possível forma de se rastrear tal problema seria incluir um breakpoint dentro do evento Application_Error. A partir disto, torna-se possível verificar detalhes de prováveis exceções disparadas em outros pontos do sistema. Uma vez que uma exceção seja lançada, pode-se visualizar o conteúdo de sua propriedade Message posicionando o mouse sobre a variável que armazena a mesma. Conforme demonstrado na Figura 2, a mensagem exibida é vaga demais, apenas indicando a ocorrência de um erro do tipo HttpUnhandledException.
Figura 2: Visualizando a mensagem associada a uma exceção O botão “+” em que consta a mensagem vinculada a uma exceção permite verificar maiores detalhes sobre uma falha. Neste exemplo específico (Figura 3) nota-se que a propriedade InnerException foi preenchida com o erro original (uma exceção do tipo DivideByZeroException), o qual é decorrente de uma operação aritmética que resultou em divisão por zero.
Figura 3: Analisando o conteúdo da propriedade InnerException O uso da propriedade InnerException é comum também em frameworks desenvolvidos por terceiros. Em tais casos, é provável que um erro inicial seja associado a um tipo de objeto mais genérico; nestas situações, apenas a consulta à propriedade InnerException poderá fornecer maiores informações na tentativa de se chegar à causa real de um problema. Procurei com este artigo fornecer uma dica simples, mas que pode ser de grande utilidade ao se efetuar o debug de aplicações .NET. Espero que o conteúdo aqui apresentado possa auxiliá-lo em algum momento. Até uma próxima oportunidade!
Ainda no contexto de identificação e tratamento de erros é importante saber utilizar os recursos de depuração/debug do Visual Studio. Um deles é o breakpoint, que permite pausar a execução da aplicação em determinado ponto do código para que possamos avaliar seu estado naquele momento. Para saber mais, acesse o link abaixo:
Breakpoints: Como depurar suas aplicações no Visual Studio Facebook Twitter (1) (0)
O artigo mostra as principais ferramentas disponibilizadas pelo Visual Studio para o programador realizar a depuração do seu código com o objetivo de encontrar e corrigir erros que possam ter aparecido em sua aplicação.
Atenção: esse artigo tem um vídeo complementar. Clique e assista! De que se trata o artigo
O artigo mostra as principais ferramentas disponibilizadas pelo Visual Studio para o programador realizar a depuração do seu código com o objetivo de encontrar e corrigir erros que possam ter aparecido em sua aplicação. Um exemplo é quando desejamos ter a certeza que tipo de valor está sendo retornado de um método.
Em que situação o tema é útil O artigo é útil para quem deseja conhecer recursos que irão aumentar a qualidade da aplicação ao tentar localizar erros nos programas e também irão permitir desenvolver uma aplicação já sendo preparado para futuras depurações, acrescentando alguns recursos, como atributos de depuração. Breakpoints e além Programas estão sujeitos a bugs. Aplicações que se definam estáveis também tem a tendência de apresentar, em alguma parte do seu ciclo de vida, eventuais bugs que podem ter sido acrescentados ao incluir ou alterar um requisito no projeto.
Boa parte do trabalho dos programadores, principalmente em projetos com ciclo de vida longo e que prevê manutenção do código, consiste em encontrar bugs e corrigi-los. Esta tarefa pode se tornar demorada, porque é fato que mesmo o mais correto código deixa de ser claro, mesmo para o seu criador em poucas semanas, ficando difícil encontrar o causador do bug. O Visual Studio e o Framework .NET sempre providenciaram ferramentas para facilitar a tarefa de depuração, desde suas versões iniciais. Além dessas, existem outros recursos que consistem mais em como desenvolver o projeto, visando a sua facilidade de depuração. Estes pontos serão alvo deste artigo e para demonstrar os mesmos, veremos a criação de um projeto de exemplo que pode ser usado para realizar testes de depuração. Desenvolver um bom software sem realizar sua devida depuração é algo praticamente impossível, considerando a realidade dos projetos de software que existem hoje em dia. Sempre haverá algum ponto do código que irá estar sujeito a algum tipo de falha, seja nos momentos em que novos requisitos forem incluídos, novas plataformas forem suportadas ou mesmo ao interagir com outras partes de software. Os bons projetos precisam reduzir o índice de bugs para o menor número possível, mas, precisam também facilitar a localização e solução destes quando surgirem. Estas são desde as mais básicas, como a interrupção do código para inspeção dos objetos na memória – o que é feito pelo já conhecido breakpoint, até preparar o código das classes para que apresentem informações que ajudem o programador a identificar o que está “acontecendo” com o seu código em um determinado ponto da execução. É fato que quanto maior e mais complexo o projeto, mais difícil será fazer a depuração, entretanto, com as técnicas apresentadas aqui, até mesmo projetos imensos podem ser depurados e ter seus problemas resolvidos.
Resolvendo problemas na aplicação Tudo se inicia dentro da IDE do Visual Studio ao se executar o código do projeto que é o objeto de depuração. Neste artigo vamos considerar apenas a depuração de códigos baseados em aplicações que tenham alguma interface com o usuário, como aplicativos Windows Forms, WPF, ASP.NET etc. Os tópicos apresentados aqui são mais indicados para estes tipos de projetos, mas, também é possível realizar a depuração em outros, como Webservices e serviços do Windows, para serem depurados quando o código está disponível ou até mesmo, fazer a depuração de processos remotamente localizados. Para iniciar a depuração é preciso que o código esteja sendo executado - aqui cabe uma observação importante para os programadores iniciantes: uma class library precisa estar anexada com um projeto executável que seja uma UI (interface com o usuário) para poder ser depurado. Se você tentar depurar um projeto deste tipo no Visual Studio, vai receber uma mensagem de que não é possível executar diretamente um projeto deste tipo.
Nota DevMan: Class Library é uma biblioteca de recursos criada a partir da plataforma .NET, sendo normalmente gerada como um arquivo recebendo a extensão .dll. Você pode, por exemplo, criar classes dentro da mesma. Desta forma, permite que as mesmas possam ser reutilizadas ao longo de uma série de projetos.
Os primeiros passos para entender como funciona a depuração é conhecer as duas modalidades para compilação e execução de um projeto, que são Debug e Release. Projetos no modo Debug incluem informações que facilitam a localização no código, como a call stack – que é a hierarquia de chamadas dos métodos e classes – contendo o número da linha da chamada no código e outros dados importantes para depuração, estes, que serão mostrados nos próximos tópicos. Normalmente, a opção escolhida para a compilação do projeto é o modo Debug (este é utilizado durante o desenvolvimento/testes da aplicação) e após, é alterado para o modo Release para ser enviado ao usuário final. A maneira mais simples de alterar o modo de compilação do projeto é através da barra de ferramentas, como ilustrado na Figura 1. Este modo é selecionado como padrão ao criar um novo projeto.
Figura 1. Barra de ferramentas com destaque para o seletor de modo de execução/compilação Existem algumas formas de iniciar a depuração de seu aplicativo: clicando no botão executar (ao lado do seletor de modo, na barra de ferramentas), pressionando F5, utilizando o meu Debug > Start Debugging ou na janela Solution Explorer, com o botão direito sobre o ícone do projeto, escolhendo Debug > Start Debugging (Figura 2).
Generics Gerenics é um recurso da linguagem C#, também presente em outras linguagens atuais, que simplifica e torna mais seguro o trabalho com objetos cujo tipo não é conhecido inicialmente. Ou seja, com esse recurso podemos trabalhar com tipos de dados "genéricos" sem ter de recorrer a declarar todos os objetos como um supertipo (como a classe Object) e realizar casts para manipulá-los de acordo com seu tipo real. Para conhecer melhor esse recurso sugerimos a leitura dos artigos a seguir:
C# - Generics- Artigo easy .net Magazine 11 Facebook Twitter (2) (0)
O artigo aborda como utilizar Generics, com foco em coleções de dados. Será apresentada uma introdução ao recurso e alguns exemplos práticos, inclusive utilizando de recursos anteriores aos Generics para entender seus benefícios.
Atenção: esse artigo tem um vídeo complementar. Clique e assista! De que se trata o artigo O artigo aborda como utilizar Generics, com foco em coleções de dados. Será apresentada uma introdução ao recurso e alguns exemplos práticos, inclusive utilizando de recursos anteriores aos Generics para entender seus benefícios. Para que serve Generics é um poderoso recurso incluído na plataforma .net desde a versão 2.0 do framework, com ele podemos criar classes e métodos reutilizáveis com mais eficiência, seu uso mais comum estão na manipulação de coleções fortemente tipadas. Em que situação o tema é útil A plataforma .net é repleta de suporte a manipulação de coleções de dados, mas foi na versão 2.0 que uma mudança significativa foi realizada, a inclusão de generics possibilitou criar coleções de forma mais eficiente, reduzindo ou eliminando a necessidade de conversão de tipos. Generics Generics são hoje uns dos principais fundamentos da programação para a plataforma .NET, de forma que podemos encontrar a aplicação do recurso em várias situações no .NET Framework. Permitem flexibilizar a forma como dados são tratados, pois é definido um parâmetro para um tipo. O artigo apresentará os motivos pelos quais os Generics surgiram, tratando de operações de Box, Unbox e conversões. Nos exemplos
práticos os Generics são demonstrados com coleções, como List e Dictionary. Ao final criaremos uma coleção customizada. Durante o desenvolvimento de um projeto procuramos sempre reutilizar o máximo de códigos, procurando evitar a repetição de linhas e com isso temos um código mais enxuto e flexível para mudanças. Felizmente as linguagens de programação mais utilizadas estão repletas de recursos que nos possibilitam realizar esta tarefa. Conhecer bem o funcionamento da linguagem é um início para criar bons projetos. Apesar de atualmente existir uma série de aprendizados para que o programador possa utilizar das melhores práticas de codificação e sempre ter um código bem elaborado, existem alguns recursos antigos que ainda são considerados poderosos para auxiliar no desenvolvimento de projetos, Generics é um deles. O .net Framework é uma poderosa tecnologia, sua evolução está em constante crescimento e sempre atualizada com as mais recentes necessidades de desenvolvimento de software, madura e com suporte a uma séria de tecnologias. Hoje proporciona o desenvolvimento para os mais diversos tipos de aplicativos. Atualmente o Visual Studio é um dos IDEs mais poderosos do mercado, e o Visual C# cada vez está melhor, sendo considerada uma das linguagens de programação mais relevantes da atualidade. Atualmente o .net framework está na versão 4.0, porém foi na versão 2.0 que o assunto deste tutorial foi adicionado ao framework. Nesta versão do .net framework houve diversas melhorias importantes em relação à versão anterior, e uma das mais relevantes e poderosas foi a inclusão de Generics. Com ele podemos criar um tipo especial que recebe como parâmetro outro tipo. Em C# temos os tipos de valor, chamados de Value Types e os tipos de referência, chamados de Reference Types. Resumidamente, os Value Types armazenam um valor e os Reference Types armazenam uma referência aos dados. Os tipos de valores derivam implicitamente do System.ValueType. Os tipos de referência guardam nele o endereço da memória onde o objeto está registrado, somente o endereço e não o objeto real. Já as variáveis de tipos de valor (Values Type) contêm o próprio objeto. Generics possibilitam a eliminação de conversão de tipos em sua aplicação. Para entender melhor o que seria uma conversão de tipos, vamos pensar no conceito de Box e Unbox. O Box seria quando adicionamos um elemento em uma caixa e fechamos, o Unbox seria quando abrimos e retiramos o elemento da caixa. Enquanto o elemento está dentro da caixa fechada, outras pessoas não saberão o que tem dentro, porém quando alguém receber a caixa e abrir, pode não estar preparado para seu conteúdo. De forma similar funcionam as conversões de tipos. Eu posso jogar qualquer valor dentro de um objeto (caixa), mas na hora de usar esse valor, eu vou precisar estar preparado para recebê-lo, ou seja, se for um tipo Double que está dentro do objeto, quando abrir a caixa eu preciso convertê-lo para Double e depois inserir em uma variável desse tipo.
Benefícios das listas genéricas no .Net Revista Easy .Net Magazine 28 Facebook Twitter (1) (0)
Neste artigo serão apresentados os benefícios da utilização de listas genéricas no .NET, com uma explicação prévia de conceitos fundamentais para a compreensão deste recurso. Artigo do tipo Tutorial Recursos especiais neste artigo: Contém nota Quickupdate. Listas genéricas Neste artigo serão apresentados os benefícios da utilização de listas genéricas no .NET, com uma explicação prévia de conceitos fundamentais para a compreensão deste recurso. Começaremos falando sobre a memória da aplicação para então abordarmos os conceitos de value type e reference type, dando todo o embasamento necessário para a compreensão dos recursos de boxing e unboxing. A partir daí detalharemos o uso das listas genéricas, que são usadas para armazenamento de objetos em memória, desde a sua conceituação até a sua implementação, através de um exemplo prático e objetivo onde construiremos um cadastro de pessoas em memória fazendo uso de diversos métodos da classe List.
Em que situação o tema é útil Este tema é útil para qualquer projeto de software que venha a fazer uso de listas de objetos em memória, auxiliando o desenvolvedor na compreensão do funcionamento e dos recursos disponíveis para manipulação de listas, podendo ser utilizado, como por exemplo, para a criação de cache de objetos em memória, possibilitando o aumento da performance da aplicação. Tradicionalmente no desenvolvimento de software nós temos a necessidade de trabalhar com estruturas de dados que agrupam uma série de elementos. Inicialmente, nos tempos
da programação estruturada, nós tínhamos à nossa disposição apenas os arrays, que nos permitiam armazenar informações dentro de uma estrutura indexada que fornecia métodos básicos para inserir, remover e recuperar tais informações. Com a chegada da orientação a objetos este cenário mudou e diversas implementações surgiram para trabalharmos com listas de objetos. Tais objetos passaram a ser disponibilizados diretamente pelas APIs das principais linguagens, como .NET e Java. Lista é uma relação de objetos que podem representar Pessoas, Carros, Bicicletas, tipos Inteiros, Strings, Decimais, dentre outros, ou seja, elas permitem que uma lista de objetos seja armazenada e manipulada em memória. Antes de entrarmos no mérito das listas, vamos ver melhor alguns conceitos fundamentais para a compreensão das mesmas.
Memória do Computador X Memória da Aplicação A memória do computador é o local físico onde são alocados as instruções e dados utilizados pelos processos em execução na máquina. Em operação normal, esta memória contém partes do sistema operacional e algumas aplicações que estão sendo executadas. A memória da aplicação é uma porção da memória do computador alocada pelo sistema operacional para rodar determinada aplicação. Essa memória está dividida em duas partes, Stack e Heap, conforme mostra a Figura 1.
Figura 1. Ilustração da divisão da memória da aplicação O espaço da stack é onde ficam registradas as variáveis declaradas em nosso sistema. O local onde o conteúdo desta variável será armazenado vai depender se esta variável é de um value type ou um reference type. Tipos de Dados por Valor (value type) Uma variável declarada como um tipo de dados por valor é estruturada na memória para conter um valor diretamente, ou seja, quando declaramos variáveis do tipo int, string, char, byte e todos os tipos que derivam de System.ValueType, o valor destas variáveis fica alocado diretamente na Stack, sem overhead (sobrecarga) de busca na heap,
fazendo com que os mesmos sejam mais leves, conforme podemos observar na Figura 2.
Figura 2. Exemplo de alocação de value type Tipos de Dados por Referência Uma variável declarada como um tipo de dados por referência é estruturada para conter uma referência para um objeto existente na Heap, ou seja, quando criamos um tipo através de palavras reservadas (Class, interface, array, delegate, etc.) estamos criando um tipo de referência. Estes ficam localizados na memória Heap quando instanciados e criam um endereço na Stack que aponta para um determinado local na Heap onde estará o objeto. Quando um objeto desse tipo perde a referência o Garbage Collector entra em ação para que não tenhamos objetos perdidos na Heap. Na Figura 3 temos um exemplo de alocação de uma variável de um reference type.
Figura 3. Exemplo de alocação de um reference type
Manipulação de arquivos Ler, escrever, criar e excluir arquivos são tarefas comuns em diferentes tipos de aplicação. Em C# contamos com um conjunto de classes que torna bastante simples implementar essas funcionalidades, como você poderá ver nos seguintes artigos:
Trabalhando com arquivos em C# Facebook Twitter (0) (0)
O artigo irá descrever os principais recursos da linguagem C# e do framework .Net para trabalhar com arquivos tanto com leitura como gravação destes. Você terá o primeiro contato com as classes disponibilizadas para escrever, ler e apagar arquivos. Também terá contato com as classes que permitem obter dados estatísticos do arquivo como tamanho, data da última modificação e se o arquivo existe.
Atenção: esse artigo tem uma palestra complementar. Clique e assista! Atenção: esse artigo tem um vídeo complementar. Clique e assista! [lead]Do que trata o artigo O artigo irá descrever os principais recursos da linguagem C# e do framework .Net para trabalhar com arquivos tanto com leitura como gravação destes. Você terá o primeiro contato com as classes disponibilizadas para escrever, ler e apagar arquivos. Também
terá contato com as classes que permitem obter dados estatísticos do arquivo como tamanho, data da última modificação e se o arquivo existe. Além dos conceitos envolvidos, será feita uma aplicação de demonstração para que você possa ver os recursos em uso. Para que serve Sempre é necessário ler dados de arquivos quer sejam de texto, arquivos XML ou qualquer outro formato. Existem muitos recursos dentro do framework .Net, mas nem sempre é muito fácil lembrar-se de como usá-los. Em que situação o tema é útil Mesmo com todas as funcionalidades oferecidas pelo sistema operacional, ao desenvolver aplicativos comerciais sempre se acaba precisando escrever dados em arquivos geralmente do tipo texto ou outro formato qualquer para troca de dados entre aplicações. Caso você esteja trabalhando com dados no formato texto e precise de referências rápidas para executar suas tarefas, este artigo oferece o que é necessário para executá-las. Além disto, existem alguns cuidados que você precisará tomar para poder escrever e ler em arquivos e que são expostos neste artigo. Resumo do DevMan Todas as informações armazenadas no computador estão dentro de arquivos. Estudando os fundamentos da computação um pouco mais a fundo, você perceberá que até mesmo as pastas (ou diretórios para os mais antigos) consistem de arquivos especiais onde outros arquivos são armazenados. As tarefas mais comuns do programador incluem manipular arquivos verificando se existem ou não dentro do sistema de arquivos da máquina onde o programa está sendo executado. Também é necessário criar os arquivos, atualizar seus dados sobrescrevendo os dados existentes, renomear ou ainda excluir um arquivo que não é mais necessário. Dentro da proposta de não precisar começar de um ponto muito elementar, você tomará conhecimento do que é preciso fazer usando a linguagem C# para executar estas tarefas. Além do contato com os códigos e classes que serão usados, também levaremos em conta alguns pontos com relação à segurança. Outro ponto a ser abordado são os elementos usados pela interface do Windows para facilitar o trabalho com os arquivos. E como sempre, é bom ter um exemplo prático onde tudo o que se aprendeu possa ser aplicado. [/lead] Uma das maiores vantagens dos computadores modernos é a possibilidade de se poderem armazenar os dados do trabalho que está sendo feito para se usar mais tarde. Pense por um momento em quais atividades que você executa diariamente usando os PC´s que fazem uso dos arquivos? Vão aqui algumas da minha lista: 1. Eu faço login em um computador que armazena em arquivo o meu ID e minha senha; 2. Após o login, o sistema lê as minhas preferências de uso do computador como o papel de parede e ícones da minha área de trabalho, mais uma vez, armazenado em arquivos;
3. Quando abro o programa para ler os e-mails, primeiramente preciso fazer o download das mensagens que estão armazenadas em um servidor de internet remoto e gravá-los em minha máquina. Mesmo que eu use um Webmail (como o Gmail ou o Hotmail), as mensagens estão armazenadas em arquivos de um servidor; 4. Como eu trabalho como programador, o código fonte fica gravado no disco do meu computador em arquivos texto especiais que são abertos pelo Visual Studio; 5. Se eu preferir jogar, os dados usados para os jogos estão em arquivos; 6. Músicas também são gravadas geralmente em arquivos MP3. São tantas as operações feitas usando-se arquivos que se for citar todas elas, o artigo fica sem graça. Mas, esteja certo: uma hora destas você vai precisar fazer o uso da manipulação de arquivos. No início, quando não havia ainda o Windows e os sistemas operacionais eram muito básicos, os programadores precisavam preocupar-se em resolver muitos problemas ao manipular os arquivos. Vejamos alguns destes problemas: • Era necessário localizar o arquivo dentro do disco, ler os bytes do mesmo e criar um objeto para manipular o arquivo a partir do endereço lógico do mesmo no disco rígido; • Antes de ler os dados, era necessário armazenar os bytes que diziam qual o tipo do arquivo: se binário ou texto. Era preciso guardar o número de bytes no momento da leitura para que fosse assegurado ao ler que o tamanho dos dados estava correto; • Se o arquivo estivesse dentro de um sistema UNIX, vários atributos precisavam ser tratados. Estes atributos diziam se se tratava de um arquivo regular, um diretório, um dispositivo de sistema ou ainda um link simbólico para outro arquivo; • Também seria necessário verificar se o usuário atualmente carregado no sistema tinha as permissões para ler o arquivo, para executar e gravar. Geralmente, nestes sistemas, nem sempre todas as permissões eram garantidas. Ou seja, era um trabalho bastante árduo. Com a chegada dos sistemas operacionais mais modernos, estes passaram a oferecer um conjunto de objetos para executar estas tarefas. Geralmente chamados de API (Application Programming Interfaces), estas bibliotecas cobriam boa parte das tarefas, embora, com alguma complexidade. A partir do surgimento dos ambientes gerenciados como o framework .Net as tarefas ficaram muito mais simplificadas pois, quase tudo o que se desejar fazer com os arquivos nos programas possuirá uma classe que irá resolver o problema para o programador. Não é possível cobrir todas as classes em um artigo como este, mas, partindo das tarefas mais básicas, vamos descobrindo como fazer as tarefas mais elementares. Para que você consiga entender bem o artigo e poder executar os exemplos eu espero que você tenha algum conhecimento dos seguintes assuntos:
• Criação de programa console e Windows Forms usando o Visual Studio; • Uso do modo de debug do Visual Studio para corrigir erros no código; • Criação de classes. • Programação orientada a objetos com C#. [nota]Nota: Apesar do artigo demonstrar as operações de maneira mais elementar deixando de apresentar alguns pontos importantes como criptografia, tabela de caracteres a ser usada (encoding pages) e gerenciamento de permissões, todas estas questões são extensamente cobertas pelo framework .Net e a linguagem C#. Para evitar estender demais o artigo, vamos tratar apenas das questões iniciais. Procure se informar mais sobre o assunto posteriormente. [/nota] [subtitulo]Classes para ler e gravar arquivos [/subtitulo] Basicamente todas as classes usadas para o trabalho com arquivos estão dentro da biblioteca System.IO. Assim, para poder usar estas em seu projeto ou sua classe você precisa adicionar a biblioteca da seguinte forma: using System.IO;
A Tabela 1 mostra as principais classes e as funcionalidades que ela provê. Além disto, demonstra também o contexto mais comum onde este tipo de classe será usada. "
Criando, copiando, movendo e excluindo arquivos em .NET Facebook Twitter (2) (0)
Veja neste artigo como manipular (criar, copiar, mover e excluir) arquivos no Windows utilizando os recursos do .NET Framework.
Criar, mover, copiar ou excluir um arquivo utilizando o .NET pode ser tão fácil quanto escrever uma única linha de código. Veremos neste artigo, duas formas de fazer isso, utilizando as classes File e FileInfo em C# e VB.NET. No exemplo das Listagens 1 e 2, é mostrado como criar, copiar, mover e excluir um arquivo utilizando a classe File. Listagem 1: utilizando a classe File em C# //Código em C#
File.Create(“C:\\arquivo.txt”);//Cria o arquivo “arquivo.txt” na unidade C:
File.Copy(“C:\\arquivo.txt”, “D:\\arquivo.txt”)//Copia o arquivo “arquivo.txt” da unidade C: para a D:
File.Move(“D:\\arquivo.txt”, “E:\\arquivo.txt”)//Move o arquivo “arquivo.txt” da unidade D: para a E:
File.Delete(“C:\arquivo.txt”)//Exclui o arquivo “arquivo.txt” da unidade C:. Agora deve restar apenas o da unidade E:
Listagem 2: utilizando a classe File em VB.NET //Código em VB.NET
File.Create(“C:\\arquivo.txt”);//Cria o arquivo “arquivo.txt” na unidade C:
File.Copy(“C:\\arquivo.txt”, “D:\\arquivo.txt”)//Copia o arquivo “arquivo.txt” da unidade C: para a D:
File.Move(“D:\\arquivo.txt”, “E:\\arquivo.txt”)//Move o arquivo “arquivo.txt” da unidade D: para a E:
File.Delete(“C:\arquivo.txt”)//Exclui o arquivo “arquivo.txt” da unidade C:. Agora deve restar apenas o da unidade E:
Notem que os métodos da classe File utilizados são estáticos, ou seja, não precisamos criar uma instância da classe para usar suas funções.
Agora, vejamos uma forma alternativa de fazer o mesmo, dessa vez utilizando a classe FileInfo. Em termos de funcionalidade, não há diferença. O que vale considerar para decidir qual das duas formas usar é saber que outras ações serão executadas com os arquivos em questão. As duas classes possuem propriedades e métodos diferentes, mas em boa parte dos casos, são usadas da mesma forma. A classe FileInfo, porém, requer que seja criada uma instância para que os métodos sejam chamados. Vejamos a seguir como fazer isso: Listagem 3: utilizando a classe FileInfo em C# FileInfo fi = new FileInfo(“C:\\arquivo.txt”);
fi.Create();//Cria o arquivo “arquivo.txt” na unidade C:
fi.CopyTo(“D:\\arquivo.txt”);//Copia o arquivo “arquivo.txt” da unidade C: para a D:
fi.MoveTo(“E:\arquivo.txt”);//Move o arquivo “arquivo.txt” da unidade C: para a E:
fi.Delete(); // Exclui o arquivo “arquivo.txt” da unidade C:. Agora devem restar os arquivos nas unidades D: e E:
Listagem 4: utilizando a classe FileInfo em VB.NET Dim fi As new FileInfo(“C:\\arquivo.txt”);
fi.Create();//Cria o arquivo “arquivo.txt” na unidade C:
fi.CopyTo(“D:\\arquivo.txt”);//Copia o arquivo “arquivo.txt” da unidade C: para a D:
fi.MoveTo(“E:\arquivo.txt”);//Move o arquivo “arquivo.txt” da unidade C: para a E:
fi.Delete(); // Exclui o arquivo “arquivo.txt” da unidade C:. Agora devem restar os arquivos nas unidades D: e E:
É importante observar que a instância da classe FileInfo mantém a referência a um arquivo. Diferente da classe file, onde referenciamos um arquivo qualquer para copiar, mover ou excluir, uma vez criado o objeto FileInfo, este será relativo sempre a um mesmo arquivo. Para que o exemplo das listagens 3 e 4 ficassem iguais ao das listagens 1 e 2, seria necessário excluir o arquivo da unidade D:. Para isso, precisaríamos criar
outra instância do FileInfo, referenciando o arquivo “D:\arquivo.txt” e depois chamar o método Delete(). Uma última observação é válida com relação ao método Move da classe File e MoveTo da classe FileInfo. Nos exemplos, o arquivo foi movido para outro diretório, porém, caso movêssemos o arquivo para o mesmo diretório de origem, seria o mesmo que renomear este arquivo. Bem, por enquanto é só. Foram dicas simples, mas que podem ser muito úteis no dia-adia, afinal, quem nunca precisou manipular arquivos externos a sua aplicação?
A maior parte das classes para manipulação de arquivos está contida no namespaces System.IO (de Input/Output), que é explorado em detalhes nos artigos abaixo:
Primeiros passos com o namespace System.IO - Revista easy .Net Magazine 25 Facebook Twitter (0) (0)
O artigo apresenta as classes que o .NET framework dispõe para realizarmos o tratamento de arquivos, diretórios do sistema e drives instalados no sistema operacional, utilizando práticas que podem melhorar o desempenho e manter a segurança nas atividades relacionadas a operações IO (entradas e saídas) em aplicações .NET. Demais posts desta série: Primeiros passos com o namespace System.IO - Parte 2
De que se trata o artigo
O artigo apresenta as classes que o .NET framework dispõe para realizarmos o tratamento de arquivos, diretórios do sistema e drives instalados no sistema operacional, utilizando práticas que podem melhorar o desempenho e manter a segurança nas atividades relacionadas a operações IO (entradas e saídas) em aplicações .NET. Em que situação o tema é útil Na grande maioria dos sistemas desenvolvidos, existe a necessidade de trabalharmos com arquivos organizados em diretórios no disco rígido da máquina ou leitura de drives e localização, manipulação e armazenamento de informações. Quando necessitamos manter alguma informação que não seja em bancos de dados relacionais, precisamos manter estas informações organizadas de uma forma que permita a leitura pela aplicação. Resumo Devman Neste artigo veremos quais as classes disponíveis no .NET framework que possibilitam a manipulação/edição e exclusão de arquivos físicos e diretórios de sistema em conjunto com as funcionalidades disponíveis pelo sistema operacional. Vamos entender a leitura das informações importantes de arquivos, diretórios e drives como, por exemplo, localização, verificação de propriedades, tamanho, espaço disponível no sistema operacional e monitoramento de alterações. Trabalhar com arquivos, diretórios e operações de IO é uma tarefa considerada trivial para desenvolvedores. Certamente a grande maioria das aplicações ainda necessita realizar o tratamento de arquivos, diretórios, leitura de imagens, vídeos e outros dados que estão armazenados tanto no disco rígido como em bancos de dados relacionais. A má utilização de comandos para estas operações podem comprometer a estrutura de uma aplicação inteira. Desta forma, para facilitar as operações I/O em aplicações .NET, o .NET framework dispõe de um namespace específico para estas tarefas. Trata-se do namespace System.IO. Com as classes disponibilizadas neste namespace, o desenvolvedor pode manipular diretórios, arquivos e drivers do sistema operacional, além de manipular leitura e reprodução de áudio, vídeo, imagens etc. Este conjunto de classes são derivadas do tipo FileSystemClass, que nada mais é que um conjunto de classes especializadas em manipulação de informação para arquivos, drivers e diretórios. Além deste conjunto de classes, este namespace disponibiliza uma série de outras classes, que tratam das mais variadas operações de I/O possíveis, como escrita e leitura em arquivos.
A Classe FileSystemInfo No namespace System.IO temos uma série de classes distintas, sendo cada uma aplicada a um tipo de objeto diferente no sistema operacional. A classe FileSystemInfo, a princípio, serve apenas como classe base, conforme os princípios da Orientação a Objetos, onde temos uma classe mais generalizada para dar
origem à classes mais específicas. Ela foi implementada justamente para que suas classes filhas (especializadas como FileInfo) possuam um conjunto de propriedades e métodos em comum, não sendo necessário reescrever os mesmos métodos em cada classe filha. Para conhecermos melhor as propriedades e métodos da classe FileSystemInfo, vamos analisar a Tabela 1, onde são listadas as propriedades e a Tabela 2, onde são apresentados os seus métodos.
Propriedade
Descrição
Attributes
permite manipular informações dos atributos do arquivo ou diretório que está se analisado. Atributos são um conjunto de informações utilizadas tanto pelo sistem operacional, aplicações ou até mesmo pelo usuário, para definir comportamento arquivo ou diretório. Um exemplo de atributo de um arquivo é o famoso “Somen Leitura”. Para verificar todos os atributos possíveis de um arquivo ou diretório, analisar o enumerador contido no mesmo namespace da classe FileSystemInfo, denominado FileAttributes.
CreationTime
permite informar ou ler informação da data em que o arquivo ou diretório foi cri
Exists
tem como único objetivo retornar se o arquivo ou diretório analisado através do informado existe ou não no sistema operacional. Neste caso a propriedade some retorna valor e não permite ser alimentada (modificada).
Extension
retorna a extensão de um arquivo, por exemplo, em arquivo com o nome “File.tx retornará a extensão txt.
FullName
retorna o caminho completo do arquivo ou diretório no sistema operacional, des drive inicial até o nome do arquivo ou diretório no sistema.
LastAccessTime
informa a data e hora do último acesso de um arquivo ou diretório.
LastWriteTime
informa a data e hora da última manipulação de escrita realizada em um arquivo diretório no sistema operacional.
Name
diferente da FullName, esta propriedade retorna apenas o nome do arquivo ou di sem o caminho ou extensão do mesmo.
Tabela 1. Propriedades da classe FileSystemInfo
Método
Descrição
Delete
responsável por excluir o arquivo ou diretório do sistema operacional.
Refresh
atualiza as informações (referente ao armazenamento) do arquivo ou diretório no operacional.
Tabela 2. Métodos da classe FileSystemInfo
A Classe FileInfo A classe destinada a fornecer informações de armazenamento de arquivos no sistema operacional é a classe FileInfo"
Primeiros passos com o namespace System.IO – Revista easy .Net Magazine Parte II Facebook Twitter (0) (0)
O artigo apresenta as classes que o .NET framework possui para realizarmos manipulação de arquivos gravados no sistema operacional ou arquivos/informações alocados na memória do computador.
Demais posts desta série: Primeiros passos com o namespace System.IO - Parte 1 De que se trata o artigo
O artigo apresenta as classes que o .NET framework possui para realizarmos manipulação de arquivos gravados no sistema operacional ou arquivos/informações alocados na memória do computador, utilizando práticas que podem melhorar o desempenho e manter a segurança nas atividades relacionadas a operações IO (entradas e saídas) em aplicações .NET.
Em que situação o tema é útil Sempre que for preciso editar, criar ou excluir algum arquivo hospedado no sistema operacional, ou manter dados gravados temporariamente na memória do computador, obtendo o máximo de performance no trabalho com dados de diversos formatos. Primeiros passos com o namespace System.IO – Parte II Neste artigo veremos as principais classes derivadas de Stream e que são usadas para manipulação de arquivos. Apresentaremos as classes File, Directory, StreamReader, StreamWriter e BufferedStream, explicando seus principais métodos e exemplificando o uso das mesmas. Conforme já comentamos no primeiro artigo, as tarefas de manipulação de arquivos são tarefas muito comuns no dia-a-dia do desenvolvedor, porém é fundamental o conhecimento dos recursos nativos do framework para tornar estas tarefas mais produtivas e seguras. Neste artigo vamos estudar como uma aplicação pode utilizar o .NET framework para ler e escrever arquivos, Para podermos realizar a manipulação de arquivos, criando, adicionando ou removendo conteúdo, ou até mesmo apenas lendo informações contidas nestes, precisamos primeiro entender como funciona o mecanismo de transferência de dados gravados para a aplicação que está solicitando os mesmos, e vice versa. Para que esta transferência de dados seja possível o framework possui uma série de classes que derivam de uma classe base, chamada Stream. Um Stream pode ser classificado como o objeto que realiza a conexão entre a aplicação que está necessitando manipular os dados e a fonte onde estes dados estão armazenados, podendo ser o disco rígido, a memória, a internet, o próprio teclado etc. Streams não se limitam apenas a trabalhar com arquivos, mas também são aplicados nas transferências de dados como vídeos, imagens, voz e demais dados que necessitem de transmissão de dados. A principal função dos Streams é permitir a interação da aplicação com elementos externos, sejam eles quais forem. Agora que conhecemos um pouco mais sobre o conceito de Stream, vamos conhecer quais as principais propriedades e métodos implementados pela classe
abstrata Stream do .NET. Assim como as demais classes de manipulação I/O, a classe Stream também está localizada no namespace System.IO e possui as propriedades apresentadas na Tabela 1 e os métodos apresentados na Tabela 2 a seguir: Nota do DevMan
I/O: Esta sigla é muito utilizada na computação e significa Input/Output (Entrada/Saída). Refere-se à comunicação de entrada e saída de dados entre softwares ou hardwares. Operações de entrada e saída de dados armazenados em um disco rígido são consideradas uma operação de I/O, assim como operações de entrada e saída entre um computador e uma impressora, mouse, teclado, scanner etc.
Propriedade
Descrição
CanRead
Esta propriedade determina quando o Stream suporta leitura de dados.
CanSeek
Esta propriedade determina quando o Stream suporta seeking. Seeking é uma bu uma posição no Stream. Através do Seek podemos posicionar o cursor de leitura gravação em um determinado local e ler ou gravar a partir deste local.
CanTimeOut
Determina se o Stream possui um Timeout, ou seja, se a operação atual com o S pode expirar depois de determinado tempo.
CanWrite
Determina se o Stream permite gravação de dados (escrita).
Length
Esta propriedade retorna o tamanho do Stream, em bytes.
Position
Retorna e permite informar qual a posição do cursor no Stream. Esta posição não ser maior que o tamanho retornado pela propriedade Length.
ReadTimeout
Informação de qual é o tempo limite para operações de leitura do Stream.
WriteTimeout
Informação de qual é o tempo limite para operações de gravação (escrita) do Str
Tabela 1. Propriedades da classe Stream
Método
Descrição
Close
Fecha o Stream e libera todos os recursos associados a ele.
Flush
Limpa qualquer buffer existente para o Stream e força as alterações para que sej gravadas na fonte de dados.
Read
Este método executa uma leitura sequencial no Stream, conforme o número de b definido e a partir da posição atual do cursor no Stream, atualizando esta mesma após a leitura.
ReadByte
Executa a leitura de apenas um byte no Stream, partindo da posição atual e atual esta posição após a leitura. É o mesmo que executar o método Read passando co parâmetros apenas um byte.
Seek
Método que permite definir a posição do cursor no Stream sem a necessidade de realizar a leitura até esta posição.
SetLength
Define o tamanho do Stream. Caso o tamanho informado seja menor que o tama dados contido no Stream este será truncado. Se for maior, apenas será expandido novo tamanho.
Write
Método que permite a gravação de informação no Stream, informando o número desta informação e atualizando o mesmo para a nova posição após a gravação.
WriteByte
Realiza o mesmo que no método Write, porém grava apenas um byte no Stream.
Tabela 2. Métodos da classe Stream Nota do DevMan
Buffer: Termo utilizado para denominar uma região da memória do computador que está alocada para o armazenamento temporário de dados, sendo possível realizar leituras e gravações neste espaço. A Figura 1 ilustra como os diferentes tipos de Streams, contidos no .net framework, se relacionam para fornecer uma estrutura organizada de leitura e gravação de dados em diversas fontes de dados e também em algumas classes de leitura (reader) e escrita (writer) que servem de apoio para o trabalho com Streams. Durante este artigo veremos os principais e mais utilizados Streams e suas classes de apoio.
Figura 1. Streams do .net framework e classes de apoio para leitura e escrita. (Fonte: http://www.cnblogs.com/erebus/articles/2176646.html)
Classes de Apoio para Streams As classes de Streams podem ser utilizadas sozinhas ou em conjunto com classes de apoio, que são classes que nos fornecem serviços que facilitam a interação com os Streams. Com a classe File podemos realizar uma série de operações com arquivos, como a leitura e escrita do conteúdo de um arquivo, a criação ou a abertura de um arquivo com permissões de somente leitura, a criação ou escrita de um arquivo com permissões de escrita, além de operações básicas com arquivos como a verificação se o arquivo pesquisado existe, exclusão de arquivos, dentre outras. A classe File auxilia o trabalho com os Streams pelo fato de possuir alguns métodos específicos que retornam instâncias de classes do tipo Stream já abertas e prontas para a utilização. O objeto do tipo Stream mais básico retornado pela classe File é o objeto FileStream, que vamos estudar profundamente mais tarde, mas que tem como objetivo representar um arquivo no sistema de arquivos e nos possibilita interagir com o mesmo. Além deste objeto mais genérico, FileStream, a classe File também possui métodos que retornam Streams, mais específicos como o objeto StreamReader, que é um objeto que também representa um arquivo, porém com permissões de operações relacionadas somente à leitura dos dados deste arquivo, ou seja, somente será possível ler dados com este tipo de Stream.
Veremos a seguir as classes de apoio para Streams disponibilizadas pelo .net framework.
Classe File Como comentamos anteriormente, a classe File possui uma série de propriedades e métodos que possibilitam o trabalho com Streams de uma forma mais simplificada. Para entender melhor como esta classe pode nos auxiliar, podemos verificar na Tabela 3 os seus principais métodos.
Método
Descrição
AppendAllText
Adiciona um texto ao final do conteúdo de um arquivo existente, ou, no caso do não existir, cria o mesmo com o texto como conteúdo.
AppentText
Abre um arquivo, ou cria um novo quando não existir, e retorna uma instância d classe StreamWriter a qual é preparada para a escrita de conteúdo no arquivo.
Copy
Cria uma cópia do arquivo.
Create "
Caso você precise lidar especificamente com arquivos ZIP também há classes específicas para isso. Se for esse o caso, sugerimos a leitura do artigo abaixo:
Manipulação de arquivos .zip no .NET Framework 4.5 Facebook Twitter (0) (0)
Veja neste artigo alguns exemplos de utilização das classes ZipFile e ZipArchive. Esses novos tipos foram disponibilizados com o lançamento do .NET Framework 4.5, tendo por objetivo simplificar operações de compactação e descompressão de arquivos. O uso de mecanismos para a compactação de arquivos é, sem sombra de dúvidas, um recurso bastante difundido no meio empresarial. Devido ao grande volume de informações processadas, diversas técnicas são empregadas com intuito de reduzir a quantidade de dados, tentando com isto maximizar o potencial de utilização dos meios de armazenamento e/ou transmissão. Cópias de segurança (backups) são um exemplo notório de aplicação de meios para a compactação de informações. A realização de backups é um recurso vital em qualquer tipo de negócio, uma vez que possibilita a rápida recuperação de dados essenciais, viabilizando assim a retomada das operações rotineiras tão logo ocorram situações consideradas como desastrosas. A importância das cópias de segurança é atestada pelo enfoque dado a este tipo de questão por muitas auditorias corporativas, com uma série de procedimentos determinando que informações se prestam à realização de backups, além da periodicidade a ser levada em conta neste último caso. Um dos formatos de compactação mais conhecidos é o ZIP. Este padrão aberto surgiu ainda em 1989, tendo passado por uma série de evoluções desde então. Sobre estruturas baseadas nesta especificação (extensão .zip), é comum que as mesmas possuam várias pastas e arquivos como parte de seus respectivos conteúdos, sendo possível inclusive configurar a taxa com a qual tais itens serão comprimidos (maior compactação significa, normalmente, um maior tempo em operações que envolvam a descompressão de arquivos). O processo de descompactação de arquivos .zip também oferece uma flexibilidade considerável. Existe a alternativa de se descompactar um arquivo específico, sem que isto se traduza na necessidade de efetuar tal ação sobre todo o conteúdo que se está
manipulando. A este comportamento que permite a execução de operações sobre um arquivo específico dá-se o nome de acesso randômico. Todas essas características aqui mencionadas estão entre as razões que contribuíram para a popularização do padrão ZIP. Este formato é a base utilizada na geração de arquivos JAR (bibliotecas contendo classes e outros recursos Java compactados), no novo padrão para documentos, planilhas e apresentações do pacote Office (Open XML), além de ser suportado nativamente pelo próprio Windows (existem inclusive funcionalidades do sistema operacional que envolvem o uso de recursos de compactação). A plataforma .NET conta desde a versão 2.0 com mecanismos que permitem a compactação de arquivos: trata-se da classe GZipStream (namespace System.IO.Compression). Contudo este tipo apresentava algumas limitações, como a impossibilidade de se comprimir vários arquivos numa mesma estrutura. Isso levou ao surgimento de alternativas que procuravam suprir esta demanda, sendo um bom exemplo disto a biblioteca SharpLibZip (http://sharpziplib.com/). Já com o .NET Framework 3.5 seriam disponibilizadas as classes Package e PackagePart (namespace System.IO.Packaging). A partir de então, a plataforma .NET passou a contar com a capacidade de manipulação de arquivos compactados com uma estrutura mais complexa (considerando inclusive pastas com arquivos vinculados às mesmas). Com o lançamento do .NET 4.5 outros melhoramentos foram introduzidos no que se refere ao suporte do formato ZIP. A finalidade deste artigo é descrever o funcionamento das classes ZipFile (http://msdn.microsoft.com/enus/library/system.io.compression.zipfile.aspx) e ZipArchive (http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchive.aspx), as quais foram disponibilizadas neste nova versão do Framework. Para isto, serão apresentados a seguir alguns exemplos de trechos de código que utilizam esses tipos.
Utilizando as classes ZipFile e ZipArchive em aplicações .NET Antes de iniciar a discussão sobre as características das classes ZipFile e ZipArchive, é necessário ressaltar que referências a duas bibliotecas precisarão ser adicionadas a projetos que venham a empregar esses tipos. Este procedimento pode ser realizado, dentro do Solution Explorer do Visual Studio 2012, clicando com o botão direito sobre o item "References" de um projeto e selecionando na sequência a opção "Add Reference...". Aparecerá então a janela “Reference Manager”. Em “Assemblies > Framework” localizar as bibliotecas “System.IO.Compression” e
“System.IO.Compression.FileSystem”, selecionando as mesmas (Figura 1). Acionar finalmente o botão “OK”, de maneira que a referências seja incluídas no projeto que se estiver criando.
Figura 1: Adicionando referências à bibliotecas de compressão a um projeto ZipFile será a primeira das classes abordada por este artigo. Trata-se de um tipo estático, o qual disponibiliza métodos que podem ser usados em operação de criação, extração ou leitura de arquivos gerado segundo o padrão ZIP. Supondo que seja preciso compactar toda uma estrutura de diretórios e arquivos de um projeto ASP.NET, como aquela que consta na Figura 2.
Figura 2: Exemplo de estrutura de diretórios e arquivos a ser compactada A Listagem 1 apresenta um exemplo de como isto pode ser feito através da classe ZipFile. No caso, foi utilizado o método CreateFromDirectory, o qual cria um novo arquivo .zip a partir de um diretório específico; devem ser fornecidos como parâmetros a esta operação:
O caminho do diretório que estará sendo comprimido; O nome do arquivo no formato .zip que será gerado como resultado do processamento; O nível de compressão do arquivo a ser criado. Para este parâmetro é necessário utilizar um dos valores disponíveis para o enumeration CompressionLevel (namespace System.IO.Compression): Optimal (utiliza-se o maior grau de compactação possível, por mais que esta tarefa possa demandar um tempo maior), Fastest (prioriza-se a velocidade do processo de compactação, mesmo que com isto não se atinja o melhor índice possível), NoCompression (os diferentes itens adicionados a um arquivo .zip não estarão comprimidos); Um valor booleano que, em caso afirmativo, indica que o nome do diretório-base será incluido na hierarquia de pastas a serem compactadas (neste exemplo específico, definiu-se o valor “false”, de forma que não exista dentro do arquivo .zip uma pasta de nome ArquivosACompactar como diretório inicial).
Listagem 1: Exemplo de utilização do método CreateFromDirectory da classe ZipFile ...
ZipFile.CreateFromDirectory(
@"C:\Temp\TesteCompactacao\ArquivosACompactar\",
@"C:\Temp\TesteCompactacao\Exemplo01.zip",
CompressionLevel.Optimal,
false);
...
Após a execução do trecho de código da Listagem 1, um arquivo de nome “Exemplo01.zip” terá sido criado conforme especificado na chamada ao método CreateFromDirectory (Figura 3). Visualizando o conteúdo deste arquivo, será possível confirmar que o mesmo foi gerado seguindo a mesma estrutura de arquivos e pastas do diretório informado originalmente (Figura 4).
Figura 3: Arquivo .zip que foi gerado a partir do método CreateFromDirectory
Figura 4: Visualizando o conteúdo do arquivo .zip gerado via método CreateFromDirectory Já na Listagem 2 está um exemplo de uso da operação ExtractToDirectory. Esse método estático recebe como parâmetros o nome de um arquivo .zip, além do diretório de destino em que será descompactado o conteúdo deste último. Listagem 2: Exemplo de utilização do método ExtractToDirectory da classe ZipFile ...
ZipFile.ExtractToDirectory(
@"C:\Temp\TesteCompactacao\Exemplo01.zip",
@"C:\Temp\TesteCompactacao\ArquivosDescompactados\");
...
O processamento da instrução que consta na Listagem 2 irá descompactar todo o conteúdo do arquivo “Exemplo01.zip” no diretório que foi definido como destino (Figura 5).
Figura 5: Conteúdo de arquivo .zip descompactado via método ExtractToDirectory Além do tipo estático ZipFile, a classe ZipArchive também oferece uma série de funcionalidades que visam facilitar a implementação de funcionalidades baseadas na manipulação de arquivos compactados. ZipArchive é um tipo que corresponde a uma representação de um arquivo .zip. Em termos práticos, isto significa que por meio dessa estrutura poderão ser executadas operações individuais sobre os itens de um arquivo .zip. Cada elemento compactado dentro de um arquivo .zip equivale a uma instância do tipo ZipArchiveEntry (namespace System.IO.Compression), a qual pode ser obtida através de uma chamada ao método GetEntry com uma referência da classe ZipArchive. A identificação de um objeto ZipArchiveEntry corresponde ao caminho de um elemento dentro de um arquivo .zip (possíveis pastas + nome do arquivo que está compactado). Dentre as ações possíveis empregando os tipos ZipArchive e ZipArchiveEntry estão: a inclusão de novos arquivos, atualizações no conteúdo de tais elementos, exclusões de itens ou ainda, a leitura individual destes. O exemplo que consta na Listagem 3 faz uso das classes ZipFile, ZipArchive e ZipArchiveEntry simultaneamente, a fim de com isto extrair dois itens que constam no arquivo .zip criado anteriormente. O método Open de ZipFile permite a abertura de um arquivo compactado, recebendo como parâmetros o caminho deste, além de que modo isto ocorrerá. Para este último parâmetro, deverá ser utilizado um dos valores do enumeration ZipArchiveMode (namespace System.IO.Compression):
Read: carregamento de um arquivo .zip para a realização somente de operações de leitura; Create: permite apenas a inclusão de novos itens a um arquivo .zip;
Update: permite tanto a realização de operações de leitura, quanto escrita em um arquivo zipado.
No trecho de código apresentado na Listagem 3, definiu-se que o arquivo “Exemplo01.zip” será aberto para a leitura, para que se consiga com isso proceder com a extração de alguns dos elementos armazenados no mesmo (valor de enumeration ZipArchiveMode.Read). A partir de instâncias da classe ZipArchiveEntry é acionado o método ExtractToFile, de maneira que a descompactação de um item gere um novo arquivo (com base no caminho que foi passado como parâmetro). A execução desse conjunto de instruções será responsável pela geração de dois arquivos em um diretório de testes, conforme indicado na Figura 6. Listagem 3: Exemplo de utilização das classes ZipFile e ZipArchive ...
using (ZipArchive archive = ZipFile.Open(
@"C:\Temp\TesteCompactacao\Exemplo01.zip",
ZipArchiveMode.Read))
{
string caminhoBaseExtracao =
@"C:\Temp\TesteCompactacao\ExtracaoZipArchive\";
ZipArchiveEntry entry1 =
archive.GetEntry(@"ProjetoExemplo.sln");
entry1.ExtractToFile(
caminhoBaseExtracao + "ProjetoExemplo.sln");
ZipArchiveEntry entry2 =
archive.GetEntry(@"ProjetoExemplo\Default.aspx");
entry2.ExtractToFile(
caminhoBaseExtracao + "Default.aspx");
}
...
Figura 6: Arquivos descompactados por meio das classes ZipFile, ZipArchive e ZipArchiveEntry Por fim, a Listagem 4 apresenta um exemplo de criação de um arquivo .zip. Neste último caso, está sendo invocado o método Open de ZipFile, informando-se ao mesmo o valor de enumeration ZipArchiveMode.Create. Esta ação resultará na criação de um novo arquivo .zip e, consequentemente, na geração de uma instância do tipo ZipArchive (já habilitada para a inclusão de itens que ficarão compactados). Chamadas à operação CreateEntryFromFile são então efetuadas utilizando a referência da classe ZipArchive, de maneira a se incluírem itens ao arquivo .zip que se está manipulando. Este método recebe como parâmetros:
O caminho do elemento que será adicionado ao arquivo .zip; O nome de tal elemento dentro do arquivo .zip considerado;
O grau de compactação do arquivo que se está comprimindo (a partir de um dos valores possíveis para o enumeration ZipArchiveMode).
Listagem 4: Criação de arquivo .zip com as classes ZipFile e ZipArchive ...
using (ZipArchive archive = ZipFile.Open(
@"C:\Temp\TesteCompactacao\Exemplo02.zip",
ZipArchiveMode.Create))
{
string caminhoBaseOrigem =
@"C:\Temp\TesteCompactacao\ArquivosACompactar\";
archive.CreateEntryFromFile(
caminhoBaseOrigem + "ProjetoExemplo.sln",
"ProjetoExemplo.sln",
CompressionLevel.Optimal);
archive.CreateEntryFromFile(
caminhoBaseOrigem + @"ProjetoExemplo\Default.aspx",
@"ProjetoExemplo\Default.aspx",
CompressionLevel.Optimal);
}
...
A execução do código que está na Listagem 4 produzirá como resultado um arquivo com conteúdo similar àquele que consta na Figura 7.
Figura 7: Arquivos criado através das classes ZipFile e ZipArchive
Conclusão Conforme detalhado no transcorrer deste artigo, as novas classes do .NET 4.5 para manipulação de arquivos .zip (ZipFile e ZipArchive) simplificam consideravelmente a implementação de funcionalidades que envolvam o uso de técnicas de compactação. Os diferentes recursos oferecidos por esses tipos permitem a realização de operações sofisticadas, sem que isso implique em grandes esforços de codificação.
LINQ LINQ (Language Integrated Query) é uma parte da linguagem C# que permite consultar coleções de dados com uma sintaxe semelhante à da linguagem
SQL, com cláusulas para filtros e junções entre resultados, por exemplo. Para conhecer esse recurso, confira o artigo abaixo:
Introdução a LINQ Revista easy .net Magazine 30 Facebook Twitter (4) (0)
Neste artigo veremos o conceito de LINQ e alguns exemplos de implementações com o mesmo que podem ser utilizados no nosso dia a dia. Artigo do tipo Exemplos Práticos Recursos especiais neste artigo: Conteúdo sobre boas práticas. Introdução a LINQ LINQ (Language Integrated Query) é uma funcionalidade muito importante do .NET framework, trazendo uma sintaxe próxima do SQL, porém simples para ser utilizada em qualquer fonte de dados, de arrays e listas em memória a implementações para o uso com banco de dados.
Neste artigo veremos seu conceito e alguns exemplos de implementações com o mesmo que podem ser utilizados no nosso dia a dia.
Em que situação o tema é útil Este tema é útil na recuperação de dados de forma prática, eficiente e produtiva, fornecendo ao desenvolvedor um modo elegante de acessar seus dados estejam eles em memória, arquivos XML ou bases de dados. Lidar com coleções de dados é algo que desenvolvedores necessitam muitas vezes, sejam dados vindos de um banco de dados, dados de XML, dados em listas de objetos
dentre outros veículos, porém o caminho mais comum é ter para cada fonte de dados uma forma diferente de trabalhar. Eis então que surge no .NET 3.5 a Language-Integrated Query, conhecida como LINQ, tornando a manipulação de itens de dados, sejam objetos, arquivos XML ou retornos de banco de dados, em objetos tendo praticamente a mesma forma para todos eles. Assim se tornou mais fácil trabalhar com os dados de forma unificada. Tendo surgido no .NET 3.5 ela é suportada a partir do C# 3.0 em diante e também por outras linguagens da plataforma, como por exemplo, o VB.Net. A LINQ é então uma sub linguagem de paradigma declarativo que roda por cima da CLR podendo ser usada misturada no contexto de outras linguagens, por exemplo, seu projeto pode ser Vb.Net ou C#, entre outras, e usar trechos de código LINQ no meio dos seus códigos. Temos então a Figura 1 que mostra onde a LINQ está na arquitetura do .NET Framework:
Figura 1. A LINQ dentro do .Net Framework Como pode ver a LINQ é disponível para qualquer linguagem do .NET como um recurso adicional. Os chamados LINQ-Enabled Data Sources seriam implementações da LINQ para diversos cenários, como a LINQ to SQL, primeiro esboço de ORM da Microsoft, substituído depois pelo Entity Framework que utiliza a LINQ to Entities, além da LINQ to XML em que podemos usar para consultar nós no XML como itens de dados em uma estrutura OO. Nota: A LINQ consegue trabalhar com qualquer estrutura de objetos que implemente a interface IEnumerable.
As principais vantagens apontadas pela Microsoft para o uso da LINQ são a validação de sintaxe em tempo de execução, suporte a IntelliSense e tipagem estática.
Sintaxe A linguagem LINQ possui duas formas de pesquisa: sintaxe de consulta (muito próxima da linguagem SQL) e a sintaxe de método.
Sintaxe de consulta A sintaxe de consulta é chamada de Comprehension Queries e utiliza de palavras chaves em comum com a SQL como from, where e select, porém em uma outra ordem. Segundo o time da LINQ eles fizeram de uma forma mais lógica do que a SQL: primeiro o quê, depois de onde, depois a clausula where e por fim o select. Sempre começando com from e terminando com select, como podemos notar no exemplo da Listagem 1. Listagem 1. Selecionando uma string dentro de um array "
Uma possibilidade muito interessante do LINQ é a de utilizar Lambda Expressions para realizar as consultas, ao invés da sintaxe padrão de queries. Caso você ainda não saiba o que são Lambda Expressions, você pode ver o curso abaixo e logo em seguida ver como aplicá-las no LINQ:
Usando Expressões Lambda com LINQ Facebook Twitter (8) (0)
Neste artigo vamos falar um pouco sobre Expressões lambda, veja o que são e como funcionam. Esse artigo abrange também a utilização de lambda com LINQ. Uma expressão Lambda é uma função anônima que você pode usar para criar Delegates ou tipos de árvores de Expressão. São particularmente muito úteis para escrever consultas LINQ. Para facilitar o entendimento, a expressão lambda é uma espécie de função, porém sem nome. Ela realiza cálculos, filtros e retorna valores ou coleções de valores. O símbolo “=>” é o operador Lambda, também conhecido como vai para (goes to), e toda expressão precisa dele. Ao criar uma você, devem-se colocar os parâmetros a esquerda do operador (caso haja parâmetros) e do outro lado do bloco coloca-se a expressão ou instrução do. Esse tipo de expressão pode ser atribuído a Delegates. Abaixo veremos alguns exemplos de expressões lambda para facilitar o entendimento. Na Listagem 1 um array numérico e deveremos saber quantos números pares tem nesse array. Listagem 1. Exemplo Expressão lambda static void Main(string[] args)
{
//Criamos uma array de numeros
int[] numeros = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//utilizamos a função cout com uma expressão lambda como parametro
int qtdPares = numeros.Count(x => x % 2 == 0); // qtdPares = 5
Console.WriteLine("Quantidades de Numeros Pares: {0}",
qtdPares.ToString());
Console.ReadKey();
}
Agora veremos um exemplo criando um delegate na Listagem 2. Imagine que você tem uma situação onde precise passa um valor e receber o quadrado dele. Listagem 2. Exemplo Expressão lambda com Delegate delegate float deleg(float i);
static void Main(string[] args)
{
//Criamos uma variavel para receber um valor digitado pelo usuário
float I = float.Parse(Console.ReadLine());
//Criamos uma variavel do tipo delegate que recebe
//a expressão lambda
deleg _Delegate = x => x * x;
float j = _Delegate(I); //j = 25
Console.WriteLine("O Quadrado de {0} é {1}",
I.ToString(), j.ToString());
Console.ReadKey();
}
Porque usar Expressões lambda? Devemos usar as Expressões lambdas para deixar seu código muito mais simples e legível, tornando-o mais agradável e elegante, principalmente na forma como embarca métodos anônimos ao código.
Expressões lambda com LINQ Bom agora que já obtivemos uma breve explicação sobre expressões lambda, vamos mostrar sua utilização com LINQ. LINQ é uma linguagem de consulta que foi introduzida a partir do .Net Framewok 3.5 e é utilizada na manipulação de coleções, sejam elas vindas de um banco de dados, documentos XML, coleções de objetos, etc. Assim, podemos definir o LINQ como uma consulta que não tem dependência da fonte de dados. Agora vamos criar um cenário para que possamos demonstrar a utilização do LINQ e a melhora quando utilizamos lambda junto a ele. Vamos criar uma lista do tipo pessoa; pessoa é um tipo de dado com as propriedades Nome, Idade, Cidade e Sexo. Para montarmos esse exemplo, Abra o Visual Studio e crie um novo projeto clicando em File -> New -> Project, conforme a Figura 1.
Figura 1. Criando o projeto. Na janela seguinte digite “empty” na caixa de pesquisa e selecione BlankSolution. Altere a propriedade Name para Lambda ou para o nome que preferir, conforme mostra a Figura 2.
Figura 2. Novo projeto em Branco.
Se olharmos no Solution Explorer veremos que foi criada uma solução vazia, então agora clique com o botão direito do mouse e escolha Add... e em seguida New Project. Crie um projeto do Tipo Console Application e altere a propriedade Name para Lambda, conforme demonstrado nas Figuras 3 e 4.
Figura 3. Adicionando um novo projeto a Solution.
Figura 4. Criando o projeto Console Application. Agora vá até o Solution explorer e clique com o botão direito do mouse sobre o projeto Console Application. Escolha a opção Add -> New Item e na janela que irá se abrir escolha a opção Class, alterando a propriedade Name para Pessoa.cs. Agora vamos
implementar a classe pessoa, onde a mesma deve ter as propriedades Nome, Idade, Cidade e Sexo e um construtor que recebe essas propriedades, conforme a Listagem 3. Listagem 3. Criando Classe Pessoa class Pessoa
{
public String Nome { get; set; }
public int Idade { get; set; }
public String Cidade { get; set; }
public String Sexo { get; set; }
public Pessoa(String _Nome, int _Idade,
String _Cidade, String _Sexo)
{
Nome = _Nome;
Idade = _Idade;
Cidade = _Cidade;
Sexo = _Sexo;
}
public override String ToString()
{
return "Nome: " + Nome + " Idade: " +
Idade.ToString() + " Cidade: " + Cidade + " Sexo: " + Sexo;
}
}
No mesmo arquivo Pessoa.cs vamos também implementar uma classe que é uma lista de pessoa. Essa classe conterá apenas um construtor sem parâmetros que servirá para alimentarmos nossa lista de pessoas, ou seja, ao instanciarmos essa lista ela já estará populada. Observe a Listagem 4. Listagem 4. Alimentando a Lista de pessoas class lPessoa : List
{
public lPessoa()
{
this.Add(new Pessoa("JOSÉ SILVA", 21, "PIRACICABA", "M"));
this.Add(new Pessoa("ADRIANA GOMES", 18, "CURITIBA", "F"));
this.Add(new Pessoa("JOSÉ NOVAES", 17, "CAMPINAS", "M"));
this.Add(new Pessoa("ANDRÉ NEVES", 50, "PIRACICABA", "M"));
this.Add(new Pessoa("JONATHAN SANTOS", 45, "CAMPINAS", "M"));
this.Add(new Pessoa("IVANILDA RIBEIRO", 29, "AMERICANA", "F"));
this.Add(new Pessoa("BRUNA STEVAN", 28, "AMERICANA", "M"));
this.Add(new Pessoa("ANDREIA MARTINS", 22,
"RIO DE JANEIRO", "F"));
this.Add(new Pessoa("ANTONIO DA SILVA", 30,
"POÇOS DE CALDAS", "M"));
this.Add(new Pessoa("JOSEFINO DANTAS", 48,
"CAMPOS DO JORDÃO", "M"));
this.Add(new Pessoa("HELENA MARIA GOMES", 25,
"SÃO PAULO", "F"));
this.Add(new Pessoa("DJONATHAN MASTRODI", 29,
"PIRACICABA", "M"));
this.Add(new Pessoa("BRUNO MARTINO", 55, "AMERICANA", "M"));
this.Add(new Pessoa("ANA MARIA GONÇALVES", 60,
"PRAIA GRANDE", "F"));
this.Add(new Pessoa("MARCELA MARIA RIBEIRO", 85,
"JOAO PESSOA", "F"));
this.Add(new Pessoa("ISABELLA MENDES", 32, "PIRACICABA", "F"));
this.Add(new Pessoa("IGOR MENDES", 22, "CAMPINAS", "M"));
this.Add(new Pessoa("ANTONIO JOSÉ FARIAS", 21,
"RIO DAS PEDRAS", "M"));
this.Add(new Pessoa("JULIO VIEIRA", 19, "ANDRADINA", "M"));
this.Add(new Pessoa("MARIO RIBEIRO ", 13, "LIMEIRA", "M"));
}
}
Agora que já criamos as classes necessárias, veremos a utilização de lambda com LINQ. A cada exemplo mostraremos a forma com e sem lambda para que você possa ver as diferenças entre os dois tipos. Primeiro vamos utilizar a nossa lista de pessoas, porém deveremos retornar na tela somente as pessoas que a primeira letra do Nome é “A”. Para isso devemos implementar a rotina utilizando Expressões Lambda. Na Listagem 5 você confere o exemplo com lamba e na Listagem 6 sem lambda. Listagem 5. Exemplo de lambda com LINQ lPessoa _lPessoa = new lPessoa();
foreach (var item in _lPessoa.Where
(p=>p.Nome.StartsWith("A")))
{
Console.WriteLine(item.ToString());
}
Listagem 6. Exemplo com LINQ lPessoa _lPessoa = new lPessoa();
var pessoa= from p in _lPessoa
where p.Nome.StartsWith("A")
select p
foreach (var item in pessoa)
{
Console.WriteLine(item.ToString());
}
Agora neste novo exemplo desejamos exibir na tela somente as pessoas que tenham idade entre 20 (vinte) e 30 (trinta) anos. Na Listagem 7 você confere o exemplo com lambda e na Listagem 8 sem lambda. Listagem 7. Segundo exemplo de lambda com LINQ lPessoa _lPessoa = new lPessoa();
foreach (var item in _lPessoa.Where(p=>p.Idade>=20
&& p.Idade<=30))
{
Console.WriteLine(item.ToString());
}
Listagem 8. Segundo exemplo de LINQ lPessoa _lPessoa = new lPessoa();
var pessoa= from p in _lPessoa
where p.Idade>=20 && p.Idade<=30
select p
foreach (var item in pessoa)
{
Console.WriteLine(item.ToString());
}
Utilizando o lambda com o LINQ o código fica bem mais curto, economizando tempo. Com as expressões lambda você conseguirá ser bem mais produtivo.
Como você já deve ter visto até aqui, o LINQ é bastante flexível e pode ser usado em diferentes contextos. Por exemplo, nos links abaixo você pode ver exemplos de utilização desse recurso na leitura de arquivos XML e na persistência de dados:
Utilizando LINQ e Extensions Methods para a leitura de arquivos XML Facebook Twitter (2) (0)
Veja neste artigo como efetuar a leitura de arquivos XML de uma forma mais produtiva, empregando para isto recursos de LINQ to XML e de construções de código conhecidas como Extension Methods. O formato XML (sigla em inglês para “Extensible Markup Language”) surgiu ainda no final dos anos 1990, sendo um padrão aberto voltado à geração de documentos hierárquicos e que conta com uma ampla adesão por parte de sistemas computacionais dos mais variados tipos. Do ponto de vista estrutural, o padrão XML possui vários pontos em comum com a linguagem HTML, estando baseados em marcações (tags). Contudo, diferente de HTML em que há um conjunto pré-definido destes elementos, o formato XML pode ser definido como uma metalinguagem: em termos práticos, isso significa que não existem tags pré-definidas, com esses itens podendo ser definidos conforme necessidades específicas. Um documento XML é formado por um conjunto de elementos (nodes), com as relações de dependência entre estes elementos sendo estabelecidas por meio de níveis hierárquicos. Todo elemento é constituído por uma tag e seu respectivo conteúdo. Quanto àquilo que consta em um elemento, o mesmo pode estar vazio, possuir atributos ou ainda, conter um agrupamento de outros elementos. Já um atributo nada mais é do que um par formado por um nome identificador e um valor correspondente (este último entre aspas), com estes dois itens separados por um sinal de “=” (“igual”), além de terem sido declarados dentro da tag que define um elemento. A estrutura flexível oferecida pelo padrão XML é utilizada em larga escala em processos de integração entre sistemas via Web Services, no conteúdo de sites da Internet, em dispositivos móveis etc. Atualmente, este formato é suportado pelas
principais linguagens de programação e sistemas operacionais, existindo diversas técnicas para a manipulação de dados neste padrão. Considerando o caso específico da plataforma .NET, diversos métodos permitem a implementação de funcionalidades baseadas na leitura e/ou escrita de informações no padrão XML. LINQ to XML é uma dessas tecnologias, oferecendo vários meios que viabilizam a realização de operações sobre dados hierárquicos. Além daquilo que é oferecido nativamente pela extensão LINQ to XML, outras funcionalidades podem ainda ser implementadas com base neste mecanismo, sem que isto implique necessariamente em herdar classes pré-existentes a fim de incluir ou, até mesmo, redefinir comportamentos: isto é possível graças ao uso de construções de código conhecidas como "Extension Methods". O objetivo deste artigo é discutir como Extension Methods podem ser combinados a expressões que envolvam o uso de LINQ to XML, simplificando assim o processo de leitura de informações contidas em documentos XML.
Conteúdo do arquivo XML utilizado nos exemplos Na Listagem 1 é apresentado o conteúdo do arquivo XML (Prestadores.xml) em que se baseiam os exemplos apresentados mais adiante. No arquivo “Prestadores.xml” constam dados de prestadores de serviço contratados junto a uma hipotética Consultoria de Tecnologia. É possível constatar neste documento XML a presença dos seguintes campos:
ID: Número inteiro que identifica um prestador de serviços; CPF: CPF do profissional; NomeProfissional: nome do prestador de serviços; Empresa: empresa da qual o prestador é proprietário; CNPJ: CNPJ da empresa prestadora de serviços; Cidade: cidade em que está aberta a empresa prestadora; Estado: estado da empresa prestadora; InscricaoEstadual: inscrição estadual da empresa prestadora de serviços (o conteúdo deste campo é opcional); ValorHora: valor recebido por hora trabalhada (preenchido quando o prestador de serviços ser pago de acordo como o número de horas trabalhadas); ValorMensal: valor recebido mensalmente (preenchido quando o prestador de serviços for pago de acordo com um valor fixo mensal); DataCadastro: data em que foi efetuado o cadastro do prestador de serviços; InicioAtividades: data em que o prestador de serviços iniciou suas atividades (o conteúdo deste campo é opcional).
Listagem 1: Arquivo Prestadores.xml
131
111.111.111-11
JOÃO DA SILVA
SILVA CONSULTORIA EM INFORMÁTICA LTDA
11.111.111/1111-11
São Paulo
SP
1111-1
50,00
03/01/2013
03/01/2013
132
222.222.222-22
JOAQUIM DE OLIVEIRA
SERVIÇOS DE TECNOLOGIA OLIVEIRA ME
22.222.222/2222-22
Belo Horizonte
MG
10527,35
03/01/2013
133
333.333.333-33
MARIA MARTINS
MARTINS TECNOLOGIA LTDA
33.333.333/3333-33
Rio de Janeiro
RJ
33333
65,00
04/01/2013
10/01/2013
134
444.444.444-44
JOANA SANTOS
CONSULTORIA SANTOS LTDA
44.444.444/4444-44
São Paulo
SP
8474,77
04/01/2013
Definindo a classe que corresponde ao conteúdo do arquivo XML de Prestadores A Listagem 2 apresenta a implementação da classe Prestador. Instâncias deste tipo equivalem, basicamente, aos dados contidos em cada elemento “Prestador” do documento XML de exemplo. É possível notar ainda que campos opcionais dos tipos decimal e DateTime estão com um sinal de interrogação (“?”). Este recurso é conhecido como Nullable, permitindo a propriedades e variáveis que o utilizam armazenar também o valor null, por mais que estas tenham sido definidas a partir de um tipo primitivo (decimal) ou estruturado (DateTime). Sem o uso de uma Nullable tais referências assumiriam, se nenhum valor fosse associado às mesmas, o valor default para seus respectivos tipos (o número zero no caso de um decimal). Listagem 2: Classe Prestador using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TesteExtensionMethodsXML
{
public class Prestador
{
public int ID { get; set; }
public string CPF { get; set; }
public string NomeProfissional { get; set; }
public string Empresa { get; set; }
public string CNPJ { get; set; }
public string Cidade { get; set; }
public string Estado { get; set; }
public string InscricaoEstadual { get; set; }
public decimal? ValorHora { get; set; }
public decimal? ValorMensal { get; set; }
public DateTime DataCadastro { get; set; }
public DateTime? InicioAtividades { get; set; }
}
}
Implementando a leitura do documento XML via LINQ to XML A classe estática ArquivoPrestadores (Listagem 3) representa a estrutura responsável por efetuar a leitura do arquivo XML, a fim de com isto gerar uma coleção de objetos do tipo Prestador. O processamento do documento XML e a posterior geração de instâncias da classe Prestador acontece através do método CarregarInformacoes:
Inicialmente é gerada uma nova instância de XDocument (namespace System.Xml.Linq), a partir de uma chamada ao método Load, passando-se como parâmetro a este o nome do arquivo XML a ser carregado (parâmetro “arquivo”); Prossegue-se agora com a geração das instâncias baseadas na classe Prestador. Isso é feito através de um loop foreach, acionando-se para isto o método Element a partir da instância do tipo XDocument; esta primeira ação irá selecionar o node “Prestadores”. Em seguida, invoca-se o método Elements, o qual irá retornar como resultado uma coleção de referências da classe XElement (namespace System.Xml.Linq) e que
correspondem aos elementos de nome “Prestador” (subordinados ao node principal “Prestadores”); A cada iteração do loop foreach é criada uma nova referência do tipo Prestador, atribuídos valores às propriedades deste objeto a partir de elementos presentes no node “Prestador” e, por fim, adicionada a instância em questão à coleção “prestadores” (a qual é retornada posteriormente como resultado da execução do método CarregarInformacoes); A leitura de cada elemento pertencente ao node “Prestador” se faz por meio do método Element, a partir da instância de XElement (variável “dadosPrestador”) declarada no início do loop foreach. Para isto, são invocados o método Element de “dadosPrestador” e, na sequência, a propriedade Value. Conforme é possível observar, o retorno da propriedade Value é sempre uma string, o que força a realização de procedimentos de conversão de um tipo para outro; além disso, verificações adicionais são realizadas quando se estiver manipulando um campo cujo conteúdo é opcional (caso das propriedades InscricaoEstadual, ValorHora, ValorMensal, DataCadastro e InicioAtividades).
Listagem 3: Classe ArquivoPrestadores using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
namespace TesteExtensionMethodsXML
{
public static class ArquivoPrestadores
{
public static List CarregarInformacoes(
string arquivo)
{
List prestadores = new List();
Prestador prestador;
string strInscrEstadual;
string strValorHora;
decimal valorHora;
string strValorMensal;
decimal valorMensal;
string strDataCadastro;
DateTime dataCadastro;
string strInicioAtividades;
DateTime inicioAtividades;
XDocument xml = XDocument.Load(arquivo);
foreach (XElement dadosPrestador in
xml.Element("Prestadores").Elements("Prestador"))
{
prestador = new Prestador();
prestador.ID = Convert.ToInt32(dadosPrestador
.Element("ID").Value);
prestador.CPF = dadosPrestador
.Element("CPF").Value;
prestador.Empresa = dadosPrestador
.Element("Empresa").Value;
prestador.NomeProfissional = dadosPrestador
.Element("NomeProfissional").Value;
prestador.CNPJ = dadosPrestador
.Element("CNPJ").Value;
prestador.Cidade = dadosPrestador
.Element("Cidade").Value;
prestador.Estado = dadosPrestador
.Element("Estado").Value;
strInscrEstadual = dadosPrestador
.Element("InscricaoEstadual").Value;
if (!String.IsNullOrWhiteSpace(strInscrEstadual))
prestador.InscricaoEstadual =
strInscrEstadual;
strValorHora = dadosPrestador.
Element("ValorHora").Value;
if (!String.IsNullOrWhiteSpace(strValorHora) &&
decimal.TryParse(strValorHora,
out valorHora))
prestador.ValorHora = valorHora;
strValorMensal =
dadosPrestador.Element("ValorMensal").Value;
if (!String.IsNullOrWhiteSpace(strValorMensal) &&
decimal.TryParse(strValorMensal,
out valorMensal))
prestador.ValorMensal = valorMensal;
strDataCadastro = dadosPrestador
.Element("DataCadastro").Value;
if (!String.IsNullOrWhiteSpace(strDataCadastro) &&
DateTime.TryParse(strDataCadastro,
out dataCadastro))
prestador.DataCadastro = dataCadastro;
strInicioAtividades = dadosPrestador
.Element("InicioAtividades").Value;
if (!String.IsNullOrWhiteSpace(
strInicioAtividades) &&
DateTime.TryParse(strInicioAtividades,
out inicioAtividades))
prestador.InicioAtividades = inicioAtividades;
prestadores.Add(prestador);
}
return prestadores;
}
}
}
Já na Listagem 4 está um exemplo de trecho de código que emprega a classe ArquivoPrestadores, fornecendo para isto o caminho em que se encontra o arquivo “Prestadores.xml”.
Listagem 4: Exemplo de uso da classe ArquivoPrestadores ...
List prestadores = ArquivoPrestadores
.CarregarInformacoes(@"C:\Devmedia\Prestadores.xml");
...
Por mais que a implementação da classe ArquivoPrestadores seja simples e não exija grandes esforços, a leitura e a conversão de valores originários de um arquivo XML pode gerar trechos extensos de código, bem como se repetir ao longo de uma aplicação. É neste ponto que o recurso conhecido como Extension Methods pode se revelar como extremamente útil, conforme será demonstrado na próxima seção. Este mecanismo possibilita não apenas um código mais enxuto, como também facilitando o uso de funcionalidades básicas para tratamento de dados no formato XML.
Utilizando Extension Methods O recurso conhecido como Extension Methods foi disponibilizado a partir da versão 3.5 do .NET Framework. Em termos práticos, um Extension Method nada mais é do que um método estático, muito embora um desenvolvedor possa enxergar o mesmo como se fosse uma operação acessível a partir da instância de uma determinada classe. Este tipo de construção evita a necessidade de se gerar uma nova classe, servindo como um meio para “adicionar” novas funcionalidades a um recurso já existente. O próprio .NET Framework emprega de forma extensiva essa prática: LINQ é um bom exemplo de mecanismo que faz uso de Extension Methods, de maneira a facilitar com isto a manipulação de coleções de objetos através de métodos como Where e OrderBy. Na Listagem 5 encontra-se a implementação da classe estática LinqToXmlExtensions. Este tipo foi estruturado da seguinte forma:
O método estático GetStringValue recebe como parâmetros uma instância do tipo XElement, além do nome de um node que pertença a este elemento. Caso tal node exista e possua um valor associado ao mesmo, será retornada a string correspondente; do contrário, o método GetStringValue devolverá como resultado de sua execução o valor null;
As operações estáticas StringValue, IntValue, DecimalValue e DateTimeValue representam exemplos de Extension Methods, adicionando novas funcionalidades ao tipo XElement, sem porém exigir a necessidade de se gerar uma nova classe a partir do mesmo. Todos esses métodos recebem como parâmetro uma instância da classe XElement precedida pela palavra-chave “this” (isto é uma característica típica de um Extension Method), além de um segundo parâmetro com o nome do node a ser lido; Os métodos StringValue, IntValue, DecimalValue e DateTimeValue acionam a operação GetStringValue, manipulando os valores fornecidos por esta última e devolvendo resultados baseados em seus respectivos tipos de retorno. No caso específico de IntValue, DecimalValue e DateTimeValue isto envolve a utilização de valores Nullable e da classe Convert em transformações de dados.
Listagem 5: Classe LinqToXmlExtensions using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace TesteExtensionMethodsXML
{
public static class LinqToXmlExtensions
{
private static string GetStringValue(
XElement element, string node)
{
string valor = null;
XElement childElement = element.Element(node);
if (childElement != null)
{
valor = childElement.Value;
if (!String.IsNullOrWhiteSpace(valor))
valor = valor.Trim();
else
valor = null;
}
return valor;
}
public static string StringValue(
this XElement element, string node)
{
return GetStringValue(element, node);
}
public static int? IntValue(
this XElement element, string node)
{
string valor = GetStringValue(element, node);
if (valor != null)
return Convert.ToInt32(valor);
else
return null;
}
public static decimal? DecimalValue(
this XElement element, string node)
{
string valor = GetStringValue(element, node);
if (valor != null)
return Convert.ToDecimal(valor);
else
return null;
}
public static DateTime? DateTimeValue(
this XElement element, string node)
{
string valor = GetStringValue(element, node);
if (valor != null)
return Convert.ToDateTime(valor);
else
return null;
}
}
}
A simples definição da classe LinqToXmlExtensions em um projeto de testes fará com que o próprio IntelliSense do Visual Studio liste os métodos que estendem uma classe. É o que pode ser observado na Figura 1 com o método IntValue ou ainda, com a operação StringValue conforme demonstrado na Figura 2.
Figura 1: Selecionando o método IntValue através do IntelliSense
Figura 2: Selecionando o método StringValue através do IntelliSense Na Listagem 6 são apresentadas algumas instruções da classe ArquivoPrestador já modificadas. Nota-se que o uso dos métodos IntValue, StringValue, DecimalValue e DateTimeValue simplificou consideravelmente a implementação do método CarregarInformacoes. Listagem 6: Utilizando Extension Methods em instruções da classe ArquivoPrestador
...
prestador.ID =
dadosPrestador.IntValue("ID").Value;
prestador.CPF =
dadosPrestador.StringValue("CPF");
prestador.Empresa =
dadosPrestador.StringValue("Empresa");
...
prestador.InscricaoEstadual =
dadosPrestador.StringValue("InscricaoEstadual");
prestador.ValorHora =
dadosPrestador.DecimalValue("ValorHora");
prestador.ValorMensal =
dadosPrestador.DecimalValue("ValorMensal");
prestador.DataCadastro =
dadosPrestador.DateTimeValue("DataCadastro").Value;
prestador.InicioAtividades =
dadosPrestador.DateTimeValue("InicioAtividades");
...
Já a Listagem 7 demonstra outra implementação possível para a classe ArquivoPrestadores, combinando para isto o uso de uma consulta LINQ em conjunto com as classes XDocument e XElement, além dos Extension Methods definidos anteriormente através da classe LinqToXmlExtensions. Esta nova versão da classe ArquivoPrestadores constitui um bom exemplo de como o uso de Extension Methods pode contribuir para uma codificação menos extensa. Por meio deste recurso conseguiu-se, basicamente, evitar a escrita de longos trechos de código visando a leitura e o tratamento de dados no formato XML. Importante frisar que os métodos definidos em LinqToXmlExtensions também poderiam ser facilmente reutilizados em outros pontos de uma aplicação hipotética. Listagem 7: Classe ArquivoPrestadores modificada using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
namespace TesteExtensionMethodsXML
{
public static class ArquivoPrestadores
{
public static List CarregarInformacoes(
string arquivo)
{
XDocument xml = XDocument.Load(arquivo);
return (from XElement dadosPrestador in xml
.Element("Prestadores").Elements("Prestador")
select new Prestador()
{
ID = dadosPrestador
.IntValue("ID").Value,
CPF = dadosPrestador
.StringValue("CPF"),
Empresa = dadosPrestador
.StringValue("Empresa"),
NomeProfissional = dadosPrestador
.StringValue("NomeProfissional"),
CNPJ = dadosPrestador
.StringValue("CNPJ"),
Cidade = dadosPrestador
.StringValue("Cidade"),
Estado = dadosPrestador
.StringValue("Estado"),
InscricaoEstadual = dadosPrestador
.StringValue("InscricaoEstadual"),
ValorHora = dadosPrestador
.DecimalValue("ValorHora"),
ValorMensal = dadosPrestador
.DecimalValue("ValorMensal"),
DataCadastro = dadosPrestador
.DateTimeValue("DataCadastro").Value,
InicioAtividades = dadosPrestador
.DateTimeValue("InicioAtividades")
}).ToList();
}
}
}
Conclusão Procurei com este artigo demonstrar como LINQ e o recurso conhecido como Extension Methods pode simplificar bastante a manipulação de arquivos XML. A definição de
métodos que estendam as capacidades de tipos como XElement evita não apenas a criação de diversas subclasses (algo que provavelmente aumentaria a complexidade de um projeto), como também possibilita o reuso de determinadas funcionalidades por toda uma aplicação (ou mesmo em diversas soluções de software).
Usando Expressões Lambda com LINQ Facebook Twitter (8) (0)
Neste artigo vamos falar um pouco sobre Expressões lambda, veja o que são e como funcionam. Esse artigo abrange também a utilização de lambda com LINQ. Uma expressão Lambda é uma função anônima que você pode usar para criar Delegates ou tipos de árvores de Expressão. São particularmente muito úteis para escrever consultas LINQ. Para facilitar o entendimento, a expressão lambda é uma espécie de função, porém sem nome. Ela realiza cálculos, filtros e retorna valores ou coleções de valores. O símbolo “=>” é o operador Lambda, também conhecido como vai para (goes to), e toda expressão precisa dele. Ao criar uma você, devem-se colocar os parâmetros a esquerda do operador (caso haja parâmetros) e do outro lado do bloco coloca-se a expressão ou instrução do. Esse tipo de expressão pode ser atribuído a Delegates. Abaixo veremos alguns exemplos de expressões lambda para facilitar o entendimento. Na Listagem 1 um array numérico e deveremos saber quantos números pares tem nesse array. Listagem 1. Exemplo Expressão lambda static void Main(string[] args)
{
//Criamos uma array de numeros
int[] numeros = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//utilizamos a função cout com uma expressão lambda como parametro
int qtdPares = numeros.Count(x => x % 2 == 0); // qtdPares = 5
Console.WriteLine("Quantidades de Numeros Pares: {0}",
qtdPares.ToString());
Console.ReadKey();
}
Agora veremos um exemplo criando um delegate na Listagem 2. Imagine que você tem uma situação onde precise passa um valor e receber o quadrado dele. Listagem 2. Exemplo Expressão lambda com Delegate delegate float deleg(float i);
static void Main(string[] args)
{
//Criamos uma variavel para receber um valor digitado pelo usuário
float I = float.Parse(Console.ReadLine());
//Criamos uma variavel do tipo delegate que recebe
//a expressão lambda
deleg _Delegate = x => x * x;
float j = _Delegate(I); //j = 25
Console.WriteLine("O Quadrado de {0} é {1}",
I.ToString(), j.ToString());
Console.ReadKey();
}
Porque usar Expressões lambda? Devemos usar as Expressões lambdas para deixar seu código muito mais simples e legível, tornando-o mais agradável e elegante, principalmente na forma como embarca métodos anônimos ao código.
Expressões lambda com LINQ Bom agora que já obtivemos uma breve explicação sobre expressões lambda, vamos mostrar sua utilização com LINQ. LINQ é uma linguagem de consulta que foi introduzida a partir do .Net Framewok 3.5 e é utilizada na manipulação de coleções, sejam elas vindas de um banco de dados, documentos XML, coleções de objetos, etc. Assim, podemos definir o LINQ como uma consulta que não tem dependência da fonte de dados. Agora vamos criar um cenário para que possamos demonstrar a utilização do LINQ e a melhora quando utilizamos lambda junto a ele. Vamos criar uma lista do tipo pessoa; pessoa é um tipo de dado com as propriedades Nome, Idade, Cidade e Sexo.
Para montarmos esse exemplo, Abra o Visual Studio e crie um novo projeto clicando em File -> New -> Project, conforme a Figura 1.
Figura 1. Criando o projeto. Na janela seguinte digite “empty” na caixa de pesquisa e selecione BlankSolution. Altere a propriedade Name para Lambda ou para o nome que preferir, conforme mostra a Figura 2.
Figura 2. Novo projeto em Branco. Se olharmos no Solution Explorer veremos que foi criada uma solução vazia, então agora clique com o botão direito do mouse e escolha Add... e em seguida New Project. Crie um projeto do Tipo Console Application e altere a propriedade Name para Lambda, conforme demonstrado nas Figuras 3 e 4.
Figura 3. Adicionando um novo projeto a Solution.
Figura 4. Criando o projeto Console Application.
Agora vá até o Solution explorer e clique com o botão direito do mouse sobre o projeto Console Application. Escolha a opção Add -> New Item e na janela que irá se abrir escolha a opção Class, alterando a propriedade Name para Pessoa.cs. Agora vamos implementar a classe pessoa, onde a mesma deve ter as propriedades Nome, Idade, Cidade e Sexo e um construtor que recebe essas propriedades, conforme a Listagem 3. Listagem 3. Criando Classe Pessoa class Pessoa
{
public String Nome { get; set; }
public int Idade { get; set; }
public String Cidade { get; set; }
public String Sexo { get; set; }
public Pessoa(String _Nome, int _Idade,
String _Cidade, String _Sexo)
{
Nome = _Nome;
Idade = _Idade;
Cidade = _Cidade;
Sexo = _Sexo;
}
public override String ToString()
{
return "Nome: " + Nome + " Idade: " +
Idade.ToString() + " Cidade: " + Cidade + " Sexo: " + Sexo;
}
}
No mesmo arquivo Pessoa.cs vamos também implementar uma classe que é uma lista de pessoa. Essa classe conterá apenas um construtor sem parâmetros que servirá para alimentarmos nossa lista de pessoas, ou seja, ao instanciarmos essa lista ela já estará populada. Observe a Listagem 4. Listagem 4. Alimentando a Lista de pessoas class lPessoa : List
{
public lPessoa()
{
this.Add(new Pessoa("JOSÉ SILVA", 21, "PIRACICABA", "M"));
this.Add(new Pessoa("ADRIANA GOMES", 18, "CURITIBA", "F"));
this.Add(new Pessoa("JOSÉ NOVAES", 17, "CAMPINAS", "M"));
this.Add(new Pessoa("ANDRÉ NEVES", 50, "PIRACICABA", "M"));
this.Add(new Pessoa("JONATHAN SANTOS", 45, "CAMPINAS", "M"));
this.Add(new Pessoa("IVANILDA RIBEIRO", 29, "AMERICANA", "F"));
this.Add(new Pessoa("BRUNA STEVAN", 28, "AMERICANA", "M"));
this.Add(new Pessoa("ANDREIA MARTINS", 22,
"RIO DE JANEIRO", "F"));
this.Add(new Pessoa("ANTONIO DA SILVA", 30,
"POÇOS DE CALDAS", "M"));
this.Add(new Pessoa("JOSEFINO DANTAS", 48,
"CAMPOS DO JORDÃO", "M"));
this.Add(new Pessoa("HELENA MARIA GOMES", 25,
"SÃO PAULO", "F"));
this.Add(new Pessoa("DJONATHAN MASTRODI", 29,
"PIRACICABA", "M"));
this.Add(new Pessoa("BRUNO MARTINO", 55, "AMERICANA", "M"));
this.Add(new Pessoa("ANA MARIA GONÇALVES", 60,
"PRAIA GRANDE", "F"));
this.Add(new Pessoa("MARCELA MARIA RIBEIRO", 85,
"JOAO PESSOA", "F"));
this.Add(new Pessoa("ISABELLA MENDES", 32, "PIRACICABA", "F"));
this.Add(new Pessoa("IGOR MENDES", 22, "CAMPINAS", "M"));
this.Add(new Pessoa("ANTONIO JOSÉ FARIAS", 21,
"RIO DAS PEDRAS", "M"));
this.Add(new Pessoa("JULIO VIEIRA", 19, "ANDRADINA", "M"));
this.Add(new Pessoa("MARIO RIBEIRO ", 13, "LIMEIRA", "M"));
}
}
Agora que já criamos as classes necessárias, veremos a utilização de lambda com LINQ. A cada exemplo mostraremos a forma com e sem lambda para que você possa ver as diferenças entre os dois tipos. Primeiro vamos utilizar a nossa lista de pessoas, porém deveremos retornar na tela somente as pessoas que a primeira letra do Nome é “A”. Para isso devemos implementar a rotina utilizando Expressões Lambda. Na Listagem 5 você confere o exemplo com lamba e na Listagem 6 sem lambda. Listagem 5. Exemplo de lambda com LINQ lPessoa _lPessoa = new lPessoa();
foreach (var item in _lPessoa.Where
(p=>p.Nome.StartsWith("A")))
{
Console.WriteLine(item.ToString());
}
Listagem 6. Exemplo com LINQ lPessoa _lPessoa = new lPessoa();
var pessoa= from p in _lPessoa
where p.Nome.StartsWith("A")
select p
foreach (var item in pessoa)
{
Console.WriteLine(item.ToString());
}
Agora neste novo exemplo desejamos exibir na tela somente as pessoas que tenham idade entre 20 (vinte) e 30 (trinta) anos. Na Listagem 7 você confere o exemplo com lambda e na Listagem 8 sem lambda. Listagem 7. Segundo exemplo de lambda com LINQ lPessoa _lPessoa = new lPessoa();
foreach (var item in _lPessoa.Where(p=>p.Idade>=20
&& p.Idade<=30))
{
Console.WriteLine(item.ToString());
}
Listagem 8. Segundo exemplo de LINQ lPessoa _lPessoa = new lPessoa();
var pessoa= from p in _lPessoa
where p.Idade>=20 && p.Idade<=30
select p
foreach (var item in pessoa)
{
Console.WriteLine(item.ToString());
}
Utilizando o lambda com o LINQ o código fica bem mais curto, economizando tempo. Com as expressões lambda você conseguirá ser bem mais produtivo.
Threads Por meio de threads uma aplicação pode executar diferentes tarefas de forma concorrente, evitando o sobrecarregamento do fluxo principal do programa. Por exemplo, por meio de threads podemos executar um procedimento pesado em segundo plano, deixando a interface com o usuário livre para interações. Para dar os primeiros passos com threads em C# você pode ver o seguinte artigo:
Iniciando com Threads no C# - Revista easy .Net Magazine 25 Facebook Twitter (0) (0)
O artigo trata de como utilizar threads no C#, tornando possível que sua aplicação execute “N” tarefas ao mesmo tempo. Desta forma, um processo não irá depender de outro para ser executado, por exemplo. Do que se trata o artigo
O artigo trata de como utilizar threads no C#, tornando possível que sua aplicação execute “N” tarefas ao mesmo tempo. Desta forma, um processo não irá depender de outro para ser executado, por exemplo. Em que situação é útil
A conexão com um banco de dados pode ser um processo demorado e em alguns casos este processo é realizado na inicialização de uma aplicação. Esta demora pode causar ao usuário a impressão de que a aplicação esteja “travada”. Neste contexto, uma forma de evitar esta situação é utilizar uma thread para realizar este processo de conexão, permitindo que a aplicação trabalhe demais processos enquanto a conexão não é concluída, por exemplo, gerar e exibir formulários da aplicação. Esse é um dos exemplos em que as threads podem ser utilizadas. Resumo do DevMan O suporte a threads no contexto de aplicações (softwares) é um recurso fornecido pelo próprio Sistema Operacional. Uma thread é responsável por executar tarefas dentro de uma aplicação. O conceito de multithreading é aplicado quando estas tarefas são divididas em duas ou mais threads, buscando diminuir o tempo de processamento destas tarefas. O .NET oferece um componente que manipula threads dentro de uma aplicação, chamado BackgroundWorker. Este artigo introduz o conceito de threads e apresenta uma implementação para conexão com o banco de dados, utilizando uma thread, por meio do componente BackgroundWorker.,br>Autores: Everton Coimbra e Giuvane Conti Threads refere-se à técnica em que a execução de um determinado processo (tarefa) é dividido em dois ou mais processos, permitindo a execução de diversas tarefas de maneira concorrente. O conceito de multithreading apresenta um modelo de programação que permite executar múltiplas tarefas, compartilhando recursos de um processo e executando de forma independente. Logo, o programador deve evitar que duas ou mais threads operem em um mesmo recurso (arquivo, endereço de memória ou conexão de rede) Um sistema que utiliza multithreading possui melhor desempenho em computadores com múltiplos núcleos de processamento, pois esses núcleos permitem que as tarefas de cada thread sejam executadas de forma simultânea. Este escalonamento de tarefas (multithreads) permite que threads trabalhem de maneira simultânea e independente, logo, o programador deve evitar que duas ou mais threads operem em um mesmo recurso (arquivo, endereço de memória ou conexão de rede). Caso aconteça a aplicação apresentará um erro conhecido como deadlock. Theads devem ser programadas para cooperar e não competir. O escalonamento (Nota do DevMan 1) entre threads segue a mesma lógica do escalonamento entre processos. Um método que busca evitar situações de deadlock é conhecido como Thread safety. Este conceito será abordado no decorrer do artigo. Nota do DevMan 1
Escalonamento de processos é uma atividade organizacional feita pelo escalonador da CPU. O escalonador escolhe o processo que tem mais prioridade e menos tempo e coloca-o na memória principal, desta maneira o processador evita ficar ocioso. Para que o multithreading seja manipulado corretamente, as linhas de execução devem ser sincronizadas para que os dados sejam processados na ordem correta. Desta maneira trabalhar com este conceito requer muito cuidado na implementação, pois todas as threads que estão sendo manipuladas dentro devem funcionar muito bem
sincronizadas, principalmente se estiverem trabalhando com variáveis dependentes. Sendo assim, deve ser respeitada a ordem em que foram escalonadas, para evitar um possível deadlock. Deadlock é caracterizado por um impasse entre dois ou mais processos, sendo assim impedidos de continuar suas execuções, ou seja, bloqueados. Para exemplificar threads, ao realizar um Ctrl + Shift + Del em seu computador e acessando a opção “Iniciar o Gerenciador de tarefas” é possível notar que o sistema operacional Windows trabalha com várias tarefas ao mesmo tempo, como pode ser visto na Figura 1.
Figura 1. Gerenciador de tarefa do MS Windows. Em uma situação onde um determinado processamento seja realizado por um processador de cinco núcleos, em teoria, o processador seria capaz de realizar cinco aplicações ao mesmo tempo, porém, existem outros diversos aplicativos em execução. Isso é possível porque o processador consegue trabalhar com todos os aplicativos e apresentar resultados satisfatórios devidos à velocidade de seu processamento. Sendo assim os aplicativos parecem estar em execução simultaneamente. Assim como os processos, as threads possuem estados em seu ciclo de vida, dentro de um sistema as ações e ordens de uma thread são controladas por uma tabela
de threads que armazena informações de seu fluxo de execução, o nome desta tabela é Thread TCB (Task Control Block) e a mesma contêm: • O endereço da pilha; • O contador de programa; • Registrador de instruções • Registradores de dados, endereços, flags; • Endereços das threads filhas; • Estado de execução. A comunicação entre threads pode ser realizada por meio de variáveis globais do processo que as criou. Existem técnicas que podem ser utilizadas para realizar o sincronismo das mesmas, como monitores e semáforos, que possuem recursos capazes de indicar a ordem de execução dos processos e são utilizadas para evitar condições concorrência. A "
Como você deve ter visto, com threads conseguimos implementar processamento assíncrono e paralelo, o que pode nos garantir ganho de desempenho e manter a aplicação funcional durante processamentos pesados. Para saber mais sobre programação assíncrona e paralela em C#, confira os conteúdos abaixo:
Programação Assíncrona (Multithreading) em .NET com C# Facebook Twitter (9) (0)
Veja neste artigo o que é, quando e como utilizar multithread. Thread é uma forma de um processo dividir a si mesmo em duas ou mais tarefas, podendo executar elas concorrentemente. O suporte a threads é oferecido pelos Sistemas Operacionais, ou por bibliotecas de algumas linguagens de programação. 1. Overview sobre Multithreads 1.1 O que são Threads? Thread é uma forma de um processo dividir a si mesmo em duas ou mais tarefas, podendo executar elas concorrentemente. O suporte a threads é oferecido pelos Sistemas Operacionais, ou por bibliotecas de algumas linguagens de programação 1.2 O que são Aplicações Multithreads? Um programa single - thread (thread única) inicia na etapa 1 e continua seqüencialmente (etapa 2, etapa 3, o passo 4) até atingir a etapa final. Aplicações multithread permitem que você execute várias threads ao mesmo tempo, cada uma executando um passo por exemplo. Cada thread é executada em seu próprio processo, então, teoricamente, você pode executar o passo 1 em uma thread e, ao mesmo tempo executar o passo 2 em outra thread e assim por diante. Isso significa que a etapa 1, etapa 2, etapa 3 e etapa 4 podem ser executadas simultaneamente. Na teoria, se todos os quatro passos durassem o mesmo tempo para executar, você poderia terminar o seu programa em um quarto do tempo que levaria para executar os quatro passos sequencialmente. Então, por que todos os programas não são multithread? Uma das razões nos diz que junto com a velocidade vem a complexidade. Controlar um grande número de threads requer bastante trabalho e conhecimento. Imagine se um passo de alguma forma dependesse das informações no passo 2. O programa pode não funcionar corretamente se o passo 1 termina cálculo antes do passo 2 ou vice-versa. Para ajudar entender essa complexidade, vamos fazer uma analogia comparando um programa multithread com o corpo humano. Cada um dos órgãos do corpo (coração, pulmões, fígado, cérebro) são processos e cada processo é executado simultaneamente. Assim, o corpo humano seria como uma grande aplicação multithread. Os órgãos são os processos em execução simultânea, eles dependem uns dos outros, sendo que todos os processos se comunicam através de sinais nervosos e fluxo de sangue, desencadeando uma série de reações químicas. Como em todos os aplicativos multithread, o corpo humano é muito complexo. Se algum processo não obtiver informações de outros processos, ou um processo retardar ou acelerar, vamos acabar com um problema médico. É por isso que, esses processos precisam ser sincronizados corretamente para funcionar normalmente.
1.3 Quando Utilizar Multithread? Quando precisamos de desempenho e eficiência. Por exemplo, vamos pensar em um jogo de vídeo game. Imagine um jogo possuindo uma única thread, tanto para o processamento de imagens quanto para o processamento de áudio. Seria possível? Sim, seria. Mas esse é o cenário ideal para garantir a performance? Não. O ideal seria criar threads para o processamento das rotinas de imagens e outra para rotinas de áudio. Outro cenário comum onde você precisaria de Multithread seria um sistema de tratamento de mensagens. Imagine um aplicativo que captura milhares de mensagens simultaneamente. Você não pode capturar eficientemente uma série de mensagens ao mesmo tempo em que você está fazendo algum outro processamento pesado, porque senão você pode perder mensagens. Então cabe aqui dividir o processamento de captura e processamentos paralelos em threads diferentes. Cada um destes cenários são usos comuns para multithread e se utilizado de maneira correta, essa técnica vai melhorar significativamente o desempenho de aplicações similares. Em linhas gerais, os benefícios do uso das threads advém do fato do processo poder ser dividido em várias threads; quando uma thread está à espera de determinado dispositivo de entrada/saída ou qualquer outro recurso do sistema, o processo como um todo não fica parado, pois quando uma thread entra no estado de 'bloqueio', uma outra thread aguarda na fila. 1.4 Quando não utilizar Multithread? Geralmente quando aprendemos os recursos de multithread, ficamos fascinados com todas as possibilidades que essa técnica nos oferece e saímos usando – a em todos os tipos de programas que vemos pela frente. É preciso tomar muito cuidado com isso, pois ao contrário de um programa single thread, você está lidando com muitos processos ao mesmo tempo, com múltiplas variáveis ??dependentes. Isso pode se tornar muito complicado de se controlar. Pensemos em multithread, um malabarismo. Malabarismos com uma única bola em sua mão (embora meio chato) é bastante simples. No entanto, se você é desafiado a colocar duas dessas bolas no ar, a tarefa é um pouco mais difícil. Imagine três, quatro, cinco bolas então, e as coisas vão se tornar cada vez mais difíceis. Como a contagem de bola aumenta você tem chances cada vez maiores de deixar cair alguma bola. Trabalhar com multithreads requer muito trabalho e concentração. É preciso ressaltar que o uso dessa técnica em sistemas simples, geralmente não nos leva a um cenário muito proveitoso. Mesmo que o programador seja um cara muito bom e desenhe direitinho o sistema utilizando threads, fatalmente o ganho em performance não irá compensar caso seja necessário dar manutenções futuras no sistema. 2. Como Utilizar Threads 2.1 Criando uma Multithread
Dentro do .Net, para criar uma thread devemos utilizar o Namespace System.Threading. Nesta Namespace se encontra a classe Thread, que é a que usamos para instanciar uma thread. No exemplo abaixo, veremos como instanciar uma thread e como rodá – la. Antes de tudo, uma thread sempre realiza determinada tarefa codificada em um método. Sendo assim, primeiramente vamos definir um método estático que realizará alguns prints em uma aplicação de Console: Lembrando que todos os exemplos citados aqui foram feitos em C#, no Visual Studio 2008, utilizando um projeto do tipo Console Application. public static void run()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Thread Atual - " + Thread.CurrentThread.Name + i);
Thread.Sleep(1000);
}
}
Este método apenas escreve na tela o nome da thread atual dentro de um loop for. Repare que no código abaixo este método será utilizado para instanciar uma thread, pois para criar threads, precisamos apontar o método que ela executará, passando como parâmetro ao construtor da thread um objeto ThreadStart, que recebe o nome do método a ser executado pela thread. Agora no método Main do seu Console Application, digite as seguintes linhas de código: static void Main(string[] args)
{
Console.WriteLine("Thread principal iniciada");
Thread.CurrentThread.Name = "Principal - ";
Thread t1 = new Thread(new ThreadStart(run));
t1.Name = "Secundária - ";
t1.Start();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("Thread atual
- " + Thread.CurrentThread.Name
+ i);
Thread.Sleep(1000);
}
Console.WriteLine("Thread Principal terminada");
Console.Read();
}
No código acima vemos como instanciar uma thread e como iniciá-la: Thread t1 = new Thread(new ThreadStart(run));
t1.Name = "Secundária - ";
t1.Start();
Depois fazemos o mesmo loop do método run, dentro do método main para que os dois métodos rodem em concorrência. Observe a chamada do método Sleep da classe Thread. Esse método suspende a execução da thread no período especificado como parâmetro em milissegundos. Ao executar nosso Console Application temos o seguinte output:
2.2 Sincronização de Threads O maior problema ao usar múltiplas threads é organizar o código de forma a dividir racionalmente o uso de recursos que elas venham a compartilhar. Esses recursos podem ser variáveis, conexões com banco de dados, dispositivos etc. Essa forma de organizar o acesso aos recursos compartilhados se chama Sincronização de Threads. Caso nós não sincronizarmos corretamente as threads, fatalmente cairemos nos famigerados deadlocks. Para realizarmos uma boa sincronização de threads, devemos conhecer alguns métodos e propriedades de uma thread: Métodos:
Suspend(): Suspende a execução de uma Thread, até o método Resume() seja chamado. Resume(): Reinicia uma thread suspensa. Pode disparar exceções por causa de possíveis status não esperados das threads. Sleep(): Uma thread pode suspender a si mesma utilizando esse método que espera um valor em Milisegundos para especificar esse tempo de pausa.
Join(): Chamado por uma thread, faz com que outras threads espere por ela até que ela acabe sua execução. CurrentThread(): Método estático que retorna uma referência à thread em execução
Estados de uma Thread: Os estados podem ser obtidos usando a enumeration ThreadState, que é uma propriedade da classe Thread. Abaixo seguem os valores dessa enumeration:
Aborted: Thread já abortada; AbortRequested: Quando uma thread chama o método Abort(); Background: Rodando em bnackground; Running: Rodando depois que outra thread chama o método start(); Stopped: Depois de terminar o método run() ou Abort(); Suspended: Thread Suspendida depois de chamar o método Suspend(); Unstarted: Thread criada mas não iniciada. WaitSleepJoin: Quando uma thread chama Sleep() ou Join(), ou quando uma outra thread chama join();
Propriedades:
Name: Retorna o nome da thread; ThreadState: Retorna o etado de uma thread; Priority: Retorna a prioridade de uma thread. As prioridades podem ser: o Highest o AboveNormal o Normal o BelowNormal o Lowest
IsAlive: Retorna um valor booleano indicando se uma thread esta “viva” ou não; IsBAckground: Retorna uma valor booleano indicando se a thread está rodando em Background ou Foreground;
2.2.1 Sincrinizando Threads Quando temos várias threads que compartilham recursos, precisamos fornecer acesso sincronizado a esses recursos. Temos de lidar com problemas de sincronização relacionados com o acesso simultâneo a variáveis ??e objetos acessíveis por múltiplas threads ao mesmo tempo. Para ajudar nesta sincronização, no .Net, podemos fazer com que uma thread bloqueie um recurso compartilhado enquanto o utiliza. Podemos pensar nisso como uma caixa onde o objeto está disponível e apenas um segmento pode entrar e outra thread só terá acesso ao recurso quando a outra que esta o utilizando saia da caixa. Vamos exemplificar o uso de múltiplas threads. No mesmo Console Application, vamos primeiramente criar uma classe que conterá um método, que por sua vez será utilizado por algumas threads concorrentemente:
class Printer
{
public void PrintNumbers()
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(100);
Console.Write(i + ",");
}
Console.WriteLine();
}
}
No método Main da classe program, vamos instanciar um objeto da classe Printer, criar três threads passando o método PrintNumbers para cada thread criada, ou seja, as três threads tentarão utilizar o mesmo recurso: static void Main(string[] args)
{
Console.WriteLine("======MultiThreads======");
Printer p = new Printer();
Thread[] Threads = new Thread[3];
for (int i = 0; i < 3; i++)
{
Threads[i] = new Thread(new ThreadStart(p.PrintNumbers));
Threads[i].Name = "threadFilha " + i;
}
foreach (Thread t in Threads)
t.Start();
Console.ReadLine();
}
Ao executar o programa temos o seguinte resultado:
Podemos ver como o Thread Scheduler nativo está chamando o objeto Printer para imprimir os valores numéricos. Como podemos ver temos um resultado inconsistente pois não temos qualquer tipo de controle de sincronização das threads, sendo que elas estão acessando o método PrintNumbers() de maneira aleatória. Existem diversos meios de sincronização que podemos utilizar para garantir o processamento correto de nossos programas multithread. A) Usando o Lock Poderíamos por exemplo utilizar a pajavra-chave Lock dentro do método PrintNumbers(): public void PrintNumbers()
{
lock (this)
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(100);
Console.Write(i + ",");
}
Console.WriteLine();
}
}
A palavra-chave lock obriga-nos a especificar um token (uma referência de objeto) que deve ser adquirido por uma thread para entrar no escopo do lock (bloqueio). Quando estamos tentando bloquear um método em nível de instância, podemos simplesmente passar a referência a essa instância. (Nós podemos usar esta palavra-chave para bloquear o objeto atual) Uma vez que a thread entra em um escopo de bloqueio, o
sinal de trava (objeto de referência) é inacessível por outros segmentos, até que o bloqueio seja liberado ou o escopo do bloqueio seja encerrado. Execute o programa e você terá a seguinte tela:
B) Utilizando Monitor Type Método Monitor.Enter () é o destinatário final do token da thread. Precisamos escrever todo o código do escopo de bloqueio dentro de um bloco try. A cláusula finally assegura que o token de thread seja liberada (usando o método Monitor.Exit () , independentemente de qualquer exceção de runtime. Na classe Printer, modifique o método PrintNumbers como a seguir: public void PrintNumbers()
{
Monitor.Enter(this);
try
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(100);
Console.Write(i + ",");
}
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Monitor.Exit(this);
}
}
Ao rodar o programa temos o seguinte resultado:
Bom, chegamos ao fim de nosso artigo.
Programação Assíncrona com C# Facebook Twitter (3) (0)
Na aplicação de exemplo deste artigo uma das principais classes para execução assíncrona de tarefas – System.ComponentModel.BackgroundWorker – é usado para criar um conjunto de tarefas sendo executadas independentemente. Artigo do tipo Tutorial Recursos especiais neste artigo: Conteúdo sobre boas práticas. Programação Assíncrona com C# - Parte 1 É lugar comum no meio de desenvolvimento de software dizer que enquanto houve um progresso muito grande no hardware os programas não vêm conseguindo aproveitar todo este
progresso.
Alguns dizem que o hardware é sub utilizado e que os programas são até mesmo mal feitos, a ponto de não usarem todos os recursos de processamento e memória colocados à disposição. Discussões à parte, um fato é que realmente os softwares estão subutilizando o hardware existente que também é bem potente. Por outro lado, aproveitar todo este potencial é muito complexo. A “evolução” dos computadores atuais se deu mais em aumentar o número de processadores disponíveis num mesmo dispositivo do que aumentar o número de operações por segundo que são executadas. Resumindo: se tem mais processadores para executar os programas, mas, a frequência de velocidade destes aumentou pouco se é que aumentou. Para utilizar efetivamente este potencial é necessário aproveitar a existência de vários processadores e criar rotinas paralelas, que sejam executadas juntamente com outras e que o sistema operacional, ao delegar que se tratam de rotinas paralelas faça a distribuição destas no processador. Sistemas operacionais mais modernos – Windows 7 e acima, Mac OS e o Linux – lidam bem com paralelismo e distribuição das tarefas nos processadores, mas, boa parte dos programas só agora começa a ser preparada para executar eficientemente rotinas paralelas. O Framework .NET permite criar estas rotinas e controla-las de uma maneira mais fácil, não que será tão fácil como a programação tradicional, mas, com um pouco de trabalho e organização se consegue bons resultados. Para melhores resultados é preciso entender diferenças sutis entre rotinas paralelas e assíncronas. A primeira modalidade diz respeito em tarefas sendo executadas de forma simultânea podendo os seus resultados serem ou não esperados ao mesmo tempo. Já tarefas assíncronas são sempre executadas em paralelo, mas não é necessário aguardar a sua finalização, ou, o término de uma rotina não depende do término de outra. Como é de se esperar o maior problema a ser resolvido é controlar o comportamento de cada tarefa sendo executada assincronamente. Assim, recursos do Framework .NET vão fazer uma boa diferença dando ferramentas bem flexíveis e mais simples para compreensão. Na aplicação de exemplo deste artigo uma das principais classes para execução assíncrona de tarefas – System.ComponentModel.BackgroundWorker – é usado para criar um conjunto de tarefas sendo executadas independentemente sendo que cada uma possui seus eventos e pode ser interrompida sem afetar a execução da outra. Em que situação o tema é útil As técnicas para controlar rotinas paralelas e assíncronas – sim, existem diferenças –
apresentadas no artigo ajudarão aos desenvolvedores a iniciar a utilização dos recursos do Framework .NET e da linguagem C# voltados à execução de várias rotinas aproveitando assim os recursos de processamento dos computadores modernos sem perder o controle, possibilitando ao usuário além de iniciar várias rotinas paralelas, saber como está o seu andamento e também cancelar a rotina quando achar necessário.
Hardware e paralelismo Vamos começar de um jeito diferente. Se puder (e estiver usando o Windows no seu computador, claro), abra o gerenciador de tarefas. Vá para a aba Desempenho e dê uma conferida como está o trabalho dos processadores do seu PC (ou notebook, ou, seja lá o que estiver usando...). Se você for um cara sortudo vai ter quatro ou até oito núcleos de processamento. Se for uma pessoa mais modesta, terá seus meros dois processadores, o que já é um bom começo. Guarde estes dados que já falarei novamente sobre estes. Há pouco mais de dez anos, o máximo que um computador pessoal poderia ter era um processador com um núcleo simples e quando muito, coprocessadores aritméticos que, segundo especificações técnicas daquela época, eram usados para auxiliar no cálculo de tarefas matemáticas mais complexas. O problema é que nunca ficou muito claro para desenvolvedores de aplicações comerciais quando e como estes processadores auxiliares podiam ser usados. Desde os tempos do Delphi eu nunca cheguei a ver uma rotina que fosse feita para rodar no processador aritmético ou algo assim e realmente não pude pesquisar sobre este assunto. Mas, segundo a lei te Moore, os processadores dobrariam de capacidade a cada dezoito meses, e, então, alcançariam um limite físico para o número de operações que podem ser feitas por segundo. Bem, parece que estamos perto deste limite. Há um bom tempo a velocidade dos processadores não tem grandes saltos em desempenho e computadores mais comuns, dificilmente ultrapassam os 2.8 GHZ de frequência. O maior problema nem é tanto aumentar a velocidade e sim, dissipar o calor produzido pelo processador. Problemas com hardware e limitações físicas à parte, estas limitações vem sendo muito bem resolvidas através da montagem de processadores em paralelo gerando os processadores multicore que temos e que você deve ter percebido ao usar o gerenciador de tarefas. Assim, resolve-se o problema de velocidade aumentando o número de processadores. Em teoria se com um processador com a velocidade de 2.2 GHZ se tem um número determinado de tarefas executadas por segundo, ao termos dois processadores da mesma frequência se terá o dobro desta velocidade. Em teoria, mas não é o que ocorre na prática. Acontece que aumentando o número de núcleos de processamento aumenta-se a complexidade em coordenar o seu funcionamento. É semelhante à gestão de projetos. Se você tem um projeto que precisa ser entregue em três semanas e tem quatro pessoas, se dobrar o número de pessoas não significa necessariamente que o projeto vai ser entregue na metade do tempo. Isto porque você vai precisar coordenar esta equipe e distribuir as tarefas para que uma pessoa da equipe não fique esperando outra terminar uma tarefa da qual a sua depende terminar o trabalho para começar o dela.
Algo análogo a isto ocorre nas CPUs multiprocessadas. É preciso coordenar para que os processadores não fiquem ociosos esperando outro terminar uma tarefa da qual a sua depende. Isto é bem complicado e por causa disto, muitos softwares são feitos quase todos sequencialmente – isto é sem tarefas paralelas – ou, quando muito, existem rotinas paralelas que são mal gerenciadas e acabam causando congelamento da aplicação que fica aguardando uma tarefa terminar para poder continuar com seu trabalho. Por causa disto ainda há alguma frustração entre os usuários de programas, principalmente aplicações corporativas e comerciais, que muitas vezes investem em hardware, mas quando colocam seus programas para serem executados nestas máquinas não tem o desempenho esperado. Parte deste problema ocorre principalmente porque o software não é criado para aproveitar os recursos de multiprocessamento. Por outro lado, é muito comum que nestas máquinas multicore, a frequência dos processadores fique muito baixa, em torno dos 1.8 GHZ. Como solução para isto os desenvolvedores precisam aprender a domesticar as rotinas paralelas. Existe uma brincadeira na minha equipe de trabalho que diz que “ninguém controla uma thread selvagem”. A boa notícia com o Framework .NET isto pode ser feito mais facilmente. É possível começar bem simples, executando processamento paralelo até criar um verdadeiro thread pool e controlar múltiplas tarefas sendo executadas paralelamente sem permitir que uma interfira na outra. Thread pool é o termo usado para uma das formas de se controlar várias threads dentro de um programa estabelecendo um ID para esta e podendo interferir no seu funcionamento. Como controle pode se citar a sua interrupção, pausa e também monitoramento do progresso do seu andamento. Uma das maneiras que isto pode ser implementado em C# e no Framework .NET é através do uso de collections. Na aplicação de exemplo é criado um controle análogo a este.
Paralelismo x Rotinas Assíncronas Rotinas paralelas são executadas em segundo plano, permitindo que outras tarefas sejam executadas ao mesmo tempo. Sua utilização mais comum é quando o software deve fazer uma operação longa como uma consulta no sistema de arquivos ou em um banco de dados, um cálculo mais complexo, uma renderização de imagens, resumindo, qualquer operação que vai levar algum tempo e que se deseja que o programa continue respondendo aos comandos do usuário. Geralmente são executadas enquanto a interface com o usuário mostra o andamento da tarefa para o usuário que pode ter ou não a opção de cancelar a operação. Existe outra forma de usar tarefas paralelas que é distribuindo seu trabalho em sub-rotinas sendo que cada uma faz uma parte da tarefa principal. Isto já é mais difícil de fazer porque neste caso, mesmo que o trabalho seja executado mais rápido, deve se ter um bom planejamento para fazer a separação das partes e, pode ser que em alguns casos, uma tarefa deva esperar o término de outra para continuar seu trabalho. Neste caso, mesmo
executando as tarefas de forma paralela o processamento é síncrono que significa que uma tarefa precisa esperar o término de outra para ser executado. Quando se fala em rotinas assíncronas está se tratando de uma forma de usar o processamento paralelo onde não é necessário aguardar o término de uma rotina para executar outra. Este tipo de rotina é usada quando não é necessário aguardar o seu término ou quando o resultado final não é necessário para que se possa continuar usando o software. Exemplos bem práticos de rotinas assíncronas são o envio de e-mail, um envio de solicitação de processamento para um banco de dados (como uma consulta, um relatório, um cálculo que possa ser executado enquanto o usuário esteja fazendo outras tarefas). Ao desenvolver rotinas assíncronas o programador precisa ter um controle do seu andamento, meios para seu cancelamento e alguma forma de conhecer quando esta foi concluída para poder mostrar o resultado para o usuário ou fazer algo com o valor que foi devolvido. Sempre que possível deve se optar por este tipo de rotina, principalmente quando se deseja criar programas que precisam estar constantemente disponíveis para o usuário e não congelar a qualquer custo.
Recursos do Framework .NET Para poder desenvolver rotinas assíncronas que atendam aos requisitos colocados antes: controle do andamento, término e cancelamento, os desenvolvedores que usam a plataforma .NET contam com vários recursos. O principal é a classe System.Threading.Thread que é usada para fazer com que o código seja executado de forma paralela. Esta é a classe base para o processamento em paralelo e o seu funcionamento bem simples. Considere um método qualquer, como o presente na Listagem 1. Listagem 1. Estrutura de um método public void MeuMetodo()
{
// faz alguma coisa...
var obj = "Teste";
Console.WriteLine(obj);
// Faz mais alguma coisa...
}
Os detalhes da implementação não são importantes por isso foram omitidos, considere que este método faça qualquer tipo de processamento. No corpo do método não é necessário nenhum comando ou classe especial, pode ser uma atribuição de valores, uma consulta a um banco de dados ou qualquer coisa. O ponto principal é que para que este método seja executado em uma thread separada e ficando assim em segundo plano, enquanto outros métodos possam ser executados em paralelo basta usar o código a seguir: var thread = new System.Threading.Thread(MeuMetodo);thread.Start(); Note que este método não retorna nada e nem requer parâmetros, porém, podemos criar métodos que usam parâmetros e tenham algum retorno. A classe System.Threading.Thread é muito versátil e possui métodos para cancelar, suspender e continuar sua execução. Se você puder, verifique a documentação sobre a classe e o seu namespace para conhecer melhor os recursos que podem ser usados, embora, mais à frente, veremos outra forma de controlar execução de threads que permite mais controle com menos complexidade.
Dificuldades ao utilizar Threading O maior problema ao se usar threads e a classe Thread do Framework .NET é a dificuldade em controlar o seu andamento e também de oferecer ao usuário maneiras realmente eficazes de cancelamento. Isto ocorre porque o desenvolvedor é quem fica responsável por realizar estas tarefas e implementar da maneira que achar melhor. De uma maneira bem direta, é preciso começar tudo do zero porque apesar de toda a flexibilidade oferecida, o programador é quem decide como fazer o controle. Dois pontos fundamentais para o desenvolvimento assíncrono são reportar o andamento da tarefa e permitir o seu cancelamento. Para que isto ocorra, deve se criar uma forma de comunicação entre o processo que chamou a thread e ela. Uma das formas de fazer a comunicação do andamento e passar para a thread um objeto que possa ser notificado sempre que necessário como um delegate usado em conjunto com um EventHandler. Delegates funcionam como “ponteiros” para métodos e normalmente são usados juntamente com ponteiros para que uma rotina dentro de uma classe possa executar uma ação quando algo acontecer como, por exemplo, for necessário reportar o progresso do trabalho. Assim, vamos considerar uma classe, como a Listagem 2 demonstra. Listagem 2. Demonstrando uso de delegates 1 public class foo
2 {
3
public delegate void FazAlgo(int valor);
4
public event FazAlgo Executa;
5
6
public void Faz()
7
{
8
for (int i = 0; i < 1000; i++)
9
if (i % 100 == 0 && Executa != null)
10
11
Executa(i);
}
12 }
Nesta classe a linha 3 cria o delegate que é a assinatura para o método que será acoplado ao evento. O assessor deve sempre ser public seguido da palavra-chave delegate. O tipo de retorno pode ser qualquer um, porém, o mais comum é void. O delegate pode ou não ter parâmetros. Novamente o usual é que se tenham estes já que seu uso só é justificável caso se deseje passar valores entre rotinas diferentes. Um delegate precisa estar acompanhado de um evento que será inspecionado e usado para fazer a chamada ao método que foi anexado ao delegate. Neste exemplo, a linha 4 cria um evento chamado Executa. Novamente o assessor deve ser público seguido da palavra event, o tipo é o do delegate criado anteriormente seguido de um nome. Seguindo em frente a classe tem um método público chamado Faz() que se inicia na linha 6 que teoricamente será executado em segundo plano. Este método executa um loop de zero a mil e a cada cem números passa o valor para um método que vai fazer alguma coisa. Observe na linha 9 um passo importante ao trabalhar com eventos que é a verificação do evento estar nulo. Se o evento não estiver, a classe faz uma chamada ao evento (linha 10). Para consumir este código, teríamos alguma coisa parecida com o código da Listagem 3. Listagem 3. Consumindo eventos e delegates 1 using System;
2
3 public class program
4 {
5
public static void Main()
6
{
7
var obj = new foo();
8
obj.Executa += DisparaFazAlgo;
9
obj.Faz();
10
}
11
12
public void DisparaFazAlgo(int valor)
13
{
14
15
Console.WriteLine(valor);
}
16 }
Trata-se de um programa que se inicia criando uma instância da classe (linha 7) e na linha 8 acopla o método DisparaFazAlgo() o evento Executa da classe. Em seguida é feita uma chamada ao método Faz() para executar o código. O método DisparaFazAlgo() está descrito a partir da linha 12. Note que o cabeçalho segue o modelo definido pela classe. Assim, de uma forma bem básica, são estes os passos para que métodos dentro de uma classe possa executar código externo, geralmente fazendo parte da classe um nível acima. Espero que tenha ficado claro que a principal desvantagem de se fazer tudo do zero é a dificuldade e o número de passos necessários, sem falar da necessidade de estar atendo a muitos detalhes. É importante também notar que, existem situações que somente esta abordagem poderá ser usada em casos de desenvolver rotinas para bibliotecas de
funções que serão usadas em múltiplos projetos. Além disto, desenvolver tudo a partir do zero dá a possibilidade de se controlar cada aspecto do código.
Background Worker Como quase todos os recursos para o Framework .NET existe uma opção mais acessível e que oferece blocos prontos para manipulação de threads. Esta opção está na classe System.ComponentModel.BackgroundWorker que é na verdade uma classe que encapsula o funcionamento de threads com propriedades, eventos e métodos prontos para serem usados em seus projetos de qualquer natureza. Como “projetos de qualquer natureza”, estão incluídas as class libraries (dlls), projetos Windows Forms, WPF e Web. Esta classe está presente nos projetos Windows Forms através de um componente da ToolBox do Visual Studio, mas, pode ser criado diretamente no código. O seu grande diferencial são recursos mais eficientes (e também mais fáceis de usar) para controle de execução, progresso e cancelamento além de um melhor tratamento do final da thread. Para se ter uma ideia de como o trabalho é realizado são duas as principais propriedades que precisam ser configuradas ao se criar um objeto desta classe: 1. WorkerReportProgress: indica se as rotinas que estão vinculadas com o componente (ou objeto se preferir) vão dar informações sobre o andamento das tarefas vinculadas. Neste caso, ao executar uma chamada para o método ReportProgress do componente, passando o percentual concluído do trabalho e opcionalmente, algum outro dado que for necessário. 2. WorkerSupportsCancellation: dá suporte para que a rotina que está sendo executada seja cancelada de forma assíncrona. Neste caso, ao finalizar o trabalho deve se criar um EventHandler para o evento RunWorkerCompleted que verifique se a rotina foi cancelada. Como se pode perceber, com poucas propriedades já se tem boa parte do trabalho executado. Como foi citado o acoplamento de eventos, são três os principais eventos que devem ser tratados ao se trabalhar com este componente. O primeiro é o DoWork. Este evento é chamado pelo método RunWorkAsync(). Ao fazer isto é necessário escrever um método com a assinatura para este evento que é a seguinte: private void worker_DoWork(object sender, DoWorkEventArgs e) O primeiro parâmetro é usado para capturar de qual objeto partiu a chamada para a rotina assíncrona e o segundo, armazena dois parâmetros que podem ser passados para a rotina: 1. Argument: valor do tipo object podendo armazenar qualquer dado. 2. Result: outra propriedade do tipo object que armazena o resultado da rotina e é enviada para o método executado ao término da tarefa. Use esta propriedade para tratar o retorno de métodos que foram executados em background.
Durante a execução da tarefa, pode ser que seja importante mostrar o andamento do trabalho usando, por exemplo, uma barra de progressos. Um aspecto importante da utilização de rotinas assíncronas ou threads é que estas não podem acessar os componentes da interface diretamente. Isto porque só códigos dentro da thread que criou o controle visual podem interagir com os mesmos. Porém, com o BackgroundWorker isto é diferente, basta escrever um método para o evento ProgressChanged, que deve ter a seguinte assinatura: private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) Mais uma vez, o primeiro parâmetro controla o remetente da chamada do método e o segundo, contém dois argumentos para serem usados: 1. ProgressPercentage: armazena um número inteiro com o percentual do trabalho concluído. 2. UserState: instância do tipo object que pode armazenar qualquer informação que seja importante durante a comunicação do andamento. Por fim, quando a rotina acoplada com o BackgroundWorker termina, é feita uma chamada ao evento RunWorkerCompleted que precisa ser manipulado por um método como o do modelo: private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) Como nos anteriores, sender identifica qual objeto fez a chamada à rotina e o segundo parâmetro, passa dois argumentos, Result e UserState, ambos do tipo objeto. O primeiro elemento pode ser armazenado durante o evento DoWork para armazenamento dos resultados da operação. A classe System.ComponentModel.BackgroundWorker vai ser muito usada na aplicação de exemplo que foi criada para demonstrar rotinas assíncronas neste artigo.
Aplicação de exemplo O objetivo é criar uma aplicação Windows que percorra as subpastas de uma pasta ou drive informado e armazene os dados dos arquivos que forem encontrados em um banco de dados SQL Server. A janela principal (Figura 1) deve disponibilizar o campo para o usuário informar a pasta inicial. Para a seleção ficar mais exata, um botão deve ser usado para mostrar uma caixa de diálogo para selecionar a pasta inicial. A janela de busca está exibida na Figura 2.
Figura 1. Tela inicial da aplicação Após a seleção da pasta, o seu nome é preenchido no campo de texto (que na interface vai estar como somente leitura).
Figura 2. Caixa de diálogo para escolher pasta inicial Para iniciar o trabalho, o usuário deve clicar no botão executar. O progresso do andamento é feito em um painel que mostra o andamento das diversas tarefas. Um detalhe importante é que após iniciar uma rotina, o usuário pode rodar outra imediatamente, para outra pasta e assim, ter várias rotinas rodando simultaneamente como demonstrado na "
Programação Paralela no .NET Framework Facebook Twitter (9) (0)
Neste artigo entenderemos como funciona a programação paralela e assíncrona no .NET Framework, aplicando técnicas para otimizar o desempenho e responsividade das aplicações.
Fique por dentro O desenvolvimento de aplicações mais robustas, responsivas e que realizam diversos cálculos por segundo tem se tornado corriqueiro principalmente devido à evolução e o barateamento dos processadores, que passaram a possuir vários núcleos de processamento.
Cada núcleo de processamento é também conhecido como core que na verdade é um processador à parte, podendo haver dois, quatro, seis e até oito dessas cores em um único chip de CPU. Este avanço incentivou o desenvolvimento da tecnologia multithread que torna o fluxo de execução de uma aplicação mais eficiente e dinâmico. O objetivo deste artigo é apresentar o ambiente multithreading, suas qualidades, os problemas que podem advir através do seu uso e as ferramentas que o .NET fornece, juntamente com a linguagem C#, para a criação de aplicações que possam proporcionar uma nova experiência ao usuário. Para que você possa entender o que é multithread, imagine uma montadora de automóveis. Agora imagine que nesta montadora haja somente um funcionário que seja responsável por planejar os custos, projetar o automóvel, organizar as peças, montar as peças, pintar o automóvel, instalar o motor, realizar a bateria de testes do automóvel e enviar o automóvel para a concessionária. Quantos automóveis você acha que seriam produzidos por dia? Melhor, por ano? Com certeza não seriam muitos. Através desse cenário de produção surreal é que pode se fazer uma analogia com os processos de software que todos os dias são processados pela CPU.
Mas o que são processos e como são formados? Para que você possa entender melhor como funciona as threads, será importante abordar alguns conceitos básicos de processos e threads.
Processos e threads Processo é um dos conceitos mais importantes de qualquer sistema operacional, pois representam programas em execução. Eles possuem um código executável, pilha de execução, estado de processo e prioridade do processo que é definida pelo sistema operacional. Possuem também um valor no registrador Program Counter (PC) e um valor do apontador de pilha Stack Pointer (SP). Com estes recursos, o sistema operacional realiza operações concorrentes entre os processos realizando uma espécie de pseudoparalelismo, onde alterna entre os processos ativos, executando partes de cada um em um intervalo de tempo definido pelas prioridades de cada processo, sendo esta troca denominada troca de contexto ou context switching. O sistema operacional não se perde porque possui endereços de cada intervalo de execução e dos próximos intervalos que serão executados, armazenados pelo registrador da CPU Program Counter. Os processos também podem ser classificados de acordo com o tipo de tarefa que realizam: · Processos CPU-bound: Processos CPU-bound passam a maior parte do tempo utilizando recursos do processador para a execução de suas tarefas. Aplicações que realizam muitos cálculos por segundo como jogos ou softwares de informação geográfica precisam de maior poder de processamento e consequentemente possuem maiores prioridades de processamento; · Processos I/O-bound: Processos I/O-bound não precisam de tanta atenção do processador como os processos CPU-bound, porque utilizam mais o disco em suas tarefas de gravação e leitura de dados. Geralmente estes processos são bem mais lentos que os processos CPU-bound porque não dependem exclusivamente da CPU, tendo seu gargalo de desempenho nos discos. A tendência é que quanto maior for a velocidade do processador, mais gargalos de processos I/O irão surgir, pois o processador precisará aguardar o disco terminar a maior parte do trabalho para que o processo continue. Essa diferença de desempenho é mostrada na Figura 1.
Figura 1. Períodos de uso da CPU alternando entre períodos de espera por operações de I/O. a) Processo CPU-bound b) Processo I/O-bound
As threads são bem parecidas com os processos, mas possuem entre elas diferenças fundamentais. Cada processo serial (que não possui paralelismo) possui seu próprio espaço de endereçamento definido pelo S.O, tendo somente uma thread principal. Porém há ocasiões em que um processo é custoso e encarregar somente uma thread de todo o trabalho acarreta em percas significantes de desempenho e eficiência do processador, portanto com a criação de múltiplas threads no mesmo processo (possuindo o mesmo espaço de endereçamento) é possível distribuir responsabilidades e partes de processamento do processo a cada thread e a mesma irá executar em paralelo sua parte. A Figura 2 ilustra as diferenças entre aplicações seriais e multithread.
Figura 2. Diferenças entre aplicações seriais (uma thread) e aplicações multithread.
Ao final todo o trabalho realizado é reunido por cada thread e entregue como um só processo, sendo este procedimento chamado de fork-join, como mostrado na Figura 3.
Figura 3. Representação gráfica do processo fork-join.
Se voltarmos ao primeiro exemplo dado de um cenário de produção, fica evidente que seria necessária mais mão de obra em uma linha de produção para que mais carros fossem produzidos e entregues. Se transformarmos este cenário de produção em um processo de software, podemos afirmar que precisaríamos de mais threads para que o processo fosse feito mais rapidamente, por exemplo cinco pessoas pintariam os carros e 10 soldariam as peças, criando um ambiente multithread (fork). Ao final um resultado único seria entregue que é o automóvel pronto em si (join).
Multithreading em .NET A partir da versão 4.0 do .NET, novos recursos de programação paralela e multithreading como a TPL (Task Parallel Library) representada principalmente pela classe Task e as cláusulas async e await (inseridas na versão 4.5), tornaram a programação paralela bem mais fácil e rápida. Neste artigo serão abordadas as principais classes de programação em threads e suas funcionalidades. Serão mostradas as classes Thread, ThreadPool e Task para programação em threads. Também serão tratadas as funcionalidades PLINQ (Parallel Language Query), as cláusulas async e await para responsividade de aplicações. Por último alguns recursos avançados de programação paralela devem ser abordados também, como semáforos, mutexes, monitors e coleções concorrentes.
Primeiro exemplo – A classe Thread Para o nosso primeiro exemplo utilizando a linguagem C#, será mostrado um código que realiza uma tarefa entre três threads. A thread main é a thread onde é executado o método de entrada Main, padrão na maioria das linguagens descendentes do C. Após são criadas duas threads; a primeira thread ficará responsável por apenas realizar exibir a mensagem: “Thread rodando”,
onde será mostrado o seu identificador, porém esta mensagem será exibida em um loop infinito. Para cancelá-la, uma segunda thread ficará responsável por interromper sua execução se o usuário digitar ‘C’. O código desse exemplo pode ser visto na Listagem 1. Listagem 1. Primeiro exemplo utilizando a classe Thread 01
bool stop = false;
02
03
Thread thread = new Thread(() =>
04
{
05
while (!stop)
06
{
07 Console.WriteLine("Thread {0} rodando...", Thread.CurrentThread.ManagedThreadId);
08
Thread.Sleep(500);
09
}
10
Console.WriteLine("Thread {0} cancelada.",
Thread.CurrentThread.ManagedThreadId);
11
});
12
13
14
var thread2 = new Thread((id) =>
{
15 var key = Console.ReadKey(true);
16
if (key.Key == ConsoleKey.C)
17
{
18
stop = true;
19
Console.WriteLine("Thread {0} cancelou a execucao
da thread {1}",
20
Thread.CurrentThread.ManagedThreadId, (int) id);
21
22
}
});
23
24
thread.Start();
25
thread2.Start(thread.ManagedThreadId);
26
27
for (int i = 0; i< 100; i++)
28
{
29
Console.WriteLine("Main thread rodando...");
30
Thread.Sleep(500);
31
}
32
33
thread.Join();
34
thread2.Join();
Na linha 1 é declarada uma variável booleana stop que ficará responsável por terminar a execução do código executado pela thread1. A thread1 é declarada nas linhas 3 a 11. A classe Thread recebe como parâmetro do construtor, um delegate do tipo ThreadStart.
O delegate ThreadStart é uma referência de um método que não recebe valores como parâmetro e retorna um tipo void. A notação lambda é utilizada para escrever um método anônimo, que é representada pelo símbolo => (goes to), que indica ao compilador que o corpo do método está sendo iniciado. Ao lado esquerdo são declarados os parâmetros do método anônimo e a direita é escrito o código do método. O método anônimo é como se fosse um método comum, porém não possui nome. Na linha 5, o bloco while é controlado pela variável stop. Enquanto a mesma for falsa, este código será executado. Mas onde que o valor de stop é alterado? Nas linhas 13 a 22 é declarada uma segunda thread, a thread2 que será responsável por cancelar a thread1. Ela irá captar as teclas digitadas pelo usuário e irá testar se é a tecla C (linhas 15 e 16). Se for a tecla correta, a thread1 é cancelada, juntamente com a thread2, exibindo uma mensagem que mostra os ID’s da thread cancelada e da thread que cancelou. Nas linhas 24 e 25 são iniciadas as threads. A thread2 recebe como argumento do método Start o ID da thread1 que será cancelada ao usuário pressionar C; este argumento é recebido pelo parâmetro id declarado na lista de argumentos do método anônimo da linha 13, que exibirá na mensagem o ID da thread cancelada. Nas linhas 33 e 34 são invocados os métodos Join das respectivas threads, mas qual é a sua finalidade? Lembra-se da Figura 3? É aqui que o fork das execuções das threads são unidos à thread main. Se estas linhas fossem comentadas, havia a possibilidade de a thread main não esperar o fim das execuções das outras threads, sendo que a aplicação poderia terminar precocemente. Aproveitando este código, pode-se mencionar algumas outras características do ambiente multithreading: · Cada thread criada neste exemplo recebe um identificador e uma prioridade. Tal prioridade pode ser alterada pela propriedade Priority e o identificador recuperado pela propriedade ManagedThreadId; · A thread não é iniciada automaticamente, mas cabe ao desenvolvedor iniciar sua execução; · Ao ser criada, a thread é colocada em uma fila de execução de threads; · Ao terminar o tempo de execução, o S.O. suspende a execução da thread, a coloca no final da fila de execuções e executa a próxima thread da fila (context switching); · Quando uma thread deve esperar alguma tarefa de I/O, também é realizado o " [...]
Serialização Com muita frequência precisamos persistir os objetos das nossas aplicações em arquivos ou enviá-los, por exemplo, através da internet para outras aplicações. Nesses casos é preciso obter uma representação desses objetos em um formato adequado, como XML, JSON ou binário. Para isso usamos a técnica de serialização, que você poderá conhecer em maiores detalhes nos links a seguir:
Serialização de objetos em C# Facebook Twitter (1) (0)
O artigo mostra os primeiros passos, classes, métodos e práticas a serem tomadas para realizar a serialização de objetos usando os recursos do framework .NET e a linguagem C#.
Fique por dentro A serialização permite que aplicações de diferentes plataformas possam trocar informações sem que tenham qualquer biblioteca compartilhada, apenas usando protocolos e padrões de formatação e armazenamento dos dados. O artigo mostra os primeiros passos, classes, métodos e práticas a serem tomadas para realizar a serialização de objetos usando os recursos do framework .NET e a linguagem C#.
Um problema muito comum enfrentado por quem precisa utilizar programas que exportam ou importam informações a partir de arquivos gerados por outrem, é a incompatibilidade entre versões. Às vezes, um documento de texto ou um arquivo de imagem criado com uma versão de um software não pode ser aberto ou utilizado corretamente por outra versão do mesmo software, ou ainda por outro software de função equivalente.
Atualmente é grande o número de programas, formatos de arquivo e plataformas que precisam ser interconectadas. Por exemplo, é bem provável que os dados que sejam inseridos em uma aplicação desktop, sejam vistos posteriormente em uma aplicação na web ou enviados para algum dispositivo móvel. No início havia pouco problema, porque também o número de plataformas era reduzido. Basicamente havia um ou dois sistemas operacionais e o formato usado era o texto puro, seguido de um arquivo indicando o layout, indicando onde um campo terminava e outro começava, basicamente indicando a coluna de início e o comprimento esperado dos dados. Também não era problema o idioma usado, pois no início da programação o inglês imperava e coisas como acentos e caracteres especiais – que complicam a interoperabilidade quando projetos rodam em idiomas diferentes – ainda não eram suportadas. Com a evolução, surgiram questões que precisaram ser resolvidas e a troca de dados entre os programas foi o ponto primeiro a ser atacado. Ao ler isto, pode-se ter a impressão de que a interoperabilidade é uma questão menor, porque os arquivos de texto são suficientemente eficazes para armazenar dados e facilmente de serem manipulados. Mas vejamos alguns dados: 1. Textos precisam ser representados de forma linear e contínua. Como fazer para representar objetos complexos que possuem objetos aninhados e listas de outros objetos? 2. Cada idioma tem o seu conjunto de caracteres específico. Considere a simples tarefa de enviar um texto em português para um programa sendo executado no idioma inglês (que como sabemos, não tem acentos nem os mesmos símbolos do nosso idioma). 3. Com o surgimento de linguagens orientadas a objeto, os dados passaram a ser estruturados de uma forma mais rica e complexa, para que pudesse se representar o mundo real através de analogias como modelos (classes) e objetos (instâncias). Logo, para que uma aplicação possa usar os dados, é necessário saber como estão estruturados, conhecendo não só os nomes usados, mas também os tipos de dados.
Uma solução abrangente Não demorou e começaram a surgir formas de se representar estes dados de uma forma eficaz e que pudesse ser adotada pelo maior número de aplicações: a serialização. Em linhas gerais, ela consiste em descobrir a estrutura de um determinado objeto ou dado e prepará-lo para ser armazenado ou transmitido pela rede de uma maneira portável, que possa ser lida por outra aplicação que conheça o padrão. Neste processo são transmitidos: 1. A estrutura dos dados – nomes de propriedades, seus tipos e hierarquia;
2. Os dados propriamente dito; 3. Como fazer para restaurar os dados. Usando uma analogia, a serialização é o teletransporte do mundo do desenvolvimento de software. Você coleta todas as informações sobre o dado a ser serializado, ordena esses metadados (dados sobre dados) de alguma forma e transmite. Na outra ponta, o programa que for ler, já conhecendo o protocolo e o funcionamento deste processo, materializa o objeto usando esses metadados. Assim, o fluxo de serialização pode ser simplificado da seguinte forma; 1. Descobrir a estrutura do objeto: quais os nomes dos seus elementos, objetos, propriedades e quais os seus tipos; 2. Fazer o mesmo para cada objeto aninhado até que não haja mais nenhum a ser descoberto; 3. Associar os valores aos objetos; 4. Transformar os dados para envio usando um formato pré-estabelecido. Embora a colocação destes passos possa parecer simples, considere como fazer para descobrir quais são as propriedades e elementos de um objeto, sendo que você não tem acesso ao código do mesmo, apenas uma amostra de uma instância deste objeto. Isto é mandatório para que a serialização possa ser largamente empregada. Qualquer desenvolvedor pode serializar um objeto se conhecer sua estrutura, mas como fazer sem acesso ao seu código? Neste ponto entra uma técnica muito difundida entre as linguagens de programação de várias plataformas chamada Reflection. Ela consiste em recursos para ler quais são as propriedades, tipos, métodos e valores de um objeto a partir de sua instância. No Framework .NET você irá encontrar este recurso no namespace classe System.Reflection. Nota
Usar Reflection é, por si só, uma tarefa longa e cheia de detalhes que não cabem neste artigo, e por isso serão apenas citados. Para obter mais detalhes, verifique a sessão Links deste artigo.
Formas de serializar Basicamente, são utilizadas duas formas de serialização:
· Binária: os dados são transformados em uma sequência de bytes. É mais complexa para enviar dados através da rede, porque pode implicar em problemas de segurança e os dados podem ser barrados por antivírus e firewalls. · Texto: os dados são estruturados usando fluxos de texto puro em uma determinada codificação. É a forma preferível quando se quer trocar dados entre plataformas diferentes e também quando estes precisam trafegar por uma rede, pois não representam ameaças à segurança. Sobre a serialização binária não há maiores considerações a serem feitas, pois como foi colocado, trata-se apenas de fluxo de bytes. Já realizar serialização no formato texto implica em escolher um jeito de estruturar as informações. Aqui entra um formato já bem conhecido e utilizado há bastante tempo, o XML, e um mais recente, mas que já tem um espaço importante na representação e transporte de informações, o JSON. Arquivos XML consistem de marcações ou tags, que podem ser aninhadas hierarquicamente, e por ser um formato de texto, podem ser lidos por qualquer editor de texto. A Figura 1 mostra um exemplo de arquivo XML aberto no navegador de internet.
abrir imagem em nova janela Figura 1. Exemplo de documento XML visualiza" [...]
Serialização de objetos no formato JSON Facebook Twitter (11) (0)
Neste artigo serão apresentadas algumas formas para se realizar a serialização e deserialização de objetos em JSON (JavaScript Object Notation) no .NET Framework, com exemplos na linguagem C#. Artigo do tipo Exemplos Práticos Serializando objetos em JSON Neste artigo serão apresentadas algumas formas para se realizar a serialização e deserialização de objetos em JSON (JavaScript Object Notation) no .NET Framework, com exemplos na linguagem C#. Serão apresentadas as classes nativas DataContractJsonSerializer, JavaScriptSerlializer e em seguida a biblioteca externa Json.NET, possibilitando a comparação entre estas e facilitando a escolha daquela que melhor atende a cada necessidade.
Em que situação este tema é útil O tema abordado neste artigo é útil no dia a dia de todo desenvolvedor que necessita armazenar ou transferir dados entre aplicações utilizando um dos formatos que mais tem ganhado espaço nos últimos anos, o JSON. Este conteúdo também é útil para aqueles que desejam conhecer as opções oferecidas pelo .NET Framework para fazer a serialização de objetos nesse formato de dados, de forma a estar pronto para escolher entre elas e utilizá-las quando for necessário. O rápido avanço dos softwares e o aumento da sua importância nos processos diários de diversos setores da sociedade têm feito ocorrer grandes modificações no perfil das aplicações em geral. Programas que antes se resumiam a editores de texto que mal exibiam na tela o conteúdo digitado e o enviavam para uma impressora matricial, agora precisam atender a exigências e seguir tendências que mudam quase que diariamente.
As aplicações atuais precisam estar aptas a trabalhar com grandes volumes de dados e, além disso, importar e exportar informações de diversos tipos e fontes. Os sistemas devem ser capazes de interagir uns com os outros e realizar a troca de informações, bem como armazená-las de forma segura e adequada à finalidade a que se destinam. Porém, não existe uma ferramenta única para desenvolvimento de software, pelo contrário, são várias as plataformas e linguagens já existentes e que não param de surgir e se renovar. Também não há uma regra universal que dite como as informações devem ser tratadas e armazenadas, o que por muito tempo fez com que cada aplicação trabalhasse de maneira totalmente individual e manipulasse seus dados de uma forma que dependia apenas dos desenvolvedores. Com o passar do tempo, a necessidade de que as informações geradas por um software pudessem ser aproveitadas por outro fez com que novas estratégias fossem desenvolvidas para atender as exigências do mercado. Os grandes sistemas gerenciadores de bancos de dados têm muito em comum entre si, como a forma de armazenar os dados em tabelas, então uma solução seria que todo software, independente da linguagem utilizada em seu desenvolvimento, pudesse acessar todo tipo de banco de dados. Hoje isso quase é possível, pois a maioria dos SGBDs comerciais disponibilizam provedores para serem acessados a partir das principais linguagens de programação. Mas é fácil perceber que essa alternativa não é tão viável, pois cada aplicação possui seu banco de dados, com um modelo totalmente planejado para atender às necessidades que motivaram sua criação. Logo, não é possível (ou viável) que um sistema, para obter informações provenientes de outro, precise acessar diretamente a base de dados principal deste segundo. Para resolver este conflito, formatos de representação de dados foram propostos e passaram a ser utilizados como padrões, seguidos por todos aqueles que os desejassem aderir a eles. Dentre estas soluções, destaca-se a XML (Extensible Markup Language, ou Linguagem de Marcação Estendida), uma linguagem reconhecida pela W3C (BOX 1) que tem como objetivo representar e organizar dados de forma hierárquica. Essa linguagem se difundiu rapidamente e passou a ser usada como principal forma de intercâmbio de informações entre aplicações, principalmente através da internet. Com sua popularização, as diversas linguagens de programação passaram a suportar diretamente a manipulação de dados nesse formato, o que permitiu que um arquivo XML gerado por uma aplicação pudesse ser facilmente lido por outra, desenvolvida em plataforma diferente, pois o que prevalecia eram as regras de estruturação dos dados definidos pelo padrão XML. A XML pode ser utilizada para representar desde dados simples, como textos e valores numéricos, até coleções de objetos complexos, com várias propriedades. Na Listagem 1 vemos um exemplo de um objeto Venda, com suas propriedades e itens, representado em formato XML. Listagem 1. Objeto complexo representado em formato XML 01
02
03
24/07/2013
04
100,00
05
102030
06
07
08
123
09
1
10
30,00
11
12
13
456
14
2
15
70,00
16
17
18
Notamos que os dados são organizados de forma hierárquica e utilizando os sinais < e >. Maiores detalhes sobre a linguagem XML fogem do objetivo deste artigo, mas é importante conhecer, ainda que de forma básica, o formato que atualmente é o mais utilizado para a troca de informações entre aplicações. Tendo esse entendimento, será mais fácil compreender o formato concorrente e que é foco deste artigo, o JSON. BOX 1. W3C
O W3C (World Wide Web Consortium) é uma comunidade fundada por Tim BernersLee, o criador da Web, e formada por diversos membros como universidades e empresas privadas, que cooperam para o desenvolvimento de padrões utilizados na web, como as linguagens HTML e CSS.
O formato JSON JSON (em inglês JavaScript Object Notation) é um formato para troca de dados baseado na sintaxe de representação de objetos da linguagem de script JavaScript. Apesar do nome e de ser derivado dessa linguagem, o uso do formato JSON não requer necessariamente a linguagem JavaScript, pelo contrário, a maioria das linguagens de programação atuais oferecem meios para se trabalhar com esse formato. A sintaxe JSON é bastante simples e muito conhecida por programadores que utilizam linguagens cuja sintaxe deriva de C, como C++, C# e Java. Isso faz com que este formato seja de fácil leitura por humanos e de fácil interpretação pelas máquinas, tornando-o bastante adequado a operações de troca de dados entre aplicações, principalmente na internet. Por este motivo, JSON se apresenta como um concorrente da linguagem XML, com maior expressividade no meio web, devido à facilidade de manipulação através de JavaScript e melhor desempenho. "
Reflection Reflection é uma técnica que tem por objetivo obter informações sobre objetos, classes e assemblies em tempo de execução. Nos artigos abaixo você poderá aprender a trabalhar com reflexão em C# e algumas aplicações práticas dessa técnica:
Trabalhando com Reflection em C# Facebook Twitter (5) (0)
Veja neste artigo como utilizar a técnica reflection para ler os metadados de um objeto e manipulá-lo na linguagem C#.
Figura 1: Trabalhando com Reflection em C# A reflexão é algo interessante que o .Net fornece, com ela podemos escrever código o qual lê as informações do metadado dos objeto em tempo de execução. Essas informações são toda a estrutura existente na classe, portanto métodos, propriedades e até mesmo atributos de classes e métodos são visualizadas. Para iniciar a captura de todas as informações da classe, devemos utilizar o namespace System.Reflection. Para visualizar as informações devemos primeiro capturar o Type do objeto, ele é responsável por encapsular todas as informações citadas e a fornece a possibilidade de manipula-las. Vamos a um caso simples de reflexão de um objeto. Listagem 1: Reflexão de ValeuType static void Main(string[] args)
{
int inteiro = 10;
string texto = "DevMedia";
float Flutuante = 10.2f;
System.Type Tipo = null;
Tipo = inteiro.GetType();
Console.WriteLine(Tipo.Name);
Console.WriteLine(texto.GetType().Name);
Console.WriteLine(Flutuante.GetType().Name);
Console.Read();
}
O resultado é:
Figura 2: Resultado da Reflexão Note que retornou os tipos e não o nome ou valores da propriedade. Usar reflexão não é só uma tarefa para descobrir tipos, existe a possibilidade de refletir métodos encapsulados em uma DLL e utilizar os métodos e propriedades em tempo de execução. Antes de efetuar o load de uma DLL, a reflexão permite também a geração de novas instâncias de um tipo, utilizando a classe Activator. Isso é só um exemplo das muitas possibilidades do reflection. Vamos refletir uma classe Humano para que possamos exemplificar todos os principais pontos da Reflection. Listagem 2: Classe Humano
public class Humano
{
private string TipoSanguineo { get; set; }
public int Idade { get; set; }
public int Altura { get; set; }
public Double Peso { get; set; }
public string Nome { get; set; }
public void Piscar()
{
Console.WriteLine("Piscar os olhos agora.");
}
public void Respirar()
{
Console.WriteLine("Repirar 1...2...3");
}
public void PensarAlgo(string pensamentos, DateTime quando)
{
Console.WriteLine("Estou pensando em : " + pensamentos + " pensei nisso agora : " + quando.ToShortTimeString());
}
public void SentirFome()
{
Console.WriteLine("Estou ficando com fome. Hora do Lanche.");
}
private void CantarNoBanheiro()
{
Console.WriteLine("Bla ... Bla ... Bla ...");
}
}
Para conhecer o assembly da classe Humano: Listagem 3: Carregar o Assembly var Assembly = typeof (Humano).Assembly;
Veja que o seguinte valor irá aparecer no objeto: Terra, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Onde Terra é o nome do namespace que comporta a classe Humano, a versão 1.0 é a versão do assembly no momento da compilação. O próximo passo é capturar o Type da classe, para ter acesso a todos os atributos relacionados a ela e poder manipulá-los. Listagem 4: Capturar o Type da classe Type humanoType = typeof(Humano);
Como é o do nosso conhecimento, a classe Humano pertence ao namespace Terra e fomos criteriosos ao escolher a classe Humano. Isto deve ser afirmado, pois podemos
também definir uma classe especifica dentro de um namespace a ser refletida. Por exemplo: Listagem 5: Capturar uma classe dentro do namespace var animaisType = Type.GetType("Terra.Animais");
Porém vamos seguir com o primeiro exemplo. Após ter o Type armazenado, vamos navegar por entre os métodos e propriedades da classe e capturar alguns valores da mesma. Primeiramente vamos criar uma nova instância para que seja possível a manipulação dos dados do objeto. Listagem 6: Activator auxiliando na geração de novas instâncias object newHuman = Activator.CreateInstance(humanoType);
Esta linha equivale ao operador New. Agora caso não queira trabalhar com novas instâncias de objetos e sim com um objeto já instanciado e passado para o método de reflexão, simplesmente informe o mesmo no método, assim o GetValue e o SetValue serão realizados dentro de um objeto do escopo. Para capturar todas as propriedades públicas existentes utilizamos o método GetProperties. Listagem 7: Usando o método GetProperties PropertyInfo[] properties = humanoType.GetProperties();
foreach (var propertyInfo in properties)
{
Console.WriteLine(propertyInfo.Name);
}
Console.Read();
Figura 3: Propriedades públicas da classe Para capturar uma propriedade pública existente na classe, vamos utilizar o método GetProperty. Listagem 8: Capturando uma propriedade com GetProperty PropertyInfo property = humanoType.GetProperty("Idade");
Console.WriteLine(property.GetValue(newHuman, null));
Console.Read();
Sabemos que o resultado é zero, pois estamos capturando valores de uma nova instância. Então para informar valores para o objeto utilizamos o SetValue. Listagem 9: Atribuindo valores usando o SetValue PropertyInfo propertySet = humanoType.GetProperty("Idade");
propertySet.SetValue(newHuman, 23, null);
Console.WriteLine(propertySet.GetValue(newHuman, null));
Console.Read();
Pronto, nosso Humano agora tem 23 anos de idade. Repare que devemos informar em qual objeto desejamos informar o valor, porém precisamos capturar a propriedade em que queremos que isso aconteça. Adendo, como tudo isso ocorre em tempo de execução, se preocupe em cada letra digitada, verifique se os tipos de valores são corretos, pois toda a exceção só será dada em tempo de execução. A classe Humano possui diversos métodos que são essenciais para ela, como respirar, portanto vamos invocar todas as necessidades existente.
Listagem 10: Invocar um método público humanoType.InvokeMember("Respirar", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, newHuman, null);
humanoType.InvokeMember("Piscar", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, newHuman, null);
humanoType.InvokeMember("SentirFome", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, newHuman, null);
O resultado é:
Figura 4: Os métodos públicos executados Assim como acontece com as propriedades, métodos privados não são visualizados de imediato pela reflexão, entretanto é possível executá-los, utilizando os BindingFlags apropriados para isso.
BindingFlags É um enumerador com diversas opções que servem como parâmetros para o momento da reflexão. Não é o foco do artigo, mas é importante conhecê-lo para usufruir melhor da reflexão, o link de documentação do mesmo está em Referências. Listagem 11: Acessando métodos privados humanoType.InvokeMember("CantarNoBanheiro", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, null, newHuman, null);
Repare que foi utilizado o BindingFlags.NonPublic.
Vale ressaltar que é possível aceder métodos que possuem parâmetros de entrada, como é o caso do PensarAlgo, veja: Listagem 12: Invocar o pensamento humanoType.InvokeMember("PensarAlgo", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, newHuman, new object[] { "em viajar.", DateTime.Now });
Conclusão A reflexão de modo bem cru é tudo o que fazemos, criamos instâncias usando o New, atribuímos valores pelo Set e recuperamos pelo Get. Chamamos os métodos apenas encontrando o nome, porém não acessamos de fora seus métodos privados. Porém a reflexão não para por aqui, isso é só o começo do entendimento. Podemos gerar, por exemplo, métodos com reflexão para realizar conversão de tipos, se tornando um método reutilizável. Assim como, a reflexão é utilizada para trabalhar com classes que possuem atributos, como o [Serializable] e com isso o poder na automatização aumenta.
Trabalhando com atributos de classe e Reflection em C# Facebook Twitter (2) (1)
Veja neste artigo como trabalhar com atributos de classe em C#, criando atributos customizados e acessando-os através da técnica Reflection.
Figura 1: Atributos com reflection Para aqueles que ainda não sabem ou até sabem, mas não tinham se dado conta, começamos este artigo com exemplos de atributos de classe. Listagem 1: Um Atributo de Classe [Obsolete]
public void Metodo();
[Serializable]
public class Classe();
[WebMethod]
public static void CallAjax();
Esses nomes entre chaves acima de cada nome de método ou declaração de classe são os atributos e servem para identificar e classificar classes e seus campos segundo alguns critérios, atribuindo-lhes propriedades específicas para utilização no contexto em que são utilizadas. Por exemplo, o atributo Obsolete permite definir uma mensagem indicando que o método está obsoleto e indicando qual utilizar em seu lugar. Então, assumindo que possuímos o conhecimento de reflexão, vamos partir para o entendimento de como criar atributos de classes.
Listagem 2: Criando um atributo simples [AttributeUsage(System.AttributeTargets.Method)]
public class MethodAttribute : Attribute
{
private int _Variavel1 = 0;
public string _Variavel2 = "";
public MethodAttribute()
{
}
public MethodAttribute(string metodo, string metodo1)
{
}
public void Metodo()
{
}
public void Metodo(string entrada)
{
}
}
AttributeUsage() Responsável pelas regras de como o atributo irá funcionar. Nele indicamos que o atributo servirá de assinaturas, para classes, métodos ou algumas outras opções encontradas na listagem 3. Listagem 3: AttributeTargets Enum public enum AttributeTargets
{
Assembly ,
Module,
ClasS,
Struct,
Enum,
Constructor,
Method,
Property,
Field,
Event,
Interface,
Parameter,
Delegate,
ReturnValue,
GenericParameter,
All = GenericParameter | ReturnValue | Delegate | Parameter | Interface | Event | Field | Property | Method | Constructor | Enum | Struct | Class | Module | Assembly
}
Cada opção dessas habilita uma possibilidade diferente para o atributo gerado. Além do AttributeTargets, existem outros dois parâmetros dentro de AttributeUsage, são eles:
AllowMultiple: Habilita que o atributo seja assinado mais de uma vez, dentro do escopo definido para o mesmo, o valor padrão é false. Inherited: Habilita que as classes derivadas herdem o atributo também, o valor padrão é true.
Muito rápido e simples, porém terá um grande ganho de automatização quando tudo estiver programado.
A Estrutura A estrutura proposta para esse sistema é feita em 3 camadas e uma camada de reflexão. A ideia da estrutura não está no foco.
Figura 2: Estrutura da aplicação Repare que o Reflection é o ultimo passo, pois ele será o responsável (neste caso) por efetuar o envio das informações, por isso foi dito que a estrutura não é o foco, pois a DAL e até mesmo a Bussines, neste caso, simplesmente passam as informação para a próxima camada, não realizam tarefas.
Codificando os Atributos Os atributos é a parte mais importante daqui e fácil de desenvolver. Os atributos são basicamente classes com construtores (não obrigatoriamente) e propriedades. Existem atributos mais complexos que realizam tarefas de classes robustas, mas na grande maioria os atributos serão simples. Toda classe de atributo herda de Attribute e a mesma é assinada com AttributeUsage com as definições que foram explicadas acima. Para este exemplo, foram criados dois atributos, um se chama StoredProcedureAttributes e o outro FieldsAttributes. Listagem 4: Codificando os atributos public enum ProcedureTypeCommand
{
Insert,
Delete,
Update
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class StoredProcedureAttributes : Attribute
{
public string ProcedureName { get; set; }
public ProcedureTypeCommand ProcedureType { get; set; }
public String[] ProcParams { get; set; }
public StoredProcedureAttributes(string procName, ProcedureTypeCommand procType, params String[] param)
{
ProcedureName = procName;
ProcedureType = procType;
ProcParams = param;
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class FieldsAttributes : Attribute
{
public string FieldName { get; set; }
public SqlDbType FieldType { get; set; }
public int Length { get; set; }
public object Value { get; set; }
public FieldsAttributes()
{
}
public FieldsAttributes(string FieldName, SqlDbType FieldType)
{
this.FieldName = FieldName;
this.FieldType = FieldType;
}
public FieldsAttributes(string FieldName, SqlDbType FieldType, int Length)
{
this.FieldName = FieldName;
this.FieldType = FieldType;
this.Length = Length;
}
}
Simples assim foram definidos dois atributos, um é assinatura de método e o outro como assinatura de propriedades. Caso tente colocar um atributo definido para propriedade em uma classe, por exemplo, o interpretador e o compilador Visual Studio irá exibir um erro.
O atributo StoredProcedureAttributes, recebe os dados do Procedure do banco de dados, o tipo de evento DML que ele realiza e os parâmetros existentes no procedure (obrigatórios ou não). O atributo FieldsAttributes recebe o nome do Parâmetro correspondente na entrada do procedure, o tipo de dado que a coluna foi definida no banco de dados e o length (para Varchar, Char e etc) para definir o tamanho da informação máxima que o parâmetro pode conter.
Codificando as Camadas As camadas não tem mistério, com exceção da camada de Entity. Nesta camada estão todas as assinaturas dos atributos e é por ela que o Reflection consegue definir o tipo de ação a ser realizada e os parâmetros passados.
Entity A Entity é um espelho da tabela de dados e possui as regras de “DML” amarradas a ela. Nesta camada estão as definições das assinaturas. Listagem 5: Entidade e os atributos public class Titulos
{
#region Propriedades
private int _id;
private string _nome;
private int _idCategoria;
private string _categoria;
private string _duracaoFilme;
[FieldsAttributes("@ID", System.Data.SqlDbType.Int)]
public int ID
{
get { return _id; }
set { _id = value; }
}
[FieldsAttributes("@NOME", System.Data.SqlDbType.VarChar, 30)]
public string Nome
{
get { return _nome; }
set { _nome = value; }
}
[FieldsAttributes("@IDCATEGORIA", System.Data.SqlDbType.Int)]
public int IdCategoria
{
get { return _idCategoria; }
set { _idCategoria = value; }
}
[FieldsAttributes("@CATEGORIA", System.Data.SqlDbType.VarChar, 30)]
public string Categoria
{
get { return _categoria; }
set { _categoria = value; }
}
[FieldsAttributes("@DURACAOFILME", System.Data.SqlDbType.VarChar, 20)]
public string DuracaoFilme
{
get { return _duracaoFilme; }
set { _duracaoFilme = value; }
}
#endregion
private TitulosBLL titulosBLL;
public Titulos()
{
titulosBLL = new TitulosBLL();
}
#region Metodos Assinados
[StoredProcedureAttributes("STP_INS_TITULOS", ProcedureTypeCommand.Insert, "@NOME", "@IDCATEGORIA", "@CATEGORIA", "@DURACAOFILME")]
public void Salvar()
{
titulosBLL.Salvar(this);
}
[StoredProcedureAttributes("STP_DEL_TITULOS", ProcedureTypeCommand.Delete, "@ID")]
public void Deletar()
{
titulosBLL.Deletar(this);
}
#endregion
}
Veja como ficaram os atributos criados, em cima das propriedades apenas o atributo para propriedade e o mesmo para os métodos. Toda a declaração que seria feita na camada de DAL foi transportado para a entidade, isso é um exemplo para a funcionalidade do Reflection. Observação: Muitos devotos por patterns e outros, com certeza não aprovariam um modelo desses.
Camada de Reflection
A última camada do escopo realiza a reflexão do objeto passado pela entidade e captura as demais informações para realizar a ação em questão (salvar, deletar). Primeiramente vamos entender alguns métodos de reflexão dessa classe. O GetFieldsAttributes é um método responsável por capturar do objeto passado para a camada todas as propriedades as quais levam a assinatura definida anteriormente. Listagem 6: Código do GetFieldsAttributes private void GetFieldsAttributes(System.Type type, object obj)
{
ListFields.Clear();
foreach (var propertyInfo in type.GetProperties())
{
foreach (Attributes.FieldsAttributes customAttribute in propertyInfo.GetCustomAttributes(typeof(Attributes.FieldsAttributes), false))
{
customAttribute.Value = propertyInfo.GetValue(obj, null);
ListFields.Add(customAttribute);
}
}
}
O GetStoredProcedureAttributes, por sua vez, é o método responsável por capturar do objeto passado para a camada todas as propriedades as quais levam a assinatura de StoredProcedureAttributes.
Listagem 7: GetStoredProcedureAttributes private void GetStoredProcedureAttributes(Type type)
{
listProcMethods.Clear();
foreach (var methodInfo in type.GetMethods())
{
foreach (Attributes.StoredProcedureAttributes storedProcedureAttributes in methodInfo.GetCustomAttributes(typeof(Attributes.StoredProcedureAttrib utes), false))
{
listProcMethods.Add(storedProcedureAttributes);
}
}
}
Eles juntam as informações em listas distintas definidas na classe como static. Essas listas de informações são necessárias para a filtragem dos parâmetros corretos na hora do envio das informações. O evento Salvar recebe uma variável genérica para permitir receber qualquer objeto dentro do escopo definido. Sem mais delongas, segue: Listagem 8: Método Salvar public void Salvar(T obj)
{
var type = typeof(T);
GetFieldsAttributes(type, obj);
GetStoredProcedureAttributes(type);
Attributes.StoredProcedureAttributes StoredProcedure = listProcMethods.First(p => p.ProcedureType == Attributes.ProcedureTypeCommand.Insert);
Salvar(StoredProcedure);
}
private void Salvar(Attributes.StoredProcedureAttributes storedProcSalvar)
{
SqlCommand cmd = new SqlCommand();
try
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = storedProcSalvar.ProcedureName;
cmd.Connection = GetConnection();
cmd.Parameters.Clear();
foreach (string procParam in storedProcSalvar.ProcParams)
{
var field = ListFields.First(whe => whe.FieldName == procParam);
cmd.Parameters.Add(new SqlParameter(field.FieldName, field.FieldType, field.Length) { Value = field.Value });
}
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
finally
{
cmd.Connection.Close();
}
}
Observação: Na linha onde é feito o First para definir qual o procedure deve ser utilizado, poderia ser feito de outra forma, utilizando atributos específicos para cada evento. Veja como é simples e reutilizável tudo isso. A reflection é capaz de ler os atributos da classe e os valores das propriedades definidos no objeto, minerando toda essa informação e facilitando na automatização do processo de envio.
Essa camada, nesse exemplo, entende os parâmetros que o procedure receberá e e dentro do foreach é feita a separação do que é preciso para a chamada do procedure.
Conclusão Os atributos são uma vantagem para as aplicações, com eles conseguimos automatizar processos rotineiros, encapsular e reutilizar tarefas repetitivas para todos os desenvolvedores de uma célula. Neste caso do CRUD facilitará em constantes acessos ao banco de dados e em termos de produtividade gera um ganho, pois é possível reutilizar os atributos e a classe de reflexão em qualquer projeto. Portanto pesquisem mais ainda sobre o assunto. Baixem o código fonte para ver o funcionamento! Foi disponibilizado uma base dentro do App_Data, caso a mesma falhe, utilize o script de banco de dados e stored procedures disponibilizado dentro do arquivo zip, não se esqueça de editar o web.config para o endereço do banco corretamente.
Mesclando Objetos em C# com Reflection Facebook Twitter (2) (0)
Veja neste artigo como “mesclar” dois objetos da mesma classe utilizando Reflection no C#, gerando um terceiro objeto resultante da união dos dois primeiros. Vamos imaginar a seguinte situação: duas lojas de uma mesma rede que não se comunicam entre si e, por este motivo, cadastraram o mesmo cliente, uma vez em cada loja. Na Loja A, o cliente informou apenas o CPF, o Nome e o Telefone. Em outro dia, o cliente precisou realizar uma compra na Loja B e lá informou apenas o CPF, o Nome e o Email. Em certo momento, tornou-se necessário sincronizar os cadastros e então surgiu a questão: que cadastro devemos considerar como “melhor”? O da Loja A ou o da Loja B. Reparem que se consideramos apenas o cadastro da Loja A, perderemos a informação do Email do cliente e, considerando apenas o da Loja B, perderemos a
informação do Telefone. Diante dessa situação, percebemos que o ideal seria “mesclar” os dois cadastros, preenchendo as informações em branco de uma loja com as informações existentes na outra, obtendo um cadastro único. Existe ainda mais uma informação importante com relação ao cadastro. O cliente se chama “Joel Rodrigues de Lima Neto”, porém, na Loja A, ele informou apenas “Joel Rodrigues”. Já na Loja B, o cliente cadastrou seu nome completo, “Joel Rodrigues de Lima Neto”. Nesse caso, o cadastro da Loja B tem prioridade sobre o cadastro da Loja A, ou seja, no caso de haver a informação preenchida nas duas lojas, deve-se manter o conteúdo da Loja B. Bem, vejamos agora na prática como fazer essa “mescla” em C#, utilizando reflection. Primeiramente, vejamos o código da classe Cliente: Listagem 1: Classe Cliente public class Cliente
{
private String _nome;
private String _telefone;
private String _cpf;
private String _email;
public String CPF
{
get { return _cpf; }
set { _cpf = value; }
}
public String Email
{
get { return _email; }
set { _email = value; }
}
public String Nome
{
get { return _nome; }
set { _nome = value; }
}
public String Telefone
{
get { return _telefone; }
set { _telefone = value; }
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Nome: " + this._nome);
sb.AppendLine("Telefone: " + this._telefone);
sb.AppendLine("CPF: " + this._cpf);
sb.AppendLine("Email: " + this._email);
return sb.ToString();
}
}
Sobrecarregamos o método ToString apenas paraa nível de verificação. Após mesclar os dois objetos, chamaremos o método ToString para verificar os valores das propriedades do novo objeto. Agora vejamos o código do método MesclarObjetos: Listagem 2: Método MesclarClientes public object MesclarObjetos(object p1, object p2)
{
if(p1.GetType() != p2.GetType())
{
throw new Exception("Os parâmetros deve ser do mesmo tipo.");
}
Object p3 = Activator.CreateInstance(p1.GetType());
foreach (PropertyInfo p in p1.GetType().GetProperties())
{
object v1 = p1.GetType().GetProperty(p.Name).GetValue(p1, null);
object v2 = p2.GetType().GetProperty(p.Name).GetValue(p2, null);
object v3 = (v1 == null || v1.ToString() == "" || v1.ToString() == "0"? v2 : v1);
p3.GetType().GetProperty(p.Name).SetValue(p3, v3, null);
}
return p3;
}
Bastante simples, não é mesmo? Primeiramente, verificamos se os dois parâmetros são do mesmo tipo. Caso isso não se verifique, geramos uma exceção e encerramos o método. Depois, criamos o objeto p3 do mesmo tipo do p1 (que é o mesmo do p2). Feito isso, verificamos cada propriedade dos objetos p1 e p2 e preenchemos o p3. Para cada iteração, v1 é o valor da propriedade de p1 e v2 é o valor da propriedade de p2. Caso v1 seja nulo, vazio ou zero, passamos para v3 (que é a propriedade de p3) o valor de v2, caso contrário, passamos o valor de v1. Vale observar que, com essa estrutura, o objeto que é passado como primeiro parâmetro é priorizado na mescla dos valores conforme discutimos anteriormente. Agora que já conhecemos como o método funciona, vamos testá-lo. Listagem 3: Testando o método Cliente c1 = new Cliente()
{
Nome = "Joel Rodrigues",
CPF = "000.000.000-00",
Telefone = "(000)0000-0000"
};
Cliente c2 = new Cliente()
{
Nome = "Joel Rodrigues de Lima Neto",
CPF = "000.000.000-00",
Email = "[email protected]"
};
Cliente c3 = (Cliente)MesclarObjetos(c2, c1); MessageBox.Show(c3.ToString());
O conteúdo do MessageBox deve ser o seguinte: Listagem 4: Resultado da mescla dos objetos Nome: Joel Rodrigues de Lima Neto
Telefone: (000)0000-0000
CPF: 000.000.000-00
Email: [email protected]
Bem, como vimos, o método é bem simples e isso só é possível graças aos recursos do Reflection do .NET.
Avançando na linguagem Para lhe ajudar a seguir evoluindo no aprendizado do C#, indo além dos recursos mais básicos e fundamentais, separamos aqui um curso que apresenta conceitos avançados dessa linguagem:
Orientação a Objetos Normalmente, quando começamos a programar, o primeiro paradigma que conhecemos é o estruturado. Ele facilita o aprendizado por não trazer consigo tantos conceitos, como os que fazem parte da Orientação a Objetos. Imagine começar a criar algoritmos e, ao mesmo tempo, conciliar conceitos abstratos para poder programar as primeiras soluções? Não seria fácil. Portanto, é natural percorrer o caminho: Programação Estruturada -> Programação Orientada a Objetos. O único porém desse processo é que algumas vezes acabamos levando algumas características da primeira para a segunda, características essas que podem prejudicar um pouco o nosso código. Pensando nessa etapa de transição, preparamos o seguinte artigo:
Programação Orientada a Objetos versus Programação Estruturada Facebook Twitter (20) (0)
Vamos abordar neste artigo qual tipo de programação deve ser utilizada no decorrer do desenvolvimento: orientada a objetos ou programação estruturada. Vamos abordar neste artigo os dois tipos de programação bem conhecidos e que ainda geram algumas dúvidas em suas utilizações e definições, que é a programação orientada a objetos e a programação estruturada. Existem dois tipos de programação bem usuais: Orientada a Objetos (OO) e Estruturada. Em cada uma delas iremos conhecer os seus principais conceitos e suas respectivas utilizações e, por fim, mostraremos um breve comparativo entre esses dois tipos e qual o melhor a ser utilizado quando um projeto de desenvolvimento de software for iniciado. Utilizaremos como linguagem de programação base para os nossos exemplos as linguagens Java e C#. Quer aprender mais sobre Orientação a objetos em Java? Confira o curso de Java básico orientado a objetos da DevMedia.
Programação Orientada a Objetos A programação orientada a objetos é um modelo de programação onde diversas classes possuem características que definem um objeto na vida real. Cada classe determina o comportamento do objeto definido por métodos e seus estados possíveis definidos por atributos. São exemplos de linguagens de programação orientadas a objetos: C++, Java, C#, Object Pascal, entre outras. Este modelo foi criado com o intuito de aproximar o mundo real do mundo virtual. Para dar suporte à definição de Objeto, foi criada uma estrutura chamada Classe, que reúne objetos com características em comum, descreve
todos os serviços disponíveis por seus objetos e quais informações podem ser armazenadas. Vamos começar exemplificando uma classe carro utilizando a linguagem C#, como mostra a Listagem 1. Listagem 1. Classe Carro class Carro {
string marca; // Define a marca do carro
string modelo; // Define o modelo do carro
int ano; // Define o ano de fabricação do carro
string categoria; // Define a categoria, por exemplo carro de passeio, utilitário...
double potencia; // Define a potência do carro
double autonomia; // Define a autonomia em km
boolean ligado; // Define se o carro está ligado
public Carro() {
}
public Carro(string marca, string modelo, int ano, string categoria, double potencia, double autonomia, boolean ligado) {
this.marca = marca;
this.modelo = modelo;
this.ano = ano;
this.categoria = categoria;
this.potencia = potencia;
this.autonomia = autonomia;
this.ligado = ligado;
}
}
A classe “Carro” contém um conjunto de características em comum que definem um objeto do tipo carro: Marca, modelo, ano, categoria, potência e autonomia são atributos que todos os carros precisam ter. Temos também dois construtores para a classe, ou seja, os responsáveis por criar o objeto em memória, onde um é inicializa o objeto carro e o outro, além da inicialização do objeto, define que o preenchimento dos parâmetros seja obrigatório nos atributos da classe. Agora vamos criar um objeto para que o mesmo possa ser utilizado, como mostra a Listagem 2. Listagem 2. Invocando o construtor e criando um objeto class Program {
public static void main(String[] args) {
// Instanciando a classe carro a partir do construtor sem a passagem de parâmetros
Carro meuCarro = new Carro();
// Atribuindo valores aos atributos que compõem a classe carro
meuCarro.marca = “Fiat”;
meuCarro.modelo = “Palio”;
meuCarro.ano = 2013;
meuCarro.categoria = “Passeio”;
meuCarro.potencia = 86.7;
meuCarro.autonomia = 320.6;
meuCarro.ligado = false;
}
}
Instanciamos nossa classe Carro e atribuímos a uma variável “meuCarro” todos os atributos da nossa classe. Poderíamos também invocar o construtor que já recebe os parâmetros com os respectivos atributos da classe, como mostra a Listagem 3. Listagem 3. Invocando o construtor com parâmetros e criando o objeto class Program {
public static void main(String[] args) {
// Instanciando a classe carro a partir do construtor que contêm os devidos parâmetros
Carro meuCarro = new Carro("Fiat", "Palio", 2013, "Passeio", 86.7, 420.5, false);
}
}
Na Listagem 4 criaremos alguns métodos, que é são comportamentos que a classe Carro possui. Listagem 4. Métodos da classe Carro
void Ligar() {
// Atribui true a propriedade ligado
this.ligado = true;
System.out.println("Carro ligado!");
}
boolean Anda(double autonomia) {
// Condição que verifica se o carro está ligado e se autonomia é maior que 0,
// retornando verdadeiro caso a condição seja satisfeita, ou falso caso contrário
if (this.ligado && autonomia > 0) {
return true;
} else {
return false;
}
}
void Desligar() {
// Atribui false a propriedade ligado
this.ligado = false;
System.out.println("Carro desligado!");
}
No método “Liga” atribuímos ao atributo “ligado” o valor verdadeiro e escrevemos no console de saída uma informação que o carro foi ligado. No método “Anda” temos uma condição onde se o carro estiver ligado e a autonomia do carro for maior que zero, o carro ainda pode andar. Caso contrário, se a autonomia for menor ou igual a zero ou então o carro esteja desligado, o mesmo fica impossibilitado de andar. E por último temos o método “Desliga”, que atribui o valor falso ao atributo “ligado” e escrevemos no console de saída a mensagem que o carro foi desligado. Veja que para utilizar esses métodos presentes na classe Carro precisamos utilizar dois conceitos cruciais: Construtores e Destrutores. Construtores são métodos invocados no momento em que o objeto é criado: por convenção é utilizado o mesmo nome da classe como, por exemplo, para a classe Carro temos o construtor Carro(). Já os Destrutores realizam a função inversa aos Construtores, onde são liberados da memória todos os objetos que foram criados no momento da execução do programa, na maioria das linguagens de programação esse método é executado automaticamente quando o objeto é eliminado e, por convenção, é utilizado também o mesmo nome da classe, porém com um “~” antecedendo o nome como, por exemplo, para a classe Carro temos o destrutor ~Carro(). Agora que já conhecemos um pouco sobre orientação a objetos, vamos conhecer seus princípios básicos
Abstração Este princípio é uma forma de abstrair o quão complexo é um método ou rotina de um sistema, ou seja, o usuário não necessita saber todos os detalhes de como sua implementação foi realizada, apenas para que serve determinada rotina e qual o resultado esperado da mesma. Sendo assim, podemos também dividir internamente problemas complexos em problemas menores, onde resolvemos cada um deles até encontrarmos a solução do problema inteiro. Um exemplo da vida real para ilustrar esse conceito seria o conceito de carro a abstração de um veículo, que é utilizado como meio de transporte por várias pessoas para mover-se de um ponto a outro. Não é necessário que a pessoa informe que irá se locomover com a ajuda de um veículo movido a combustível, contendo rodas e motor. Basta a pessoa informar que utilizará um carro para tal, pois esse objeto é conhecido por todos e abstrai toda essa informação por trás disso. Veja na Listagem 5 um exemplo de abstração utilizando a linguagem Java.
Listagem 5. Exemplo de abstração public class Conta {
int cod_banco;
int num_conta;
double saldo;
double limite;
void ConsultarSaldo() {
System.out.println(“Conta: ” + this.num_conta);
System.out.println(“Saldo: ” + this.saldo);
}
void Depositar(double valor) {
this.saldo = this.saldo + valor;
}
void Sacar(double valor) {
this.saldo = this.saldo - valor;
}
}
Para este caso, um cliente só precisa entender que uma conta é um local, em um determinado banco, onde é possível ser depositado e sacado valores.Para exemplificar este caso, criamos uma classe Conta com os atributos: código do banco, número da conta, saldo e limite. Criamos também um método “ConsultarSaldo”, onde ele retorna qual o saldo da conta naquele momento. Criamos também outro método chamado “Depositar” onde passamos um valor como parâmetro e ele soma esse ao saldo atual da conta. Outro método chamado “Sacar” foi criado com um valor passado por parâmetro, onde o mesmo subtrai esse valor do saldo atual da conta.
Encapsulamento O princípio do encapsulamento é a forma pela qual o programa é divido a ponto de se tornar o mais isolado possível, ou seja, cada método pode ser executado isoladamente e retornar um resultado satisfatório ou não para cada situação. Sendo assim, o objeto não necessita conhecer qual forma cada método foi implementado. Vejamos na Listagem 6 um exemplo prático de encapsulamento, onde é possível obter e atribuir valores a propriedades da classe Pessoa de forma independente, sem alterar o funcionamento do restante da classe. Listagem 6. Exemplo de encapsulamento public class Pessoa {
private String nome; // Define o nome da pessoa
private String cpf; // Define o cpf da pessoa
private Date dat_nasc; // Define a data de nascimento da pessoa
// Obtem o nome da pessoa
public String getNome() {
return nome;
}
// Atribui o nome a pessoa
public void setNome(String nome) {
this.nome = nome;
}
// Obtem o cpf da pessoa
public String getCpf() {
return cpf;
}
// Atribui o cpf a pessoa
public void setCpf(String cpf) {
this.cpf = cpf;
}
// Obtem a data de nascimento da pessoa
public Date getDatNasc() {
return dat_nasc;
}
// Atribui a data de nascimento a pessoa
public void setDatNasc(Date dat_nasc) {
this.dat_nasc = dat_nasc;
}
}
Herança Esse princípio diz que, uma classe pode compartilhar métodos e atributos entre si como, por exemplo, em um sistema escolar, onde temos uma classe Pessoa que contém características que definem uma pessoa. Porém, dentro do sistema temos uma outra classe Funcionário, que contém os mesmos atributos de Pessoa, além de outros atributos que apenas funcionários podem ter. Outro exemplo seria uma classe Aluno, que também contém atributos de pessoas e outros atributos que apenas pertencem a aluno. Vejamos na Listagem 7 como podemos implementar esse princípio utilizando a linguagem de programação Java. Listagem 7. Implementando o princípio da herança public class Pessoa {
public String nome; // Define o nome da pessoa
public String cpf;
// Define o cpf da pessoa
public Date data_nascimento; // Define a data de nascimento da pessoa
// Construtor da classe Pessoa, passando todos os seus atributos
public Pessoa(String _nome, String _cpf, Date _data) {
this.nome = _nome;
this.cpf = _cpf;
this.data_nascimento = _data;
}
}
// Classe Aluno que herda da classe Pessoa
public class Aluno extends Pessoa {
// Herda os atributos da classe super
public Aluno(String _nome, String _cpf, Date _data) {
super(_nome, _cpf, _data);
}
public String matricula; // Define a matricula do Aluno
}
public class Professor extends Pessoa {
// Herda os atributos da classe super
public Professor(String _nome, String _cpf, Date _data) {
super(_nome, _cpf, _data);
}
public double salario; // Define o salário do professor
public String disciplina; // Define a disciplina do professor
}
public class Funcionario extends Pessoa {
// Herda os atributos da classe super
public Funcionario(String _nome, String _cpf, Date _data) {
super(_nome, _cpf, _data);
}
public double salario; // Define o salário do funcionário
public Date data_admissao; // Define a data de admissão do funcionário
public String cargo; // Define o cargo do funcionário
}
Note que temos uma classe Pessoa que contém propriedades em comum com as classes Aluno, Professor e Funcionário. Essas outras classes que herdam de Pessoa recebem a palavra reservada “extends”, que indica que as mesmas contêm as propriedades nome, cpf e data, presentes na classe Pessoa.
Polimorfismo Polimorfismo é uma característica na qual os mesmos atributos ou métodos podem ser utilizados por objetos distintos e com implementações de lógica diferentes. Por exemplo, podemos dizer que um carro e uma bicicleta são veículos utilizados para locomoção e ambos contêm um método “Andar” em comum, porém, a implementação de cada um deles é feita de forma diferente. Vamos exemplificar a implementação de uma classe Forma, onde a mesma contém um método Desenhar, como mostra a Listagem 8.
Listagem 8. Classe Forma public abstract class Forma
{
public Forma()
{
}
public virtual void Desenhar(System.Drawing.Graphics g)
{
}
}
Note que a classe Forma é uma classe abstrata e que o método desenhar não tem implementação, pois cada figura tem uma forma diferente de ser desenhada. Porém, é nas classes derivadas da classe Forma que esse método será implementado e, por isso, essa nova classe será uma classe virtual, como mostra a Listagem 9. Listagem 9. Classe Circulo using System.Drawing;
public class Circulo : Forma
{
// Implementa o método desenhar baseado na forma Circulo
public override void Desenhar(System.Drawing.Graphics g)
{
base.Desenhar (g);
g.DrawEllipse(Pens.Red, 5, 5, 100, 100);
}
}
Veja agora que a classe Circulo herda da classe Forma, portanto o método Desenhar deve ser implementado. Partindo desse princípio podemos ter diversas classe diferentes que herdem da classe Forma e implementem de outra forma o método Desenhar, como mostra a Listagem 10. Listagem 10. Classe Quadrado public class Quadrado : Forma
{
// Implementa o método desenhar baseado na forma Quadrado
public override void Desenhar(System.Drawing.Graphics g)
{
base.Desenhar (g);
g.DrawRectangle(Pens.Green, 5, 5, 100, 100);
}
}
Classes abstratas são classes que jamais serão instanciadas diretamente, pois não possuem implementação suficiente para oferecer funcionalidades concretas a serem utilizadas.
Programação Estruturada
O princípio básico da programação estruturada é que um programa pode ser divido em três partes que se interligam: sequência, seleção e iteração.
Sequência Na sequência são implementados os passos de processamento necessários para descrever determinada funcionalidade. Um exemplo básico seria um fluxograma, onde primeiro é executado a Etapa 1 e após a sua conclusão a Etapa 2 é executada, e assim por diante.
Seleção Na seleção o fluxo a ser percorrido depende de uma escolha. Existem duas formas básicas para essa escolha.
A primeira é através do condicional “Se”, onde se uma determinada condição for satisfatória o fluxo a ser corrido é um e, caso contrário, o fluxo passa a ser outro. Ou seja, se o fluxo só percorre apenas um caminho, apenas uma ação é processada. A outra forma de escolha é onde o número de condições se estende a serem avaliadas. Por exemplo, se a Condição 1 for verdade faça Processamento 1, caso contrário, se a Condição 2 for verdade faça Processamento 2, caso contrário, se a Condição 3 for verdade faça Processamento 3, e assim por diante.
Iteração Na iteração é permito a execução de instruções de forma repetida, onde ao fim de cada execução a condição é reavaliada e enquanto seja verdadeira a execução de parte do programa continua.
Modularização
A medida que o sistema vai tomando proporções maiores, é mais viável que o mesmo comece a ser divido em partes menores, onde é possível simplificar uma parte do código deixando a compreensão mais clara e simplificada. Essa técnica ficou conhecida como Subprogramação ou Modularização. No desenvolvimento utilizamos essa técnica através de procedimentos, funções, métodos, rotinas e uma série de outras estruturas. Com essa divisão do programa em partes podemos extrair algumas vantagens, como:
Cada divisão possui um código mais simplificado; Facilita o entendimento, pois as divisões passam a ser independentes; Códigos menores são mais fáceis de serem modificados; Desenvolvimento do sistema através de uma equipe de programadores; Reutilização de trechos de códigos.
Hoje as linguagens estruturadas, como Algol, Pascal e C, ainda são encontradas em muito sistemas, apesar das linguagens Orientadas a objetos serem mais usuais. Para exemplificar esse tipo de programação, vamos ver um exemplo em C na Listagem 11. Listagem 11. Exemplo de Programação estruturada # include
int main()
{
int soma, n=1;
soma = 0;
// inicialização da variável soma
for (n=1; n<=100; n++)
soma= soma + n; // atualização da variável soma
printf("O valor da soma = %d\n",soma);
return 0;
}
Portanto, como vimos no decorrer do artigo, a programação orientada a objetos define uma programação voltada aos conceitos de classes e herança e, em contrapartida, a programação estruturada define uma programação voltada a procedimentos e funções definidas pelo usuário. Vejamos na Tabela 1 um breve comparativo entre programação orientada a objetos e programação estruturada.
Programação orientada a objetos
Programação estruturada
Métodos
Procedimentos e funções
Instâncias de variáveis
Variáveis
Mensagens
Chamadas a procedimentos e funções
Classes
Tipos de dados definidos pelo usuário
Herança
Não disponível
Polimorfismo
Não disponível
Tabela 1. Comparativo entre programação orientada a objetos e programação estruturada. Na Programação estruturada observamos algumas vantagens como um controle mais eficaz quanto ao fluxo de execução do programa e a facilidade de compreender o código quando o mesmo é analisado. Como principal desvantagem temos a facilidade em desenvolver um código confuso caso o desenvolvedor faça o tratamento dos dados juntamente com o restante da execução do programa, além do que o reuso de código se torna um pouco complicado devido a não definição da tarefa. Já na programação orientada a objetos temos como vantagens a reutilização de código e a melhor organização do código do programa. Porém, alguns detalhes tornam essa programação um pouco prejudicada quando comparada a programação estruturada como, por exemplo, o desempenho do código e a confusão na hora de aplicar os conceitos de orientação a objetos. Portanto, não podemos assumir que um tipo de programação é mais vantajoso que o outro, pois em ambos existem características bem peculiares nas suas definições,
deixando cada uma mais viável de ser implementada, dependendo do negócio escolhido, para facilitar o bom entendimento e a manutenibilidade futura do código feito. Manutenibilidade é uma característica inerente a um projeto de sistema ou produto, e se refere à facilidade, precisão, segurança e economia na execução de ações de manutenção nesse sistema ou produto.
Fundamentos básicos da Orientação a Objetos Facebook Twitter (7) (0)
Este artigo trata sobre os principais fundamentos da orientação a objetos em C# explicando os conceitos de abstração, herança e polimorfismo. De que se trata o artigo
Este artigo trata sobre os principais fundamentos da orientação a objetos em C#, explicando os conceitos de abstração, herança, polimorfismo além de falar sobre a implementação de alguns destes fundamentos em C#, assim como palavras reservadas do mesmo que incidem sobre estes conceitos.
Em que situação o tema é útil Os tópicos abordados neste artigo poderão ser úteis no dia a dia de todo desenvolvedor, pois abordará Fundamentos básicos de Orientação a Objetos Neste artigo veremos os principais conceitos e fundamentos da orientação a objetos aplicados no C#. Além disso, veremos o uso das palavras chaves virtual, override, abstarct, sealed, partial e dos modificadores de acesso public, protected, internal, protected internal e private.
Atualmente existem dois paradigmas principais de desenvolvimento de software na mente dos desenvolvedores e no mercado. Temos o paradigma estruturado, que antes dominava o mercado, e o paradigma da orientação a objetos (O.O), que cada vez mais vai tomando conta do mercado. Normalmente, profissionais mais antigos estão habituados com o paradigma estruturado e os mais novos com o paradigma O.O, mas é claro que isso não é uma regra. O fato é que a orientação a objetos vem tendo sua adoção numa crescente no mercado, mas é fato também que na maioria das vezes ela é subutilizada. É muito comum vermos equipes de desenvolvimento que desconhecem alguns fundamentos básicos da orientação a objetos, que são os pilares da mesma. A ideia deste artigo é apresentar alguns dos conceitos mais básicos e fundamentais da orientação a objetos, com exemplos pontuais e aplicados ao C#. Vamos aproveitar também para falar sobre algumas palavras reservadas do C# que estão de certa forma relacionadas aos conceitos que vamos apresentar. Para começar, vamos traçar um breve comparativo, sobre os paradigmas estruturados e orientado a objetos.
Programação estruturada VS Programação Orientada a Objetos Antigamente, há algumas décadas atrás, o paradigma que predominava no desenvolvimento de software era o da programação estruturada. Basicamente, a grosso modo, os softwares eram compostos por rotinas e sub-rotinas que chamavam umas às outras, além de variáveis que tinham escopo local (dentro de rotinas / sub-rotinas) ou global. Assim como todo paradigma, o paradigma estruturado tinha seus prós e contras e foi bastante eficiente no que se propôs durante seus anos de domínio no mercado, além de ter sido bastante importante para a evolução da engenharia de desenvolvimento de software. Com o passar dos anos, surgiu o paradigma da orientação a objetos, que também não é tão novo como muitos pensam, mas que veio a ganhar mais força na última década. A orientação a objetos surgiu com o objetivo de tornar o desenvolvimento de software menos complexo e mais produtivo. A idéia era termos estruturas de dados, que possuem estado e comportamento e colaboram entre si. Dessa forma, deixaríamos de ter todas as rotinas e sub-rotinas “espalhadas” pelo sistema e passamos a atribuir elas a uma dessas estruturas de dados, de forma coesa, cada qual com sua responsabilidade. Além disso, encapsularíamos as variáveis nestas mesmas estruturas, controlando o acesso às mesmas e tornando público apenas aquilo que for pertinente. O paradigma da orientação a objetos possui três conceitos fundamentais: • Encapsula mento – Prevê o isolamento a determinados elementos do objeto (métodos /atributos) de acordo com a necessidade de acesso a eles. Este conceito parte da
premissa de que nem todo método e atributo precisam estar visíveis e acessíveis publicamente. Existem elementos que são pertinentes apenas ao próprio objeto, outros pertinentes aos objetos filhos e outros que são pertinentes todos os objetos associados. O encapsula mento se dá através dos modificadores de acesso, que veremos mais a frente. • Abstração – É a capacidade de focar nos pontos mais importantes do domínio de aplicação do sistema e abstrair os detalhes menos relevantes. Na modelagem de um sistema orientado a objetos, uma classe tende a ser a abstração de entidades existentes no mundo real (domínio da aplicação). Ex.: Cliente, Funcionário, Conta Bancária. • Polimorfismo – É a capacidade de um elemento ser capaz de assumir diferentes formas. Na orientação a objetos, chamamos de polimorfismo a possibilidade que temos de mudar o comportamento de um mesmo método de um objeto dentro da sua hierarquia. Além disso, podemos citar os seguinte elementos com sendo os alguns dos principais da orientação a objetos: • Classe – Uma classe é um tipo de dado que representa tudo aquilo que um objeto deste tipo poderá ter/fazer. Na classe determinamos o que será armazenado em seu estado e quais comportamentos ele terá, ela funciona como uma estrutura de referência para a criação de objetos. Uma classe pode ter vários objetos. • Objeto – Um objeto é uma instância de uma classe. É a estrutura completa, criada em memória, que irá representar a classe com tudo o que foi definido nela, inclusive com os valores armazenados nos seus respectivos atributos. • Atributos – Os atributos representam o estado de um objeto. É neles que armazenaremos as informações de nossos objetos. Ex.: Nome, Idade, Endereço etc... • Métodos – Representam os comportamentos (operações) de nossos objetos, são as operações que o mesmo pode executar.Ex.: ValidarCPF, AprovarCredito, LiberarPagamento etc...
Figura 1. Representação Gráfica de classe com muitos objetos Na Figura 1, podemos ver que uma classe pode ser instanciada diversas vezes, dando origem a diversos objetos diferentes.
Tipos de Referência x Tipos de Valor Em C# nós temos duas classificações de tipos de dados. Que são os tipos de referência (References Types) e os tipos de valor (Value Types). A diferença chave entre os dois tipos é a na passagem de valores dos mesmos. No caso dos reference types, os valores dos objetos não são copiados, mas apenas sua referência, enquanto que nos value types os valores são copiados de um objeto para o outro. Todos os objetos que são do tipo de uma classe ou interface são reference types. Tipos enumerados e tipos primitivos são value types. Ao atribuirmos uma referência de value type a outra, estamos literalmente copiando o seu valor, replicando o mesmo para o novo elemento. Ao atribuirmos uma referência de um reference type para outro, não há cópia de valores, mas apenas de suas referências. Listagem 1. Exemplo de Value Types
01
using System;
02
using System.Collections.Generic;
03
using System.Linq;
04
using System.Text;
05
using ExemplosFundamentos.Modificadores;
06
using ExemplosFundamentos.AbstractExemplo;
07
using ExemplosFundamentos.PartialClass;
08
09
namespace ExemplosFundamentos
10
{
11
class Program
12
{
13
static void Main(string[] args)
14
{
15
double valorA = 10;
16
double valorB = 20;
17
Console.WriteLine("valorA : " + valorA);
18
Console.WriteLine("valorB : " + valorB);
19
Console.WriteLine("Copiando valor de A para
B...");
20
valorB = valorA;
21
Console.WriteLine("valorA : " + valorA);
22
Console.WriteLine("valorB : " + valorB);
23
Console.WriteLine("Alterando valor de A para
50...");
24
valorA = 50;
25
Console.WriteLine("valorA : " + valorA);
26
Console.WriteLine("valorB : " + valorB);
27
Console.ReadLine();
28
}
29
30
}
}
"
Entendendo a Orientação a Objetos Facebook Twitter (0) (0)
O artigo apresentará uma breve introdução à orientação a objetos e padrões de projetos com foco em desenvolvedores que estão começando a programar ou desenvolvedores que não programam orientado a objetos
Atenção: esse artigo tem um vídeo complementar. Clique e assista! De que se trata o artigo
O artigo apresentará uma breve introdução à orientação a objetos e padrões de projetos com foco em desenvolvedores que estão começando a programar ou desenvolvedores que não programam orientado a objetos. Em que situação o tema é útil A programação orientada a objetos é um dos principais requisitos de conhecimento de um programador atualmente, presente na maioria das linguagens modernas e atualizadas. A programação orientada a objetos serve para permitir diversas melhorias em um projeto. Um dos grandes desafios durante a construção de um projeto é que ele seja flexível para receber novas funcionalidades, escalável, com códigos reutilizáveis e compatíveis com outros processos. A orientação a objetos existe para ajudar o programador a alcançar melhores resultados durante todas as etapas de criação de um projeto. OO A programação Orientada a objetos é uma forma de programação de software adotada pela maioria das linguagens de programação modernas, ela busca expressar as coisas de uma forma mais próxima da vida real, baseado na composição e interação entre os objetos. Neste artigo veremos uma introdução à programação orientada a objetos, seus fundamentos e sua aplicação. Em seguida será apresentada uma introdução aos padrões de projetos, mostrando o porquê foram criados e para que servem. Orientação a objetos talvez seja um dos assuntos mais importantes e aplicados nos dias de hoje. Apesar de antigo, nem sempre essa forma de programar foi tão adotada no mercado como atualmente, onde temos como boa prática a criação de projetos orientados a objetos. Mas existem diversos paradigmas de programação que foram criados para buscar melhorar e atender a evolução tecnológica de desenvolvimento de softwares. Neste artigo veremos de forma resumida alguns conceitos e características da programação orientada a objetos. O artigo não vai se aprofundar em aplicações práticas, pois o objetivo é introduzir o leitor que não conhece o que é a programação orientada a objetos. Por ser um artigo teórico, serão apresentados conceitos sobre os fundamentos da POO, além de introduzir superficialmente o leitor aos padrões de projetos e boas práticas de programação. O artigo não tem foco em nenhuma linguagem de programação específica, ele trata dos fundamentos que podem ser aplicados nas linguagens de programação com suporte a orientação a objetos, como o Visual C# .NET e Visual Basic .NET. A IDE de desenvolvimento da Microsoft, o Visual Studio, atualmente na versão 2010, é repleto de recursos que facilitam a vida do desenvolvedor a programar orientado a objetos, tendo inclusive uma versão gratuita (express) que é recomendada para quem está iniciando com o desenvolvimento de softwares. Quem está começando na área de desenvolvimento de software, talvez tenha mais facilidade de entender seus fundamentos e conceitos, começando diretamente a programar orientado a objetos, porém, uma dúvida pode já surgir, se eu não estou programando orientado a objetos, eu estou programando em que?
A Orientação a objetos é um paradigma de programação, um paradigma determinará a visão de como será a estrutura de um software. No início os programas eram criados em código de máquina, um paradigma complexo e de difícil leitura, em seguida outros paradigmas foram criados, mas vou destacar três, os paradigmas procedural, funcional e orientado a objetos. "
Orientação a Objetos no .NET Framework Facebook Twitter (1) (0)
Este artigo demonstra alguns dos conceitos relacionados com a Orientação a Objetos, visando demonstrar e esclarecer os conceitos por detrás do .NET framework.
Atenção: esse artigo tem um vídeo complementar. Clique e assista! [lead]Do que trata o artigo Este artigo demonstra alguns dos conceitos relacionados com a Orientação a Objetos, visando demonstrar e esclarecer os conceitos por detrás do .NET framework. Para que serve A Orientação a Objetos tem por principais objetivos: facilitar o reuso de código, evitar duplicações, proporcionar maior clareza no desenvolvimento do sistema, aumentar a compreensão do que está sendo feito e ainda possibilita extensão e adaptação do código sem grandes impactos e alterações. Em que situação o tema é útil Para se trabalhar utilizando o máximo do poder do .Net é preciso conhecer e entender Orientação a Objetos, obtendo assim todas as facilidades e produtividade deste incrível framework. Na atualidade praticamente todas as aplicações comerciais são construídas
utilizando Orientação a Objetos, desta forma é imprescindível entender seus conceitos de forma prática. Resumo do DevMan Neste artigo veremos alguns dos conceitos da Orientação a Objetos de forma prática. Analisaremos uma aplicação construída sem utilizar OO e então transformaremos a mesma aplicação, utilizando conceitos de Classes, Métodos e Encapsulamento, entendendo como estes conceitos e práticas tão particulares da OO tornarão nossa aplicação mais extensível, coesa e simples. Ao final teremos transformado nosso código de maneira que possamos reutilizá-lo tanto em uma aplicação Windows como em uma aplicação WEB. [/lead] Não iniciarei este artigo com um histórico das linguagens que trabalham com Orientação a Objetos. Definiremos de forma teórica alguns pontos da Orientação a Objetos e trabalharemos estes pontos de forma prática, visando compreender como eles realmente funcionam e são aplicados, entendendo seus reais benefícios. Atualmente existem muitas linguagens que trabalham com Orientação a Objetos, e o .Net Framework funciona desta maneira com suas linguagens. Quando estamos utilizando C# estamos trabalhando com uma linguagem Orientada a Objetos e podemos tirar o máximo proveito de tudo que este paradigma de programação e desenvolvimento tem anos oferecer. No entanto apesar de uma linguagem ser Orientada a Objetos isso não significa que todo código criado utilizando esta linguagem seja um código realmente Orientado a Objetos. [subtitulo]Definindo alguns conceitos da Orientação a Objetos [/subtitulo] O paradigma da Orientação a Objetos define alguns pilares sobre os quais as linguagens devem atuar, e além disso, algumas características que estas linguagens devem possuir. Os conceitos com os quais trabalharemos neste artigo são: • Classes; • Objetos; • Encapsulamento; • Atributos/Propriedades; • Métodos. Além destes conceitos a Orientação a Objetos também define os seguintes, que não abordaremos aqui: • Herança • Polimorfismo
[subtitulo]Classes [/subtitulo] O paradigma da orientação a objetos está intimamente ligado com o conceito de classes. As classes podem, a grosso modo, serem definidas como um molde para a criação de objetos. Uma classe contém a descrição das características de um objeto, ou seja, suas propriedades, como também contém o comportamento de um objeto, ou seja, seus métodos. "
É válido ressaltar que a Orientação a Objetos também não é uma “bala de prata”, não é a opção ideal para tudo. Ela possui vantagens e desvantagens. Para conhecer esses pontos, assim como alguns mitos que foram criados em torno dela, acesse os artigos:
Vantagens e Desvantagens da POO Facebook Twitter (2) (1)
Veremos neste artigo como o paradigma de programação Orientado a Objeto pode nos ajudar no desenvolvimento de aplicações mais enxutas, flexíveis e fáceis de manter, bem como situações onde aplica-lo pode não ser a melhor opção.
Fique por dentro Este tema é útil para os desenvolvedores que estão iniciando na Programação Orientada a Objetos, bem como para aqueles que desejam aprofundar-se na mesma. A orientação a objetos muita das vezes nos impõe certos desafios e não basta apenas conhecer a sua estrutura e conceitos para resolvê-los, é preciso dominá-los. O objetivo deste artigo é demonstrar algumas das principais vantagens e desvantagens da aplicação do paradigma de Orientação a Objetos e situações onde seu uso é fundamental para o desenvolvimento de aplicações mais flexíveis e fáceis de manter, bem como cenários onde a programação estruturada pode ainda ser a melhor opção.
Encontramos muitas definições sobre o que é Programação Orientada a Objetos (POO), que muitas das vezes estão cercadas de mitos e definições que distorcem o paradigma, e muitos desses mitos assombram os novatos que desejam aprendê-lo. A intenção desse artigo é mostrar os principais erros e acertos durante o desenvolvimento orientado a objetos, esclarecendo as dúvidas mais frequentes a respeito da aplicação correta desse paradigma. O que é, na verdade, Programação Orientada a Objetos? Como implementar? Quais as linguagens que suportam a mesma? Orientação a objetos é abstração do mundo real? Polimorfismo, mais herança, mais abstração é orientação a objetos? Desenvolver um sistema orientado a objetos é mais lento? Essas são algumas das várias perguntas feitas por diversos desenvolvedores, até os mais experientes no assunto. Vamos esclarecer essas questões com exemplos práticos, sempre procurando as melhores soluções. A Programação Orientada a Objetos é um método de codificação que implica na utilização de objetos e suas relações a fim de descrever, de forma programática, o problema a ser resolvido. A definição clássica de POO foi baseada em três pilares fundamentais: encapsulamento, herança e polimorfismo. O princípio do encapsulamento define que os elementos de dados não estão disponíveis para o mundo exterior diretamente. Em vez disso, seriam criados métodos para dar acesso a esses valores fora do objeto. Hoje em dia, linguagens como C#, por exemplo, têm a capacidade de utilizar não só métodos para essa função, mas também podemos criar propriedades que podem acessar elementos de dados internos do objeto, como mostra o código da Listagem 1. Listagem 1. Exemplo de implementação do encapsulamento. 01
public class Pessoa
02
{
03
private string _nome;
04
05
public string Nome
06
{
07
get
08
{
09
return _nome;
10
}
11
set
12
{
13
_nome = value;
14
}
15
16
}
}
O código apresentado é uma forma alternativa que a linguagem C# disponibiliza para acessar elementos que estão protegidos segundo o conceito de encapsulamento. Na linha 09 está sendo retornado conteúdo da variável interna _nome, mas ao invés de simplesmente retornar o valor, nós poderíamos efetuar várias manipulações dos dados antes de retornar o mesmo, assim como também antes de definir o valor para a variável interna _nome (linha 13) nós também poderíamos efetuar várias manipulações utilizando as propriedades. Outro conceito fundamental que precisamos conhecer é o de herança, pois este recurso permite que os desenvolvedores possam definir os objetos de uma forma hierárquica, como mostra a Figura 1.
Figura 1. Diagrama de classe.
O Diagrama de classe representa a base para criar um objeto ou um conjunto deles de forma abstrata para que possamos manipular informações do mundo real em nossos sistemas. Cada nível da hierarquia define um objeto mais específico do que o nível pai e
cada nível herda todas as propriedades e métodos de seu objeto pai. Nesse ponto você define as propriedades e métodos mais específicos, fazendo assim com que os objetos que estão mais distantes sejam mais específicos. Por fim, precisaremos também entender o funcionamento do terceiro pilar da POO que é o polimorfismo. Este pilar é a capacidade que os objetos de uma classe específica têm de ser tratados como objetos de uma classe base. As classes base podem definir e aplicar métodos virtuais e as classes derivadas podem substituí-los, o que significa que elas fornecem sua própria definição e implementação. Podemos então concluir que polimorfismo, como o próprio nome já diz, é a capacidade de um objeto de ter várias formas. Confira na Listagem 2 um exemplo de implementação do polimorfismo em C#. Listagem 2. Implementação do polimorfismo. 01
public abstract class Pessoa
02
{
03
public string Nome { get; set; }
04
05
public abstract decimal GetSaldoNoBanco();
06
07
}
08
09
public class PessoaFisica : Pessoa
10
{
11
public PessoaFisica()
12
{
13
14
Nome = "Pessoa Física";
}
15
16
public override decimal GetSaldoNoBanco()
17
{
18
return 1500;
19
20
}
}
21
22
public class PessoaJuridica : Pessoa
23
{
24
public PessoaJuridica()
25
{
26
Nome = "Pessoa Jurídica";
27
}
28
29
public override decimal GetSaldoNoBanco()
30
{
31
return 20000;
32
33
34
}
}
35
class Program
36
{
37
static void Main(string[] args)
38
{
39
var pessoaFisica = new PessoaFisica();
40
var pessoaJuridica = new PessoaJuridica();
41
42
ImprimirValorDoBanco(pessoaFisica);
43
ImprimirValorDoBanco(pessoaJuridica);
44
45
46
Console.Read();
}
47
48
private static void ImprimirValorDoBanco(Pessoa pessoa)
49
{
50
var valorBanco = pessoa.GetSaldoNoBanco();
51
Console.WriteLine("Valor disponível na conta da {0} é:
{1}",
52
53
pessoa.Nome, valorBanco);
}
54
}
Como podemos ver no código que implementa de fato os conceitos de polimorfismo, o método I" [...]
Mitos e Verdades sobre a Orientação a Objetos em .NET Facebook Twitter (3) (0)
Neste artigo iremos desvendar os mistérios da Programação orientada a objetos com ênfase em .NET, mostrando os seus princípios e resolvendo algumas questões referentes a dúvidas cotidianas sobre o assunto. Artigo no estilo Mentoring (saiba mais)
Fique por dentro O desenvolvimento orientado a objetos é o principal paradigma de programação atualmente. O seu tipo de design permite diversas interpretações, o que leva a vários mitos criados em torno da POO. Esse artigo visa trazer alguns dizeres comuns no meio de desenvolvimento e analisá-los de forma a entendermos se são, de fato, verdades, ou apenas mitos criados ao longo do tempo. A ideia é que o leitor, ao final do artigo, tenha um entendimento desses elementos e também como eles irão se aplicar ao C#/.NET. A programação orientada a objetos está presente no dia-a-dia de grande parte dos desenvolvedores. Com a grande quantidade de programadores que desenvolviam em linguagens procedurais como C e Pascal, a alteração para a POO criou uma série de mitos que muitos acreditam até hoje como verdades. Como veremos ao longo desse artigo, alguns deles são, de fato, verdades, enquanto outros são mitos que acabam sendo repetidos erroneamente.
O objetivo final é entendermos porque esses dizeres são verdades ou mitos através de exemplos utilizando a linguagem de programação C# .NET. Ao longo desse artigo, iremos trazer uma introdução breve à programação orientada a objetos e o que ela significa no mundo de desenvolvimento. Juntamente com essa introdução, traremos alguns exemplos de linguagens orientadas a objetos comuns no mercado. Note que a implementação dos conceitos da POO irá variar de acordo com a linguagem de programação. A seguir, iremos trazer alguns dizeres comuns a respeito da POO e analisá-los de forma detalhada através de exemplo com C#.
Programação Orientada a Objetos Em primeiro lugar, precisamos entender que a programação orientada a objetos não é exclusividade de nenhuma linguagem de programação: trata-se de um design de software, um modelo a ser seguido. Nesse modelo, o objeto é a unidade essencial, responsável por dados e métodos para modificação do mesmo. A grande vantagem da POO com relação a outras, como a programação procedural (ou estruturada - BOX 1), é a divisão extremamente clara entre os elementos do software. Além disso, o paradigma da orientação a objetos facilita a modelagem do software, auxiliando na criação de uma documentação muito mais completa e explicativa para a aplicação. Essa clara divisão entre os elementos facilita na criação de aplicações modernas. A POO ainda facilita a introdução de alguns modelos de programação, como o MVC (ModelView-Controller) e o MVP (Model-View-Presenter). BOX 1. Programação procedural
A programação procedural (ou estruturada) obedece a um paradigma de programação que difere da POO em alguns pontos. Nela, os procedimentos estão ligados a dados globais, diferentemente do que vemos na POO, onde os métodos (equivalentes aos procedimentos) estão ligados aos dados do objeto. Esse tipo de diferenciação é importante em termos da organização do software, o que traz algumas facilidades e alguns problemas. Atualmente, linguagens estruturadas como C e Pascal são utilizadas principalmente em microcontroladores e outros elementos de programação embarcada. O paradigma da orientação a objetos traz o conceito da abstração de objetos do mundo real. Entretanto, como veremos ao longo de nosso artigo, isso não é uma verdade absoluta. Isso é devido ao fato de que a programação orientada a objetos trouxe uma implementação um pouco diferente, baseada em classes que podem ou não ser representações da realidade.
Como um exemplo, podemos ter uma classe Carro ou Pessoa, mas também podemos ter classes que não representam um objeto real, como DadosCarro ou DadosPessoa. Essas classes, então, irão definir os objetos presentes no sistema. Dentre os desenvolvedores, há a consciência de que uma linguagem orientada a objetos deve obedecer obrigatoriamente a quatro conceitos, considerados os pilares da POO: · Abstração: consiste em abstrair o que queremos representar e transformá-lo em informações para serem utilizadas dentro da aplicação. Essas informações existem na forma de uma identidade (nome único da classe ou objeto), propriedades (características que definem o objeto) e métodos (ações ou eventos aos quais o objeto deve obedecer). · Encapsulamento: consiste em esconder as informações a respeito de características e métodos de uma classe. Normalmente, esse encapsulamento é baseado em métodos especiais getters e setters, que serão considerados as propriedades da classe. O dado encapsulado é chamado, normalmente, de atributo. · Herança: consiste em criar uma hierarquia de classes dentro da aplicação. A ideia principal aqui é a reutilização de código. Por exemplo, temos uma aplicação que possui uma classe Animal e uma classe Cachorro. O ideal é que a segunda herde informações da primeira, uma vez que se trata de um tipo específico de animal, com características a mais. · Polimorfismo: normalmente, é tratado como uma área adjacente à herança. Isso porque o polimorfismo consiste em um objeto se comportando como vários. Em poucas palavras, linguagens que obedecem a esse conceito permitem que um objeto filho (do tipo Cachorro, para ficarmos no exemplo anterior) possa se comportar como ele mesmo (Cachorro) ou como sua classe pai (Animal, nesse caso). Linguagens de programação orientada a objetos modernas, como C# e Java, utilizam uma artimanha muito interessante para criar aplicações capazes de executar em diferentes arquiteturas. No caso do Java, a presença da JVM (Java Virtual Machine) garante que o software pode ser executado em diferentes máquinas. O compilador Java cria a aplicação em bytecodes capazes de serem executados por essa JVM, criando uma aplicação capaz de executar em qualquer dispositivo imaginável, em teoria. O C# .NET utiliza uma estrutura similar, compilando a aplicação para uma linguagem intermediária de tempo de execução, que é executada pelo .NET Framework. Existem também outras abordagens, como a utilizada pelo Python. Essa linguagem é considerada uma linguagem de script, o que significa que ela não é compilada, e sim interpretada em tempo de execução. Isso faz com que a mesma tenha uma melhor performance. É interessa" [...]
Pilares da Orientação a Objetos Após o primeiro contato com a Orientação a Objetos, você deve ter observado termos como abstração, encapsulamento, herança e polimorfismo. Estes são os fundamentos, os quatro pilares da POO. Para aprender sobre eles, algo fundamental para programar corretamente com esse paradigma, acesse:
Os 4 pilares da Programação Orientada a Objetos Facebook Twitter (80) (2)
Conheça nesse artigo os 4 principais pilares, bem como as diferenças para programação estruturada e as principais vantagens da POO. O desenvolvimento de software é extremamente amplo. Nesse mercado, existem diversas linguagens de programação, que seguem diferentes paradigmas. Um desses paradigmas é a Orientação a Objetos, que atualmente é o mais difundido entre todos. Isso acontece porque se trata de um padrão que tem evoluído muito, principalmente em questões voltadas para segurança e reaproveitamento de código, o que é muito importante no desenvolvimento de qualquer aplicação moderna. A Programação Orientada a Objetos (POO) diz respeito a um padrão de desenvolvimento que é seguido por muitas linguagens, como C# e Java. A seguir, iremos entender as diferenças entre a POO e a Programação Estruturada, que era muito utilizada há alguns anos, principalmente com a linguagem C. Esse padrão se baseia em quatro pilares que veremos ao longo desse artigo. Além disso, a POO diversas vantagens em sua utilização, que também serão vistas e explicadas.
Saiba mais sobre Orientação a Objetos
Programação Estruturada vs Programação Orientada a Objetos Na Figura 1 vemos uma comparação muito clara entre a programação estruturada e a programação orientada a objetos no que diz respeito aos dados. Repare que, no paradigma estruturado, temos procedimentos (ou funções) que são aplicados globalmente em nossa aplicação. No caso da orientação a objetos, temos métodos que são aplicados aos dados de cada objeto. Essencialmente, os procedimentos e métodos são iguais, sendo diferenciados apenas pelo seu escopo.
Figura 1. Estruturada x Orientação a Objetos
A linguagem C é a principal representante da programação estruturada. Se trata de uma linguagem considerada de baixo nível, que atualmente não é utilizada para projetos muito grandes. A sua principal utilização, devido ao baixo nível, é em programação para sistemas embarcados ou outros em que o conhecimento do hardware se faz necessário para um bom programa. Essa colocação nos traz a um detalhe importante: a programação estruturada, quando bem feita, possui um desempenho superior ao que vemos na programação orientada a objetos. Isso ocorre pelo fato de ser um paradigma sequencial, em que cada linha de código é executada após a outra, sem muitos desvios, como vemos na POO. Além disso, o paradigma estruturado costuma permitir mais liberdades com o hardware, o que acaba auxiliando na questão desempenho. Entretanto, a programação orientada a objetos traz outros pontos que acabam sendo mais interessantes no contexto de aplicações modernas. Como o desempenho das aplicações não é uma das grandes preocupações na maioria das aplicações (devido ao poder de processamento dos computadores atuais), a programação orientada a objetos se tornou muito difundida. Essa difusão se dá muito pela questão da reutilização de código
e pela capacidade de representação do sistema muito mais perto do que veríamos no mundo real. Veremos em detalhes esses e outros pontos que dizem respeito a programação orientada a objetos. Como desenvolvedores, é nossa missão entender quais são as vantagens e desvantagens de cada um dos paradigmas de programação e escolhermos o melhor para nossa aplicação. A escolha da linguagem também deve estar presente nessa escolha. Saiba mais sobre Orientação a Objetos x Programação Estruturada DevCast: Por que adotamos Orientação a Objetos?
Os 4 pilares da Programação Orientada a Objetos Para entendermos exatamente do que se trata a orientação a objetos, vamos entender quais são os requerimentos de uma linguagem para ser considerada nesse paradigma. Para isso, a linguagem precisa atender a quatro tópicos bastante importantes:
Abstração A abstração consiste em um dos pontos mais importantes dentro de qualquer linguagem Orientada a Objetos. Como estamos lidando com uma representação de um objeto real (o que dá nome ao paradigma), temos que imaginar o que esse objeto irá realizar dentro de nosso sistema. São três pontos que devem ser levados em consideração nessa abstração. O primeiro ponto é darmos uma identidade ao objeto que iremos criar. Essa identidade deve ser única dentro do sistema para que não haja conflito. Na maior parte das linguagens, há o conceito de pacotes (ou namespaces). Nessas linguagens, a identidade do objeto não pode ser repetida dentro do pacote, e não necessariamente no sistema inteiro. Nesses casos, a identidade real de cada objeto se dá por .. A segunda parte diz respeito a características do objeto. Como sabemos, no mundo real qualquer objeto possui elementos que o definem. Dentro da programação orientada a objetos, essas características são nomeadas propriedades. Por exemplo, as propriedades de um objeto “Cachorro” poderiam ser “Tamanho”, “Raça” e “Idade”. Por fim, a terceira parte é definirmos as ações que o objeto irá executar. Essas ações, ou eventos, são chamados métodos. Esses métodos podem ser extremamente variáveis, desde “Acender()” em um objeto lâmpada até “Latir()” em um objeto cachorro. Saiba mais sobre Abstração e Polimorfismo
Encapsulamento O encapsulamento é uma das principais técnicas que define a programação orientada a objetos. Se trata de um dos elementos que adicionam segurança à aplicação em uma programação orientada a objetos pelo fato de esconder as propriedades, criando uma espécie de caixa preta. A maior parte das linguagens orientadas a objetos implementam o encapsulamento baseado em propriedades privadas, ligadas a métodos especiais chamados getters e setters, que irão retornar e setar o valor da propriedade, respectivamente. Essa atitude evita o acesso direto a propriedade do objeto, adicionando uma outra camada de segurança à aplicação. Para fazermos um paralelo com o que vemos no mundo real, temos o encapsulamento em outros elementos. Por exemplo, quando clicamos no botão ligar da televisão, não sabemos o que está acontecendo internamente. Podemos então dizer que os métodos que ligam a televisão estão encapsulados. Saiba mais sobre Encapsulamento em Java
Herança O reuso de código é uma das grandes vantagens da programação orientada a objetos. Muito disso se dá por uma questão que é conhecida como herança. Essa característica otimiza a produção da aplicação em tempo e linhas de código. Para entendermos essa característica, vamos imaginar uma família: a criança, por exemplo, está herdando características de seus pais. Os pais, por sua vez, herdam algo dos avós, o que faz com que a criança também o faça, e assim sucessivamente. Na orientação a objetos, a questão é exatamente assim, como mostra a Figura 2. O objeto abaixo na hierarquia irá herdar características de todos os objetos acima dele, seus “ancestrais”. A herança a partir das características do objeto mais acima é considerada herança direta, enquanto as demais são consideradas heranças indiretas. Por exemplo, na família, a criança herda diretamente do pai e indiretamente do avô e do bisavô.
Figura 2. Herança na orientação a objetos
A questão da herança varia bastante de linguagem para linguagem. Em algumas delas, como C++, há a questão da herança múltipla. Isso, essencialmente, significa que o objeto pode herdar características de vários “ancestrais” ao mesmo tempo diretamente. Em outras palavras, cada objeto pode possuir quantos pais for necessário. Devido a problemas, essa prática não foi difundida em linguagens mais modernas, que utilizam outras artimanhas para criar uma espécie de herança múltipla. Outras linguagens orientadas a objetos, como C#, trazem um objeto base para todos os demais. A classe object fornece características para todos os objetos em C#, sejam criados pelo usuário ou não.
Polimorfismo Outro ponto essencial na programação orientada a objetos é o chamado polimorfismo. Na natureza, vemos animais que são capazes de alterar sua forma conforme a necessidade, e é dessa ideia que vem o polimorfismo na orientação a objetos. Como sabemos, os objetos filhos herdam as características e ações de seus “ancestrais”. Entretanto, em alguns casos, é necessário que as ações para um mesmo método seja diferente. Em outras palavras, o polimorfismo consiste na alteração do funcionamento interno de um método herdado de um objeto pai.
Como um exemplo, temos um objeto genérico “Eletrodoméstico”. Esse objeto possui um método, ou ação, “Ligar()”. Temos dois objetos, “Televisão” e “Geladeira”, que não irão ser ligados da mesma forma. Assim, precisamos, para cada uma das classes filhas, reescrever o método “Ligar()”. Com relação ao polimorfismo, valem algumas observações. Como se trata de um assunto que está intimamente conectado à herança, entender os dois juntamente é uma boa ideia. Outro ponto é o fato de que as linguagens de programação implementam o polimorfismo de maneiras diferentes. O C#, por exemplo, faz uso de método virtuais (com a palavra-chave virtual) que podem ser reimplementados (com a palavrachave override) nas classes filhas. Já em Java, apenas o atributo “@Override” é necessário. Esses quatro pilares são essenciais no entendimento de qualquer linguagem orientada a objetos e da orientação a objetos como um todo. Cada linguagem irá implementar esses pilares de uma forma, mas essencialmente é a mesma coisa. Apenas a questão da herança, como comentado, que pode trazer variações mais bruscas, como a presença de herança múltipla. Além disso, o encapsulamento também é feito de maneiras distintas nas diversas linguagens, embora os getters e setters sejam praticamente onipresentes. Saiba mais sobre Polimorfismo em Java
Principais vantagens da POO A programação orientada a objetos traz uma ideia muito interessante: a representação de cada elemento em termos de um objeto, ou classe. Esse tipo de representação procura aproximar o sistema que está sendo criado ao que é observado no mundo real, e um objeto contém características e ações, assim como vemos na realidade. Esse tipo de representação traz algumas vantagens muito interessantes para os desenvolvedores e também para o usuário da aplicação. Veremos algumas delas a seguir. A reutilização de código é um dos principais requisitos no desenvolvimento de software atual. Com a complexidade dos sistemas cada vez maior, o tempo de desenvolvimento iria aumentar exponencialmente caso não fosse possível a reutilização. A orientação a objetos permite que haja uma reutilização do código criado, diminuindo o tempo de desenvolvimento, bem como o número de linhas de código. Isso é possível devido ao fato de que as linguagens de programação orientada a objetos trazem representações muito claras de cada um dos elementos, e esses elementos normalmente não são interdependentes. Essa independência entre as partes do software é o que permite que esse código seja reutilizado em outros sistemas no futuro. Outra grande vantagem que o desenvolvimento orientado a objetos traz diz respeito a leitura e manutenção de código. Como a representação do sistema se aproxima muito do que vemos na vida real, o entendimento do sistema como um todo e de cada parte individualmente fica muito mais simples. Isso permite que a equipe de desenvolvimento não fique dependente de uma pessoa apenas, como acontecia com frequência em linguagens estruturadas como o C, por exemplo.
A criação de bibliotecas é outro ponto que é muito mais simples com a orientação a objetos. No caso das linguagens estruturadas, como o C, temos que as bibliotecas são coleções de procedimentos (ou funções) que podem ser reutilizadas. No caso da POO, entretanto, as bibliotecas trazem representações de classes, que são muito mais claras para permitirem a reutilização. Entretanto, nem tudo é perfeição na programação orientada a objetos. A execução de uma aplicação orientada a objetos é mais lenta do que o que vemos na programação estruturada, por exemplo. Isso acontece devido à complexidade do modelo, que traz representações na forma de classes. Essas representações irão fazer com que a execução do programa tenha muitos desvios, diferente da execução sequencial da programação estruturada. Esse é o grande motivo por trás da preferência pela linguagem C em hardware limitado, como sistemas embarcados. Também é o motivo pelo qual a programação para sistemas móveis como o Google Android, embora em Java (linguagem orientada a objetos), seja feita o menos orientada a objetos possível. No momento atual em que estamos, tecnologicamente essa execução mais lenta não é sentida. Isso significa que, em termos de desenvolvimento de sistemas modernos, a programação orientada a objetos é a mais recomendada devido as vantagens que foram apresentadas. Essas vantagens são derivadas do modelo de programação, que busca uma representação baseada no que vemos no mundo real. Saiba mais sobre vantagens e desvantagens da Orientação a Objetos
Exemplos de Linguagens Orientadas a Objetos Há uma grande quantidade de linguagens de programação orientada a objetos no mercado atualmente. Nesse artigo, iremos apresentar 3 das mais utilizadas no momento: Java, C# e C++. Cada uma delas possui uma abordagem diferente do problema que as torna muito boas para alguns tipos de aplicações e não tão boas para outros.
Java O Java é, muito provavelmente, a linguagem de programação mais utilizada no mercado atual. Auxiliado pela presença do JRE (Java Runtime Environment), ou variações dele, em quase todos os dispositivos eletrônicos do momento, a linguagem Java é um grande sucesso entre os desenvolvedores. O sucesso da linguagem aumentou ainda mais com o Google Android, que escolheu o Java como linguagem preferencial de desenvolvimento de aplicações. O Java implementa os quatro pilares de forma bastante intuitiva, o que facilita o entendimento por parte do desenvolvedor. A abstração, o primeiro pilar, é implementado através de classes, que contém propriedades e métodos, de forma bastante simples. Já o encapsulamento é realizado através de propriedades privadas, auxiliadas por métodos especiais getters e setters, como mostra a Listagem 1. Vale
ressaltar a palavra-chave “this” mostrada no método SetId(). Essa palavra-chave funciona como um representante da classe atual, uma auto-referência ao próprio objeto. private int id;
public int GetId()
{
return id;
{
public void SetId(int id)
{
this.id = id;
} Listagem 1. Encapsulamento em Java
As questões de herança e polimorfismo no Java são um pouco mais complexas. O Java possui herança simples, o que significa que cada classe pode herdar de apenas uma outra. Entretanto, o Java possui as chamadas Interfaces, que possuem propriedades e assinaturas de métodos. Essas interfaces precisam ser implementadas para funcionar, o que significa que uma classe pode implementar várias interfaces e herdar de apenas uma classe. Na questão de polimorfismo, o atributo @Override é responsável por informar ao Java que o método em questão está sendo reescrito.
C# O C#, por sua vez, é outra das linguagens mais utilizadas no mercado. Como os computadores pessoais no mundo, em sua maioria, possuem o sistema operacional Windows, da Microsoft, o C# se popularizou. Isso porque o Windows implementa o Framework .NET, ao qual o C# está associado. O C# é uma linguagem de uso geral e
especialmente criada para utilização com a orientação a objetos. Vale ressaltar que, em C#, tudo é um objeto (herda da classe object). A abstração é muito simples, e segue o modelo do Java. A questão de encapsulamento é um pouco diferente devido a implementação dos métodos getter e setter. A nomenclatura também é um pouco diferente. A variável que realmente guarda o valor do dado é chamada atributo, enquanto a propriedade é o elemento que realmente acessa aquele dado do mundo externo. Isso está mostrado na Listagem 2. Além disso, o C# faz uso de duas palavras-chave especiais: get e set. // Atributo
private int id;
// Propriedade
public int Id
{
get;
set;
} Listagem 2. Encapsulamento em C#
A questão da herança em C# também segue o modelo do Java: herança simples e a possibilidade de utilização de interfaces. A importância das interfaces é muito grande, uma vez que elas podem dar o tipo dos dados, que somente posteriormente serão associados a um tipo real, como mostra a Listagem 3. Isso também é válido para o Java. Por padrão, as identidades das interfaces começam com a letra “I”. O polimorfismo, por sua vez, é baseado em métodos virtuais (com a palavra-chave virtual) na classe pai e reescritos com a palavra-chave override na classe filha. IExemploInterface exemplo;
exemplo = new ImplementacaoIExemploInterface(); Listagem 3. Interfaces em C#
C++ O C++, por sua vez, é uma linguagem um pouco mais primitiva, e permite muito mais liberdades com o hardware. Como ele foi derivado imediatamente do C, o C++ permite a utilização de ponteiros, por exemplo, que irão trabalhar diretamente com a memória. Além disso, o C++ pode utilizar todas as bibliotecas C que existem diretamente. Em termos de abstração, o C++ implementa classes, assim como qualquer linguagem orientada a objetos. Ele também possui o sentido de privado e público, que é utilizado para encapsulamento. Esse encapsulamento é realizado através de métodos getter e setter, muito similar ao visto em Java, como mostra a Listagem 4. Repare que a listagem mostra somente a assinatura dos métodos especiais, sendo que sua implementação é a mesma que em Java. Esse tipo de adaptação é muito comum em C++, onde a classe é guardada em um arquivo .h e sua implementação em um arquivo .cpp. private:
int id;
public:
int GetId() const;
void SetId(int const id); Listagem 4. Encapsulamento em C++
A questão da herança no C++ é um pouco diferente. A linguagem permite a herança múltipla, o que significa que cada classe pode herdar de quantas classes desejar. Isso pode causar problemas de métodos que possuem o mesmo nome, portanto o desenvolvedor precisa estar atento. O polimorfismo é baseado em métodos virtuais, da mesma forma como o C#. A complexidade, entretanto, é maior, uma vez que temos que cuidar de detalhes de mais baixo nível, como acesso a memória. Além dessas exemplificadas, existem outras linguagens que merecem ser citadas. Entre elas, podemos elencar: Python, linguagem de script orientada a objetos que é muito utilizada em pesquisas científicas devido a sua velocidade; Object Pascal (também conhecida como Delphi, devido ao nome de sua IDE), apesar do grande número de sistemas mais antigos que a utilizam; Objective-C, que é a linguagem de preferência para desenvolvimento de aplicações para os sistemas da Apple, como iPhone e iPad; Ruby, voltada para o desenvolvimento web; e Visual Basic .NET, muito utilizada até pouco tempo, mas também caindo em desuso, principalmente devido ao avanço do C# em popularidade.
Ao longo desse artigo, procuramos elencar os elementos que fazem da programação orientada a objetos um sucesso no momento. Vimos os quatro pilares desse paradigma e entendemos como eles são implementados em algumas das linguagens mais utilizadas no mercado de desenvolvimento. Além disso, entendemos algumas das vantagens que tornaram a programação orientada a objetos um grande sucesso para o desenvolvimento de sistemas modernos.
Abstração e polimorfismo na prática Facebook Twitter (12) (0)
Este artigo apresenta importantes fundamentos da programação orientada a objetos com C#, como herança, abstração e polimorfismo, aplicados em um exemplo prático. Artigo do tipo Tutorial Recursos especiais neste artigo: Conteúdo sobre boas práticas. Abstração e polimorfismo na prática Este artigo apresenta importantes fundamentos da programação orientada a objetos com C#, como herança, abstração e polimorfismo, aplicados em um exemplo prático. Usando boas práticas, veremos como estes fundamentos podem tornar um software mais fácil de ser mantido e evoluído, usando algumas técnicas de desenvolvimento ágil, como a refatoração. Veremos como resolver problemas comuns encontrados em código fonte (os chamados bad smells, ou “mau cheiros”), usando uma abordagem de desenvolvimento corretiva e evolutiva. Uma introdução ao desenvolvimento ágil evolutivo é apresentada, como forma de unir estas técnicas de orientação a objeto com as técnicas atuais de engenharia de software, demonstradas em um exemplo com C# e Visual Studio 2013.
Em que situação o tema é útil As técnicas aqui apresentadas são úteis para construir softwares mais fáceis de serem mantidos e evoluídos ao longo do tempo, promovendo reutilização de código, reduzindo
acoplamento entre classes, minimizando a ocorrência de bugs, tornando mais fácil a implementação de novos requisitos e tornando o código mais fácil de ser entendido. No documento “The new Methodology” [4], Martin Fowler traz uma excelente visão sobre um novo estilo de desenvolvimento de software, baseado em metodologias ágeis. Metodologias tradicionais de engenharia de software, focadas em planejamento, têm sido utilizadas ao longo do tempo. O grande problema é que elas são muito burocráticas, dão ênfase em contratos fixos, documentação excessiva, vasto planejamento antecipado (prematuro). Essa abordagem não condiz mais com a realidade da maioria dos projetos na área de tecnologia, onde novos requisitos são criados, modificados ou mesmo removidos após um planejamento inicial. Processos e metodologias modernas, como as metodologias ágeis de desenvolvimento, são evolutivos por natureza. Organizam o trabalho de forma interativa, realizado em pequenas partes, com pequenas entregas (design, código, teste, documentação), liberadas aos poucos, ao invés de um único release enorme. Dão maior ênfase na colaboração e comunicação aberta e fluente entre stakeholders, clientes e membros do time, minimizando as chances de insucesso em projetos. Os princípios básicos da abordagem ágil sugerem que indivíduos e interações são mais importantes que processos e ferramentas, trabalho deve ser feito mais focado no software em si que em uma documentação abrangente, colaboração do cliente mais que negociação de contrato e, principalmente, responder a mudanças mais que seguir um plano. O problema é que as abordagens tradicionais de engenharia de software se baseiam em outros ramos de engenharia, como a civil e mecânica, onde os custos com planejamento são menores. Em software, estima-se que até 50% de custos são gastos com design e planejamento. Projetos com um escopo invariável são raros, e se requisitos constantemente mudam, há um grande desperdício de esforço com análise que é descartada em virtude disso. Trabalho, tempo e dinheiro que são preciosos são perdidos. Uma abordagem ágil garante que a equipe e projeto sejam capazes de se adaptar às mudanças que ocorrerão, dentro de um contexto evolutivo. A XP – Extreme Programming, uma abordagem ágil, prega cinco valores principais: comunicação, feedback, simplicidade, coragem e respeito, com foco maior em disciplina do que processos. Já Scrum prega um desenvolvimento ágil baseado em iterações chamadas sprints e reuniões de acompanhamento frequentes (meetings). Aplicações reais precisam continuamente ser evoluídas, novos requisitos implementados, melhorias devem ser feitas no projeto existente (design), algumas vezes é necessário mudar de tecnologia ou plataforma, corrigir bugs, otimizar e melhorar a performance. Isso remete a uma pergunta: O que pode ser considerado um “bom software”? Um bom software é aquele que funciona como planejado, mas depende do seu negócio. Para pequenas companhias (startups), bons softwares devem ser criados rapidamente. Para grandes corporações, o foco na construção de um software é torná-lo fácil de ser mantido ao longo do tempo. Nesse sentido, é necessário tomar cuidado com bons princípios de design, como criar componentes com baixo acoplamento, reutilizáveis, que garantam que alterações em componentes não afetam outros, que objetos tenham responsabilidades únicas, separadas, partes facilmente substituíveis, complexidade encapsulada. Esses são princípios básicos da programação orientada a objetos que, vistos dentro de um contexto de uma abordagem ágil evolutiva, vão tornar o sistema de software mais fácil de testar, evoluir, manter e entender. Padrões de Projeto (Design Patterns) [1] ajudam a criar bons designs de software, já o uso de refatoração
[2] contínua (outra técnica amplamente divulgada por Fowler) garante código claro, limpo e funcional em equipes que programam de forma mais ágil, sem se preocupar em um primeiro momento com muitos aspectos de design, usando, por exemplo, Padrões de Projeto. Padrões de Projeto são soluções reutilizáveis para problemas recorrentes no desenvolvimento orientado a objetos. Aplicar padrões antecipadamente em um projeto requer mais tempo e aumenta custos, ao mesmo tempo em que aumenta sua complexidade. Requisitos são modificados, criados ou mesmo eliminados conforme projetos evoluem. Tentar aplicar padrões antecipadamente para prever essas mudanças é um problema conhecido como excesso de engenharia [3]. Já projeto em escassez reduz custos, pois o sistema de software é criado rapidamente devido à falta de tempo, conhecimento ou necessidade de se adicionar funcionalidades em curto prazo. Ao longo do tempo, armazena-se um débito de projeto [3], que torna o sistema de software muito difícil de ser mantido e evoluído. É nesse contexto que entram as refatorações, como uma forma de melhorar o projeto existente sem alterar seu comportamento externo observável. Refatoração é o aspecto-chave para um projeto evolutivo, e é na evolução que se encontra a verdadeira sabedoria. Nesse caso, desenvolvimento ágil, evolutivo, incremental, apoiado em refatorações e testes, é uma excelente receita para garantir o sucesso do projeto, seja ela para um startup ou uma grande corporação. Este artigo vai mostrar como aplicar estes conceitos em um exemplo prático com C# e Visual Studio 2013. Serão demonstrados os principais fundamentos da orientação a objetos (ver BOX 1) em uma aplicação que simula notificação de clientes de uma determinada companhia, por exemplo, uma operadora de telefonia, que precisa avisar seus clientes sobre cobranças, promoções e lançamentos. Além dos fundamentos da orientação a objetos, são demonstradas importantes técnicas como programação para interfaces, padrões, anti-patterns, bad smells [2] e uso contínuo de refatoração para um desenvolvimento ágil com código sempre limpo e funcional. "
Coesão e acoplamento E agora, como saber se estou programando orientado a objetos, se estou aplicando corretamente seus conceitos? Uma maneira de sanar essa dúvida é observar se seu código está coeso e com baixo acoplamento. Um código coeso é aquele que implementa apenas o que de fato é de sua responsabilidade, por exemplo: um método responsável por imprimir um
relatório não deve saber como acessar o banco de dados. Se ele sabe como fazer isso, dizemos que ele tem baixa coesão, o que deve ser evitado. Já um código fortemente acoplado é aquele que depende de muitos pacotes, classes e/ou métodos para prover uma funcionalidade. Quando se deparar com situações assim, cuidado. O post a seguir expõe como melhorar a qualidade do seu código, o ajudando a reduzir o acoplamento entre classes.
Princípios SOLID Ao avançar seus estudos em OO, logo você se deparará com os Princípios SOLID. Esse termo representa cinco regras que são avaliadas por arquitetos e programadores como as boas práticas para uma programação orientada a objetos, nos auxiliando na construção de um código de fácil leitura, manutenção e extensão. O nome SOLID também representa um acrônimo, e como apresentado a seguir, é formado pela junção da primeira letra do nome de cada um dos princípios. Single Responsibility Principle: De acordo com esse princípio, cada classe deve ser planejada para possuir apenas uma responsabilidade. Sobre ele, temos alguns posts específicos, os quais indicamos a seguir:
Arquitetura - O Princípio da responsabilidade única Facebook Twitter (4) (0)
O princípio da responsabilidade única é um dos princípios SOLID. Os princípios SOLID, são uma série de cinco princípios introduzidos por Robert C. Martin( Uncle bob ),como boas práticas de design de software. O princípio da responsabilidade única, foca na preocupação de que uma classe tenha seu papel e venha desempenhar somente ele de forma eficiente. Muito tenho falado sobre boas práticas de engenharia de software. Aplicar boas práticas, nos rende muitos benefícios como manutenção facilitada, um código organizado, dependências mais leves e uma arquitetura pronta para “abraçar” as mudanças, ao invés de brigar com elas. Hoje estarei abordando um dos princípios SOLID, que são princípios que foram introduzidos por Robert C. Martin, ou como é comumente chamado Uncle Bob. Os princpipios SOLID, são cinco princípios de engenharia de software para alcançar os benefícios que falamos anteriormente. Ficou famoso como SOLID, por que são as iniciais dos princípios, e o pessoal percebeu que formava a palavra SOLID e passou a usá-la. Veja abaixo:
Principios SOLID
Sigle Responsability Principle Open Close Principle Liskov substitution Principle Interface segregation principle Dependency inversion principle
Abordaremos neste artigo o Sigle Responsability Principle.
Combatendo a Classes faz tudo Uncle Bob diz eu seu livro clean code, que se uma classe tem mais de um motivo para ser alterada ela já está ferindo o princípio da responsabilidade única. As classes devem ter apenas uma responsabilidade, realizar somente ela e realizá-la bem. Observe o diagrama abaixo:
Figura 1. Classe com muitas responsabilidades
O que você vê de arrado na classe acima? Concorda que ela esta assumindo responsabilidades que não são suas? Concorda que ela esta fazendo coisas demais? Ela além de suas propriedades, obtem por id, obtem por nome e ainda salva; diriamos que é uma geniuna classe “canivete suiço”. E geralmente, um modelo de um sistema não tem somente uma classe, mais várias e se todas estivessem assim? Teriamos vários problemas, para dar manutenção e evoluir. Agore veja o exemplo abaixo, refatorado e contemplando o princípio da responsabilidade única.
Figura 2. Classes com responsabilidades bem definidas
Note que temos um cenário completamente diferente, desacolplado e com cada “peça” realizando somente o seu papel. Temos uma classe Entidade, imaginando que estejamos seguindo as premissas do DDD( Domain Drive Design ), uma classe produto que herda de entidade e possui propriedades comuns a todos os produtos, como por exemplo nome e fabricante e classes que herdam de produto e tem suas particularidades em cada uma delas; note que estas classes possuem apenas propriedades e poderiam também, ter métodos e eventos, mais que tratassem apenas do negócio e não de persistência como vimos no exemplo anterior. Na hora de persistir, temos classes apropriadas que vão pegar este objeto de negócio e salvar no banco de dados, ou no repositório destinado a armazená-los. Lembrando que estamos utilizando um padrão de arquitetura chamado repository pattern.
O Mandamento do princpipio da responsabilidade única Uma classe deve fazer apenas uma coisa, deve fazê-la bem e deve fazer somente ela. Se uma classe tem mais de um motivo para ser alterada, ela não segue este princípio. Se ao se referir a uma determinada classe, você diz por exemplo: “minha classe tem as informações do cliente e salva o mesmo no banco de dados” perceba que o “e” na frase,
indica mais de uma responsabilidade, ferindo assim o SRP( single responsability rpinciple ). Aplicar o princípio da responsabilidade única, é importante para uma arquitetura madura e sustentável. Quando começamos a dar valor a princípios como este, vemos que estamos amadurecendo e fazendo melhor o que gostamos de fazer: software. Bom pessoal espero que eu possa ter ajudado, e colaborado em algo com o crescimento profissional de cada leitor deste artigo. Um abraço e até a próxima.
SOLID: Padrões flexíveis para suas classes C# Facebook Twitter (19) (0)
Aprenda com esse artigo a criar suas classes obedecendo estes princípios de orientação a objetos. Artigo no estilo Mentoring (saiba mais)
Fique por dentro Neste artigo veremos importantes princípios da orientação a objetos que farão com que os softwares sejam mais flexíveis, de fácil manutenção e fácil entendimento do código. Aprenderemos a aplicar os cinco princípios SOLID com o C# em situações bem comuns do dia a dia de cada desenvolvedor.
Veremos as vantagens e as maneiras corretas de aplicar este princípio passo a passo. Ainda veremos como eles se relacionam com vários dos vinte três Designs Patterns do GoF, através de exemplos práticos de aplicação destes princípios, com situações reais que todo desenvolvedor enfrenta no dia a dia de seu trabalho. Uma das maiores dificuldades nos dias atuais em termos de desenvolvimento de software se refere à manutenção de sistemas computacionais. Pesquisas indicam que as empresas gastam mais de 80% do orçamento destinado ao software em manutenção.
Isso ocorre porque na maioria dos desenvolvedores de sistemas não seguem os princípios da orientação a objetos e não utilizam os padrões de projetos e técnicas corretas de desenvolvimento. São sistemas mal codificados que o tornam difícil de manter e evoluir. Outros ainda utilizam outros paradigmas, como a programação estruturada, que demanda muito mais manutenção que o paradigma orientado a objetos. Em sistemas de informação que adotam o paradigma orientado a objetos, existem vários padrões, princípios e técnicas que o desenvolvedor deve seguir para que o sistema seja de fácil entendimento para outros desenvolvedores, de fácil manutenção após o sistema estar em ambiente de produção, que mudanças e novas funcionalidades não causem impacto em todo o sistema já existente. Para um melhor entendimento sobre padrões de projetos é necessário um sólido conhecimento de orientação a objetos, do contrário, os padrões não serão bem compreendidos e o leitor acaba não sabendo aplicar estes no seu dia a dia. Os princípios SOLID são a base para vários padrões de projetos criados e tornam softwares mais evolutivos, de fácil manutenção e mudanças efetuadas depois de finalizado, não impactando em outras áreas do programa, mudanças não propagam erros por outras partes desse sistema. Muitos desenvolvedores discutem sobre os padrões de projeto de software, alguns dizem que os eles servem simplesmente para resolver problemas das linguagens de programação orientadas a objetos e que outras linguagens, com mais recursos, não precisam implementar nenhum desses padrões, mas esse não é um tópico abordado neste artigo. Mas o que todos não discutem é que os princípios SOLID da orientação a objetos são a base para todo e qualquer projeto que siga este paradigma de desenvolvimento. Com certeza, um código que não segue estes princípios não é de qualidade e um desenvolvedor ao olhar para esse código percebe isso. Existem várias dicas, macetes e técnicas para aplicação de todos estes princípios. Para um código limpo e de qualidade, ele deve estar bem modularizado, cada classe deve ter a sua responsabilidade e as relações entre essas classes devem ser claras e bem definidas. É aqui que entra a ideia de código sólido, de onde vem o tema deste artigo. Antes de entrarmos no estudo destes princípios, veremos quais os principais problemas encontrados em sistemas e que poderiam ser evitados com a aplicação de padrões de projetos e princípios de orientação a objetos (BOX 1): · Rigidez: o sistema foi desenvolvido de forma que é muito difícil de mudar, qualquer alteração provoca uma cascata de operações por todo o restante do sistema; · Imobilidade: a codificação foi feita de forma que o reuso é muito difícil, nenhuma parte do sistema pode ser reaproveitada em outro sistema;
· Fragilidade: o sistema foi feito de maneira que qualquer mudança o desestabiliza e o torna inoperante; · Complexidade: o sistema foi desenvolvido utilizando-se de muitos padrões para resolver problemas simples, algo desnecessário (dependendo do caso); · Repetição: mesmos trechos de códigos espalhados por todo o sistema, ao invés de estarem encapsulados em um método ou classe. Aplicar boas práticas de engenharia de software nos rende muitos benefícios como manutenção facilitada, código organizado, dependências mais leves e uma arquitetura completamente aberta a receber atualizações, melhorias e novos recursos. Além disso, quando temos o sistema com várias classes desempenhando bem o seu papel e cada método tendo o seu objetivo bem definido, ficam muito mais fáceis de ser realizados os testes unitários do sistema. BOX 1. Padrões de Projeto GoF
Padrões de Projeto do GoF tem como objetivo solucionar problemas comuns de software relacionados as orientação a objetos. Ganharam este nome de GoF, pois foram concebidos num livro de 1995 chamado Design Patterns: Elements of Reusable Object-Oriented Software por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, a chamada Gangue of Four. São organizados em três grupos: · Padrões de criação: relacionados à criação de objetos (Abstract Factory, Builder, Factory Method, Prototype, Singleton); · Padrões estruturais: tratam das associações entre classes e objetos (Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy). · Padrões comportamentais: tratam das interações e divisões de responsabilidades entre as classes ou objetos (Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor).
Princípios SOLID SOLID são cinco princípios básicos que todos os analistas e desenvolvedores deveriam seguir para uma boa modelagem de classes. Cada letra da palavra SOLID representa um princípio, são eles: · S – Single Responsibility Principle (SRP): princípio da responsabilidade única; · O – Open Closed Principle (OCP): princípio do aberto/fechado; · L – Liskov Substituition Principle (LSP): princípio da substituição de Liskov;
· I – Inteface Segregation Principle (ISP): princípio da segregação de interfaces; · D – Dependency Inversion Principle (DIP): princípio da inversão de dependência.
SRP (Sigle Responsibility Principle) O princí" [...]
Open Closed Principle: Esse princípio determina que uma classe deve ser fechada para modificações e aberta para extensões. Sobre ele, recomendamos os posts:
Open Closed Principle Facebook Twitter (0) (0)
O princípio open Close ou aberto/fechado, é um dos princípios SOLID, O princípio do aberto/fechado nos auxilia a desenhar um software que seja fácil de modificar, e que não sofre com o impacto das mesmas. A uma das grande dificuldades hoje em dia em termos de desenvolvimento de software, esta relacionada a manutenção. Segundo pesquisas, 80% do investimento feito em software está relacionado ao custo da manutenção.O fato de muitos softwares serem construindo sem boa práticas e padrões adequados, ou até mesmo, construídos sem uma sólida arquitetura; acaba por se ter um produto mal feito, um verdadeiro “elefante branco”. Mais ai fica a pergunta: o que torna um software difícil de se evoluir ou manter? Além dos problemas citados anteriormente, o que ocorre, é que o código mal escrito faz com que uma pequena alteração cause outros erros, e isto quando ao solucionar um problema não surja outro. O padrão Open Close Princíple, um dos princípios SOLID, diz o seguinte: “Entidades de software ( classes, métodos, módulo, etc ) devem estar abertas para extensão, mas fechadas para modificação”. A afirmação acima, só pode ser mantida se o software tiver um design concebido sob os pilares das boas praticas de desenvolvimento de software, e isto incluir aplicar padrões e
princípios que deem base para um softwares que tenha uma base sólida. Veja o exemplo abaixo, sem ter sido considerado o princípo do aberto/fechado:
Veja que no código acima, temos muitos problemas de design. O principal é que, caso seja feita uma modificação no cálculo do salário, seja a inclusão de alguma fórmula adicional ou qualquer outro fator, pode causar comportamentos inesperados para quem usa esta classe, até mesmo um bug. Observe agora o cenário do exemplo acima, aplicando o Open Close Principle:
Diagrama
Exemplo de código:
Note que no exemplo aciuma, a base não muda, ou seja, é fechada para modificação; passando assim ser de responsabilidade das classe herdeiras, implementarem o comportamento de acordo com um determinado cenário. Implementando um design de arquitetura, tendo em mente o princípio do aberto/fechado, o impacto das mudanças reduz drasticamente. Fazendo com que tenhamos um software flexível, robusto e aceita as mudanças, ao invés de brigar com elas. O exemplo acima deixa claro isto, e uma vez sendo
alterado o cálculo do salário do analista, como o do gerente, a base se manter imutável diminuindo a possibilidade de bugs no sistema.
Open/Closed Principle: Estenda comportamentos sem alterar o código Facebook Twitter (0) (0)
Melhorias de código e design de software usando algumas técnicas da orientação a objetos e principalmente o princípio Aberto/Fechado (Open/Closed principle - OCP) para que possamos estender o comportamento de uma classe sem modificá-la. De que se trata o artigo
Melhorias de código e design de software usando algumas técnicas da orientação a objetos e principalmente o princípio Aberto/Fechado (Open/Closed principle - OCP) para que possamos estender o comportamento de uma classe sem modificá-la e também sem comprometer o código-fonte que já foi implementado e testado. Em que situação o tema é útil Em qualquer situação onde é necessário introduzir novas funcionalidades em um sistema ou código já existente, equipes de manutenção de software que precisam fazer alterações sem que isso introduza algum tipo de erro, manutenção de código legado e melhorias de design tendo como objetivos a criação de classes que sejam extensíveis, mas, que não permitam serem modificadas.
Open/Closed Principle Mudanças ou novos requisitos em projetos de software são sempre constantes e para isso nós alteramos o nosso código-fonte para que nosso software possa atender aos nossos clientes e usuários. O grande problema é que muito possivelmente a nossa aplicação já esteja testada e em produção. Estas novas alterações têm a possibilidade de introduzir erros. Para que isto não ocorra, podemos usar algumas técnicas que nos ajudam a abordar esse tipo de problema, de modo que as alterações sejam feitas de forma consciente e sem quebrar o que já está construído. Uma simples mudança em uma parte de um software pode resultar em atualizações em inúmeros pontos da aplicação que fazem uso deste trecho de código que foi modificado, ou seja, existe um alto nível de dependência entre a classe modificada e os usuários que a utilizam. Na verdade, isto é considerado como sendo um design mal feito, de difícil manutenção e nem um pouco extensível, já que esta mudança deveria ser transparente para todos aqueles que dependem deste código modificado. Por outro lado, a cada mudança feita há sempre uma possibilidade de introduzir comportamentos inesperados (bugs) na aplicação, que podem passar despercebidos durante nossos testes e, com isso, muito possivelmente estes bugs sejam descobertos somente quando o software estiver em produção. Por mais estranhas que as possibilidades comentadas possam parecer, é muito comum nos depararmos com estes tipos de situações durante o desenvolvimento de um software. Mas, como garantir que modificações em um software sejam feitas de forma segura e da maneira mais correta possível? Como nosso código pode ser projetado de forma que todas as outras partes não precisam nem ficar sabendo desta alteração? Infelizmente, não existe uma fórmula mágica, mas, existe um conjunto de boas práticas que devem sempre ser seguidas. Dentre este conjunto de boas práticas, podemos citar: · Uso de padrões de projeto (ou do inglês Design Patterns) · Desenvolvimento dirigido por testes (ou do inglês Test Driven Development – TDD) · Não adicionar complexidade desnecessária ao nosso código (princípio YAGNI) · Manter as coisas sempre o mais simples possível (princípio KISS) Nota do DevMan
Test Driven Development (TDD) – é uma técnica de desenvolvimento de software na qual um desenvolvedor escreve um teste automatizado que defina uma melhoria, correção ou uma nova funcionalidade. Após isso, é feito o código a ser avaliado pelo teste e consequentemente validado. Posteriormente, o código é refatorado e testado novamente com intuito de checarmos se esta refatoração não alterou o comportamento do código escrito anteriormente.
Na verdade o nome TDD é um nome ruim, já que ele utiliza testes para guiar o desenvolvimento da aplicação, mas o foco não são os testes e sim o design da aplicação. Criando testes antes da escrita do código fornece a visão de como os outros irão utilizar o método a ser desenvolvido (semelhante ao uso de uma API), já que estamos desenvolvendo de fora para dentro (dos testes para dentro da implementação). Nota do DevMan
Princípio YAGNI – Esta abreviação quer dizer You Ain´t Gonna Need It e defende que não devemos adicionar funcionalidades ao nosso código até que elas sejam realmente necessárias. Este princípio tem como principal função fazer com que o requisito de negócios seja satisfeito, nenhuma linha de código a mais deve ser adicionada. Nota do DevMan
Princípio KISS – Esta abreviação em inglês que significa Keep It Simple, Stupid! e diz que devemos valorizar a simplicidade do código eliminando toda a complexidade desnecessária. Este princípio teve a sua inspiração diretamente do princípio da Navalha de Occam e das máximas de Albert Einstein ("tudo deve ser feito da forma mais simples possível, mas não mais simples que isso").
Classes Abstratas Quando criamos nossas classes é comum percebermos que algumas delas possuem alguns métodos em comum (por exemplo: andar, voar ou correr) e que estes métodos podem se comportar de forma idêntica, ou assumir um comportamento diferente em cada classe que o contém. As classes abstratas são a base do modelo de abstração na programação orientada a objetos. Uma classe abstrata representa um modelo abstrato para outras classes. Através da herança, este modelo permite reaproveitar métodos já implementados, possuindo assim um comportamento igual neste caso, e também fazer com que as classes derivadas definam as suas próprias versões de um mesmo método, possuindo assim um comportamento diferente neste outro caso. A abstração é um poderoso recurso que faz com que os usuários de uma classe não precisem conhecer a sua implementação concreta, ao invés disso, apenas conhecer o modelo abstrato que possui o método em que ela necessita, não importando como as diferentes classes se comportam em relação a este método. "
Liskov Substitution Principle: Esse princípio está relacionado à herança e indica que devemos ser capazes de substituir a classe filha pela classe pai sem que o sistema apresente problemas;
Interface Segregation Principle: Já o princípio da segregação dita que precisamos planejar interfaces específicas, com propósito bem determinado, de modo que quando implementada a classe que o faz não precise codificar métodos desnecessários; Dependency Inversion Principle: Por fim, o princípio da inversão de dependência sinaliza que o recomendado é depender apenas de classes abstratas, e não de classes concretas. Para aprender sobre todos esses princípios, acesse o conteúdo a seguir:
SOLID: Padrões flexíveis para suas classes C# Facebook Twitter (19) (0)
Aprenda com esse artigo a criar suas classes obedecendo estes princípios de orientação a objetos. Artigo no estilo Mentoring (saiba mais)
Fique por dentro Neste artigo veremos importantes princípios da orientação a objetos que farão com que os softwares sejam mais flexíveis, de fácil manutenção e fácil entendimento do código. Aprenderemos a aplicar os cinco princípios SOLID com o C# em situações bem comuns do dia a dia de cada desenvolvedor.
Veremos as vantagens e as maneiras corretas de aplicar este princípio passo a passo. Ainda veremos como eles se relacionam com vários dos vinte três Designs Patterns do GoF, através de exemplos práticos de aplicação destes princípios, com situações reais que todo desenvolvedor enfrenta no dia a dia de seu trabalho. Uma das maiores dificuldades nos dias atuais em termos de desenvolvimento de software se refere à manutenção de sistemas computacionais. Pesquisas indicam que as empresas gastam mais de 80% do orçamento destinado ao software em manutenção. Isso ocorre porque na maioria dos desenvolvedores de sistemas não seguem os princípios da orientação a objetos e não utilizam os padrões de projetos e técnicas corretas de desenvolvimento. São sistemas mal codificados que o tornam difícil de manter e evoluir. Outros ainda utilizam outros paradigmas, como a programação estruturada, que demanda muito mais manutenção que o paradigma orientado a objetos. Em sistemas de informação que adotam o paradigma orientado a objetos, existem vários padrões, princípios e técnicas que o desenvolvedor deve seguir para que o sistema seja de fácil entendimento para outros desenvolvedores, de fácil manutenção após o sistema estar em ambiente de produção, que mudanças e novas funcionalidades não causem impacto em todo o sistema já existente. Para um melhor entendimento sobre padrões de projetos é necessário um sólido conhecimento de orientação a objetos, do contrário, os padrões não serão bem compreendidos e o leitor acaba não sabendo aplicar estes no seu dia a dia. Os princípios SOLID são a base para vários padrões de projetos criados e tornam softwares mais evolutivos, de fácil manutenção e mudanças efetuadas depois de finalizado, não impactando em outras áreas do programa, mudanças não propagam erros por outras partes desse sistema. Muitos desenvolvedores discutem sobre os padrões de projeto de software, alguns dizem que os eles servem simplesmente para resolver problemas das linguagens de programação orientadas a objetos e que outras linguagens, com mais recursos, não precisam implementar nenhum desses padrões, mas esse não é um tópico abordado neste artigo. Mas o que todos não discutem é que os princípios SOLID da orientação a objetos são a base para todo e qualquer projeto que siga este paradigma de desenvolvimento. Com certeza, um código que não segue estes princípios não é de qualidade e um desenvolvedor ao olhar para esse código percebe isso. Existem várias dicas, macetes e técnicas para aplicação de todos estes princípios. Para um código limpo e de qualidade, ele deve estar bem modularizado, cada classe deve ter a sua responsabilidade e as relações entre essas classes devem ser claras e bem definidas. É aqui que entra a ideia de código sólido, de onde vem o tema deste artigo.
Antes de entrarmos no estudo destes princípios, veremos quais os principais problemas encontrados em sistemas e que poderiam ser evitados com a aplicação de padrões de projetos e princípios de orientação a objetos (BOX 1): · Rigidez: o sistema foi desenvolvido de forma que é muito difícil de mudar, qualquer alteração provoca uma cascata de operações por todo o restante do sistema; · Imobilidade: a codificação foi feita de forma que o reuso é muito difícil, nenhuma parte do sistema pode ser reaproveitada em outro sistema; · Fragilidade: o sistema foi feito de maneira que qualquer mudança o desestabiliza e o torna inoperante; · Complexidade: o sistema foi desenvolvido utilizando-se de muitos padrões para resolver problemas simples, algo desnecessário (dependendo do caso); · Repetição: mesmos trechos de códigos espalhados por todo o sistema, ao invés de estarem encapsulados em um método ou classe. Aplicar boas práticas de engenharia de software nos rende muitos benefícios como manutenção facilitada, código organizado, dependências mais leves e uma arquitetura completamente aberta a receber atualizações, melhorias e novos recursos. Além disso, quando temos o sistema com várias classes desempenhando bem o seu papel e cada método tendo o seu objetivo bem definido, ficam muito mais fáceis de ser realizados os testes unitários do sistema. BOX 1. Padrões de Projeto GoF
Padrões de Projeto do GoF tem como objetivo solucionar problemas comuns de software relacionados as orientação a objetos. Ganharam este nome de GoF, pois foram concebidos num livro de 1995 chamado Design Patterns: Elements of Reusable Object-Oriented Software por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, a chamada Gangue of Four. São organizados em três grupos: · Padrões de criação: relacionados à criação de objetos (Abstract Factory, Builder, Factory Method, Prototype, Singleton); · Padrões estruturais: tratam das associações entre classes e objetos (Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy). · Padrões comportamentais: tratam das interações e divisões de responsabilidades entre as classes ou objetos (Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor).
Princípios SOLID
SOLID são cinco princípios básicos que todos os analistas e desenvolvedores deveriam seguir para uma boa modelagem de classes. Cada letra da palavra SOLID representa um princípio, são eles: · S – Single Responsibility Principle (SRP): princípio da responsabilidade única; · O – Open Closed Principle (OCP): princípio do aberto/fechado; · L – Liskov Substituition Principle (LSP): princípio da substituição de Liskov; · I – Inteface Segregation Principle (ISP): princípio da segregação de interfaces; · D – Dependency Inversion Principle (DIP): princípio da inversão de dependência.
SRP (Sigle Responsibility Principle) O princí" [...]
Orientação a Objetos na prática Agora que você já conhece os conceitos, que tal colocá-los em prática programando com a linguagem C#? Os artigos e o DevCast a seguir lhe ensinarão como aplicar os conceitos da Orientação a Objetos com os recursos do C#:
Como criar minha primeira classe em C# Facebook Twitter
(25) (0)
Neste conteúdo você aprenderá a criar sua primeira classe na linguagem C#. Aprenda também a usar herança e interfaces, bem como métodos, atributos e propriedades. Ir para o código
A definição de classes na linguagem C# é feita com uma sintaxe simples, de fácil compreensão e nos permite criar atributos, propriedades e métodos. Por exemplo, na Figura 1 temos a representação de uma classe chamada Produto com seus campos e logo em seguida vemos como essa classe seria implementada em C#:
Figura 1. Diagrama da classe Produto 1 2
public class Produto {
3
private int codigo;
4
private string nome;
5
private decimal preco;
6 public int Codigo { get => codigo; set => codigo = value; }
7
public string Nome { get => nome; set => nome = value; }
8
public decimal Preco { get => preco; set => preco = value; }
9 10
}
Linha 1: Nessa linha temos os seguintes termos: public define a visibilidade da classe como pública; class define que estamos criando uma classe; e Produto é o nome da classe. Linhas 3 a 5: Aqui temos o que chamamos de atributos. São variáveis privadas que representam as características da classe. Note o modificador de acesso private, o tipo e o nome de cada atributo; Linhas 7 a 9: Já aqui temos as propriedades da classe, que são públicas e encapsulam os atributos. Observe que para cada atributo há uma propriedade e que no corpo delas definimos os métodos get e set. O get retorna o atributo correspondente e o set recebe um valor e o repassa para o atributo.
No corpo de cada propriedade usamos expressões lambda para definir os métodos get e set. Essa sintaxe é equivalente à seguinte: 1
public int Codigo
2
{
3
get { return codigo; }
4
set { codigo = value; }
5
}
O parâmetro value possui o mesmo tipo da propriedade e é recebido de forma implícita no método get, sem que seja necessário declará-lo. Isso é equivalente a criar, por exemplo, os métodos getCodigo e setCodigo, como fazemos em outras linguagens: 1 2
public int getCodigo {
3 4
return codigo; }
5 6
public void setCodigo(int value)
7
{ codigo = value;
8 9
}
Herança Herança é um tipo de relacionamento muito comum na orientação a objetos e ocorre quando uma classe descende da outra e herda suas características e comportamentos, além de implementar os seus próprios. Por exemplo, considere o diagrama de classes da Figura 2 em que Assinatura herda de Produto.
Figura 2. Diagrama classes com Assinatura herdando de Produto
Nesse cenário a classe Assinatura herda de Produto e também define uma propriedade e método próprios. Em C# essa classe seria implementada da seguinte forma: 1 2
public class Assinatura : Produto {
3
private DateTime dataExpiracao;
4 5
public DateTime DataExpiracao { get => dataExpiracao;
6
set => dataExpiracao = value; }
7 8
public TimeSpan GetTempoRestante()
9
{ return dataExpiracao - DateTime.Today;
10 }
11 }
12
Linha 1: A herança em C# é representada pelos dois pontos na definição da classe, seguido do nome da classe que está sendo herdada. Nesse caso, Assinatura herda de Produto; Linhas 3 a 6: Nesse trecho temos o atributo e propriedade que dizem respeito apenas à assinatura; Linhas 8 a 11: Aqui temos o método GetTempoRestante da assinatura, que retorna um TimeSpan representando o tempo que falta até a assinatura expirar;
Interfaces Na Orientação a Objetos as interfaces funcionam como contratos, ou seja, elas definem comportamentos que devem ser cumpridos pelas classes. Quando uma classe atende a uma interface, dizemos que ela implementa essa interface. Na Figura 3 temos um diagrama que mostra uma nova configuração para as classes que representamos anteriormente:
Figura 3. Diagrama de classes com interface
Nesse cenário a interface IExpiravel define que toda classe que representa um produto cuja data de expiração ou validade chega ao fim (expira) deve implementar o método GetTempoRestante. Por exemplo, se tivéssemos outra classe Voucher ou Desconto, por exemplo, ela poderia implementar essa classe e definir o comportamento desse método. Em C# a interface IExpiravel seria escrita da seguinte forma: 1
public interface IExpiravel
2
{ TimeSpan GetTempoRestante();
3 4
}
Linha 1: Note o uso da palavra reservada interface e também no nome da interface: IExpiravel. Em C# convenciona-se iniciar o nome das interfaces com I (maiúsculo); Linha 3: As interfaces definem apenas a assinatura do método, não seu comportamento. A implementação do método fica por conta da classe que implementar essa interface.
Agora, considerando que a classe Assinatura implementa essa interface, seu código seria modificado da seguinte forma: 1 2
public class Assinatura : Produto, IExpiravel { private DateTime dataExpiracao;
3 4 5 6
public DateTime DataExpiracao { get => dataExpiracao; set => dataExpiracao = value; }
7
public TimeSpan GetTempoRestante()
8
{ return dataExpiracao - DateTime.Today;
9 }
10 }
11 12
Linha 1: A sintaxe para implementar uma interface em C# é a mesma que para herdar de outra classe: usando dois pontos; Linhas 8 a 11: Como a classe implementa a interface IExpiravel, ela deve obrigatoriamente definir o comportamento do método GetTempoRestante.
A partir desses conceitos podemos criar diversas outras classes e interfaces, com seus atributos, propriedades e métodos específicos.
C# Orientado a Objetos: Introdução Facebook Twitter (22) (0)
Neste artigo será apresentado uma introdução a orientação a objetos de uma forma geral em CSharp, em seguida colocaremos em prática os conhecimentos teóricos adquiridos para a linguagem de programação C#. A orientação a objetos é uma ponte entre o mundo real e virtual, a partir desta é possível transcrever a forma como enxergamos os elementos do mundo real em código fonte, a fim de nos possibilitar a construção de sistemas complexos baseados em objetos. O primeiro passo para utilizar o paradigma da orientação a objetos é perceber o universo em que se deseja trabalhar, uma vez que possuímos essa informação é necessário descrever a estrutura desses elementos, ou seja quais são as características mais marcantes destes para o desenvolvimento do sistema proposto. Vejamos como descrever um objeto “Pessoa”. As perguntas a serem feitas são: “O que é uma pessoa?”,”Quais são as características de uma pessoa?”,”Como uma pessoa se
comporta?”. Após responder a este questionário, teremos condições de modelar o objeto da forma como queremos. Vamos responder essas perguntas: · O que é uma pessoa? Resposta: A pessoa é um ser do mundo real que interage com toda a natureza. · Quais são as características de uma pessoa? Resposta: Uma pessoa possui: nome, olhos, boca, braços, pernas, cabelos e etc. · Como uma pessoa se comporta? Resposta: Uma pessoa corre, anda, fala, pula, come e etc. Nota: Os objetos a serem implementados no sistema devem obedecer as regras de negócio solicitadas pelo cliente. O analista deve perceber a partir da explicação do usuário o que realmente é necessário e perceber a responsabilidade que os objetos terão. Agora que já possuímos as informações do que realmente representa um objeto do tipo pessoa, precisaremos passar esse conhecimento utilizando uma linguagem de programação; é claro que qualquer linguagem que se utilize da orientação a objetos, neste artigo será exemplificado com C#. Nota: O suporte a orientação a objetos está em muitas linguagens, no entanto a sintaxe e as funções das linguagens são diferentes, ou seja, caso esteja utilizando o Java ou o PHP é necessário verificar as particularidades destes para continuar com o artigo.
Principais conceitos da OO Projetamos o nosso elemento do mundo real, agora como iremos traduzir isto para o código fonte? Antes disso é preciso compreender o que foi feito até agora veja os passos: · Descrever uma estrutura de uma pessoa · Descrever as características de uma pessoa · Descrever o comportamento de uma pessoa Quando descrevemos de estrutura de um elemento, na verdade criamos uma especificação básica do que todo elemento daquele tipo deve ter, isso se chama Classe. Observe a Listagem 1. Listagem 1. Classe C# de pessoa
class Pessoa
{
}
Tudo que faz parte de uma pessoa deve estar contido neste bloco de chaves. Uma pessoa possui características que as diferem de outros seres do mundo real, isso no mundo da programação é chamado de Atributos (Listagem 2). Listagem 2. Atributos C# de pessoa class Pessoa
{
string nome;
int olhos,bracos,pernas;
string cor_olhos;
string cor_cabelos;
}
Nota: É uma boa prática declarar todos os atributos (variáveis) com o modificador de acesso private. Os atributos devem vir acompanhados dos tipos de dados e também dos modificadores de acesso, neste caso não estamos declarando nenhum modificador de acesso, o C# implicitamente introduz a palavra “private” antes dos atributos e métodos (veremos a seguir). Classes só podem ser “public” ou “internal”. No Java, que possui uma estrutura semelhante à linguagem C#, seria a palavra “default”. Uma pessoa também possui um comportamento diferenciado de todos os outros elementos da natureza, na POO isso é conceituado como métodos (Listagem 3). Listagem 3. Métodos C# class Pessoa
{
string nome;
int olhos, bracos, pernas;
string cor_olhos;
string cor_cabelos;
void andar(int velocidade)
{
// Ande um pouco
}
void falar()
{
// Converse mais
}
void comer()
{
// alimente-se mais
}
}
Finalmente criamos e modelamos o objeto. Isso pode parecer simples mas no entanto é fundamental para que um projeto orientado a objetos tenha sucesso que o programador de sistemas conheça os conceitos abordados anteriormente.
Refinando a classe “Pessoa” Um dos pontos fortes da Orientação a objetos é o reuso de código, ou seja, permitir que outras rotinas utilizem funções predefinidas no sistema. Ou seja, evitar escrever uma rotina várias vezes em locais diferentes. A nossa classe pessoa possui alguns atributos e métodos que podem ser utilizados por outras classes, no entanto falta um pequeno detalhe para que essa comunicação seja realizada. Veja o seguinte exemplo da Figura 1.
Figura 1. Método inacessível Para utilizar a classe Pessoa é necessária a criação de objetos vinculados a esta classe. Para fazer isto seguimos os seguintes passos: · Declarar a classe em que deseja que o objeto pertença; · Crie um nome para o objeto, neste caso declaramos como ”p”; · Acrescente o sinal de “=”, que neste caso não é uma comparação e sim uma atribuição; · “new” para a criação de todo objeto; Veja o resultado na Listagem 4. Listagem 4. Objeto C# Pessoa p = new Pessoa();
Veja que ao fazer a chamada ao método “falar()”, o IntelliSense não reconheceu este procedimento, pois o nível de acessibilidade (como foi visto anteriormente) está
declarado de forma implícita como “private”. Uma forma de resolver este problema é declarar o modificador de acesso public no início dos métodos, conforme a Listagem 5. Listagem 5. Métodos públicos class Pessoa
{
string nome;
int olhos, bracos, pernas;
string cor_olhos;
string cor_cabelos;
public void andar(int velocidade)
{
// Ande um pouco
}
public void falar()
{
// Converse mais
}
public void comer(string comida)
{
// alimente-se mais
}
}
Na classe Program.cs instancie (crie) um objeto do tipo de pessoa e cheque se o Visual Studio reconhece os métodos e propriedades a partir do IntelliSense. Repare que os métodos são reconhecidos, no entanto as propriedades (atributos) ainda continuam invisíveis, identificamos que isso acontece por causa dos modificadores de acesso. O nível de proteção está sendo restringido, isso na POO é definido como encapsulamento. Para resolvermos os níveis de acesso dos atributos seria muito mais fácil declararmos todos como “public”, a visibilidade se estenderia a todos a classes da aplicação, no entanto não é uma boa prática fazer isto, pois desta forma toda classe pode alterar os atributos. Uma forma elegante de tratar isto é manter os atributos como privados e utilizar os “Getters and Setters”, como o próprio nome diz “Retornar e alterar”. O método “Get()” será responsável por retornar alguma informação do objeto atual, enquanto o “Set(param)” realizará a alteração do objeto. Vejamos a Listagem 6. Listagem 6. Get and Set C# public string getNome()
{
return nome; // Retorne o nome da pessoa
}
public void setNome(string nome)
{
this.nome = nome; //Altere o nome da pessoa
}
Essa é a estrutura básica dos Getters and setters em C#. Veja que no método getNome(), nenhum parâmetro é especificado, apenas a palavra reservada “return” vem seguida do atributo desejado, neste caso o “nome”. No método setNome(string nome), ao contrário do método getNome(), declaramos um parâmetro que é justamente o valor a ser alterado e não possui nenhum tipo de retorno, por isso utilizamos a palavra “void”.
Nota: Veja que agora podemos manipular as variáveis através dos getters and setters, mesmo os atributos declarados como “private”. Isto é uma boa prática utilizada em todo projeto. Podemos ver que a única classe que pode alterar as variáveis é a classe que possui os métodos get e set declarados. Caso queira um atalho para esses métodos digite “prop” e em seguida aperte duas vezes a tecla tab; será criado um get e set automático. A estrutura será igual a essa: public int MyProperty{get;set ;}. Uma grande vantagem é que desta forma a variável é criada implicitamente nessa propriedade. Na Listagem 7 vemos a execução do projeto. Listagem 7. Executando o projeto using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace POO
{
class Program
{
static void Main(string[] args)
{
Pessoa p = new Pessoa();
p.setNome("Tiago de Oliveira Vale");
Console.WriteLine("A pessoa: " + p.getNome() +
" foi criada com sucesso");
Console.ReadKey();
}
}
}
Execute o projeto e visualize a informação apresentada na tela de console. Primeiro foi criado o objeto “p” do tipo Pessoa, em seguida realizamos uma chamada ao método setNome(string nome), para alterar o nome do objeto e em seguinte o objeto chama o método getNome() para retornar o nome da pessoa.
Inicialização dos objetos Existem objetos que possuem valores padrões, ou seja, nós queremos que toda instância de uma classe apresente os mesmos valores. No nosso exemplo declaramos variáveis de tipos primitivos com os seguintes nomes: bracos, pernas, olhos. Para fazer isto podemos utilizar os getters and setters, mas só isso não ia adiantar, seria necessário declarar manualmente a quantidade de braços, olhos e pernas. Assumindo que nosso sistema não terá ninguém com três olhos, dez pernas e cinco braços, faz-se necessário a criação de um construtor. O .NET nos presenteia com um construtor padrão que não recebe nenhum argumento e ele é executado sempre que criamos um objeto na memória (Listagem 8). Listagem 8. Construtor padrão public Pessoa()
{
}
Observe que em nosso exemplo não criamos este construtor, no entanto se precisarmos de mais construtores é necessário que especifique o “construtor padrão”. Todo construtor deve possuir o mesmo nome da classe. Observe a Listagem 9.
Listagem 9. Classe Pessoa final com construtores using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace POO
{
public class Pessoa
{
public Pessoa(string cabelo)//Valor obrigatório
{
Olhos = 2;//Valor default
Bracos = 2;
Pernas = 2;
CorCabelo = cabelo;
}
public Pessoa() {
}
public string Nome { get; set; }
public int Olhos { get; set; }
public string CorCabelo { get; set; }
public int Bracos { get; set; }
public int Pernas { get; set; }
}
}
Veja como a estrutura da nossa classe está bem mais enxuta com os getters and setters automáticos. Observe que em vez de apenas um construtor temos dois. Agora veja a Listagem 10. Listagem 10. Program.cs final using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace POO
{
class Program
{
static void Main(string[] args)
{
Pessoa p = new Pessoa();//UTILIZANDO CONSTRUTOR DEFAULT
p.Nome = "Tiago de Oliveira Vale";
p.Bracos = 2;
p.Pernas = 2;
p.Olhos = 2;
p.CorCabelo = "Preto";
Console.WriteLine(p.Nome + " possui " + p.Bracos +
" braços," + "\n" + p.Pernas + " pernas, \n " + p.Olhos +
" olhos e cabelo " + p.CorCabelo+"\n");
Pessoa p1 = new Pessoa("Loiro");
//UTILIZANDO CONSTRUTOR PERSONALIZADO
p1.Nome = "Fulano";
Console.WriteLine(p1.Nome+" possui "+p1.Bracos+
" braços"+",\n"+p1.Pernas+" pernas, \n "+p1.Olhos+
" olhos e cabelo "+p1.CorCabelo);
Console.ReadKey();
}
}
}
Observe como reduzimos a quantidade de linhas de código com um construtor personalizado, sem dúvidas a melhor saída para utilizar valores default na POO. Na Figura 2 vemos o resultado do nosso exemplo.
Figura 2. Executando o projeto Portanto, neste artigo abordamos os conceitos básicos de orientação a objetos, aprendemos como modelar uma classe e algumas boas práticas adotadas pelo mercado, como a utilização de getters and setters. Todos os exemplos foram utilizados com a linguagem C#, mas os conceitos aprendidos são reaproveitados para outras linguagens que suportam a orientação a objetos.
Orientação a Objetos em .NET: uma abordagem completa Facebook Twitter (31) (0)
Veja nesse artigo os conceitos fundamentais sobre o paradigma de programação orientada a objetos. Demais posts desta série: Programação Orientada a Objetos: OO na Prática e Encapsulamento – Parte 2 Artigo no estilo Curso
Fique por dentro Este artigo trata de fazer uma abordagem teórica e prática sobre os principais conceitos da POO (Programação Orientada a Objetos), explorando temas importantes como o encapsulamento, herança e polimorfismo. O leitor terá a oportunidade de conhecer os paradigmas de programação que antecederam o principal paradigma usado atualmente, o paradigma da programação orientação a objetos.
Este artigo também fala a respeito do surgimento da orientação a objetos, desde o processo de abstração até o entendimento do objeto e suas características como a identidade, propriedades, estado e comportamento, para que seja possível mostrar ao leitor uma base inicial da orientação a objetos antes de mostrar exemplos práticos dos principais recursos da POO com a linguagem de programação C#. Esse artigo abordará assuntos importantes a respeito da programação orientada a objetos. Aqui serão apresentados ao leitor os conceitos introdutórios à orientação a objetos, como o conceito de objetos e suas representações gráficas através dos diagramas da UML, chegando até a prática propriamente dita, onde iremos mostrar exemplos de como pôr em prática os conceitos da OO com a linguagem de programação C#. Veremos como utilizar os conceitos de encapsulamento, herança, polimorfismo, dentre outros assuntos inerentes.
Os paradigmas das linguagens de programação Antes de entrarmos no mundo da programação orientada a objetos, precisamos conhecer um pouco sobre os paradigmas das linguagens de programação de computadores, paradigmas estes que até os tempos atuais ainda dividem espaço junto à comunidade desenvolvedora de software, buscando a resolução de problemas no mundo real, criando soluções para os problemas de forma computadorizada, através do produto de software. Antes da programação do software, o problema a ser revolvido deve ser analisado e transformado em uma documentação contendo aspectos fundamentais ao domínio do negócio e de sua solução, documentação esta que posteriormente servirá como base para desenvolver o produto de software, ou sistema computacional, que tenha como objetivo principal a resolução do problema central e assim possa cumprir com seu papel, atendendo a todas as necessidades a que se propõe. Mas a final, o que é o tal paradigma? Podemos conceituar um paradigma como sendo uma visão, ou um ponto de vista, do mundo real (da realidade em que vivemos) e a forma de atuação sobre tal concepção. Resumindo, é a forma como o analista e o programador lidam com um determinado problema na busca de uma solução em forma de sistema de software. Os paradigmas são classificados em imperativo, estruturado, funcional, lógico e orientado a objetos. Cada qual possui seus conceitos e métodos de como abordar um determinado problema do mundo real para elaborar e propor uma solução para o mesmo. Há seguir conheceremos um pouco sobre os paradigmas das linguagens de programação: · Paradigma Imperativo: o paradigma imperativo foi o primeiro paradigma a existir. Também é conhecido como paradigma procedural e trata de resolver um problema com comandos (instruções) um após o outro, até sua solução, ou seja, de forma sequencial. A arquitetura de computadores exerce uma forte influência para projetar uma linguagem de programação, assim sendo, as linguagens consideradas imperativas têm como base a tão conhecida arquitetura de computadores de Von Neumann. A linguagem de programação imperativa tem algumas características como: as variáveis que reservam os espaços na memória, comandos para atribuição e transferência de dados, execução sequencial das instruções e a possibilidade de repetição de blocos de instrução. Podemos considerar como linguagens imperativas o ALGOL, Assembler, Basic, Pascal, Cobol, C, Fortran e etc. · Paradigma Estruturado: este paradigma é caracterizado pela forma de programação que deve ser composta por três estruturas simples: sequência, decisão e iteração. Na
prática, é uma programação modular, onde a estrutura de um programa deve ser simples e usar funções e sub-rotinas. Cobol, Pascal, Basic e C são exemplos de linguagem de programação estruturada. · Paradigma Funcional: o propósito deste paradigma é dividir um problema em partes menores (funções) que recebem dados de entrada e retornam dados na saídas para a base chamadora. Dessa forma, este paradigma busca na programação resolver o problema central através da divisão do problema em partes menores e, ao final do processamento, mostrar o resultado. A linguagem LISP é baseada no modelo funcional. · Paradigma Lógico (declarativo): para definir este paradigma precisamos fazer uma pergunta que o precede: Qual o problema? O mesmo trata de descobrir um algoritmo geral para res" [...]
Orientação a Objetos em .NET: uma abordagem completa – Parte 2 Facebook Twitter (9) (0)
Veja neste artigo a prática da Programação Orientada a Objetos, criando uma classe a partir de um diagrama UML, definindo seus atributos e também como funciona o encapsulamento. Demais posts desta série: Programação Orientada a Objetos: Conceitos fundamentais - Parte 1 Artigo no estilo Curso
Fique por dentro Este artigo é importante, pois mostra em detalhes os principais conceitos da programação orientada a objetos (POO). Assim, o leitor terá uma base mais sólida para iniciar ou seguir sua carreira na programação de sistemas orientado a objetos.
A utilização da O.O. na construção de um software o torna mais próximo da compreensão do problema do mundo real a ser resolvido através de uma solução computacional, pois o software é modularizado através de suas classes com responsabilidades distintas, sendo que cada classe representa aspectos importantes da entidade da realidade em se detêm o contexto do problema. No primeiro artigo sobre a POO focamos em uma introdução aos paradigmas das linguagens de programação, introdução a orientação a objetos e conhecemos um pouco da POO e sua estrutura e como funciona no mundo real. Neste segundo artigo iremos de fato aprender a programação orientada a objetos, ou seja, criar uma classe a partir de um diagrama UML, definir seus atributos, criar e simular a implementação de métodos, entender os modificadores de acesso, o encapsulamento, gets e sets e por fim instanciaremos um objeto fazendo uso de seus recursos em aplicação Windows Forms. Para isso, será demonstrado o funcionamento POO através da linguagem de programação C#. O mais interessante na POO é poder capturar objetos e características das entidades e assim iniciar o processo modelagem das classes que irão compor um sistema. Este conjunto de classes troca mensagens entre si, e é importante ressaltar que um programador faz uso das boas práticas que a OO nós trás e deixa de lado o vício de ficar criando e reinventando código a cada novo projeto que surge. Uma equipe de desenvolvimento que se presa faz uso de boas bibliotecas de código que já deram certo em projetos passados, melhorando o tempo de produção do sistema. De agora em diante iremos analisar representações de um sistema orientado a objetos através da modelagem UML e codificar o mesmo em um sistema real com o Visual Studio 2012 e linguagem de programação C# da Microsoft.
Composição e codificação de uma classe Uma classe representa uma entidade do mundo real, seja algo concreto ou abstrato. Assim, precisamos abstrair pontos importantes da entidade que fazem sentido na proposta do software a que se pretende construir. As abstrações retiradas das entidades devem compor a estrutura da classe formando seus atributos e métodos. A seguir iremos detalhar algumas das características importantes que o leitor deve ter conhecimento a respeito de uma classe. Para isso, teremos como base uma classe representada através do diagrama de classes da UML, conforme a Figura 1, pois trata
de representar a base para criar um objeto produto ou um conjunto deles de forma abstrata para que possamos manipular informações de produtos do mundo real em nossos sistema computacional. Perceba na representação que temos vários atributos como o nome do produto, preço de venda, data de cadastro, e cada atributo é de um determinado tipo de dado como um inteiro, sequência de caracteres string, número decimal, valor booleano e um tipo que representa data e hora na linguagem de programação C#.
Figura 1. Classe produto representação com diagrama UML Ainda observando a classe produto da Figura 1 temos os métodos que representam as operações que a classe pode executar, neste contexto temos quatro operações que representa um CRUD (BOX 1) para a manipulação de dados em um banco de dados qualquer. BOX 1. CRUD
O CRUD é um acrônimo de create, read, update e delete, que são quatro operações básicas de inclusão, consulta, alteração e exclusão de dados em um SGDB. Estas operações geralmente são realizadas em banco de dados relacionais através de transações enviadas por um cliente, ou seja, um software pode manipular dados entre o banco de dados onde estão armazenados os dados e a exibição em UI para o usuário final, pois a interface de usuário pode permitir ao mesmo realizar as operações CRUD de forma simples e eficaz. Para tornar este artigo bem produtivo, daqui para frente usaremos duas ferramentas para exemplificar a estrutura da POO. Neste caso iremos usar o Visual Studio 2012 para criar um projeto e codificar o mesmo em C# e também será disponível para download o projeto UML criada com a ferramenta Astah Community no qual pode ser baixado no link que se encontra na sessão Links ao final do artigo. Em projetos de software bem organizados e estruturados, a codificação das classes de um sistema é criada a partir de diagramas de classes da UML, que é uma linguagem de
modelagem bem difundida há anos e que auxilia na construção de sistemas e seus diagramas fazem parte da documentação. Os conceitos da UML são bem aplicados dentro da engenharia de software etapa esta que antecede a codificação. Geralmente, a documentação inicial do software tem diagramas de caso de uso que representam as funcionalidades do sistema a que se pretende construir. Os diagramas UML que representam as características do software são criados por um analista de negócio, analista de sistemas ou pelo um arquiteto de software. Para codificar um software e iniciar a criação de suas classes é necessário escolher uma linguagem de programação e a ferramenta IDE para ajudar a automatizar parte das tarefas durante o processo de desenvolvimento como, por exemplo, a compilação do software, que é o processo que transforma o código fonte escrito em um código que possa ser entendível pelo hardware e assim execute o programa construído em cima sistema operacional. Partindo deste princípio uma linguagem de programação segue uma sintaxe, que são regras a serem seguidas na escrita da codificação do software, ou seja, os comandos da própria linguagem de programação seguem uma ordem lógica. Na Figura 2 podemos ver a sintaxe correta para escrever uma classe. Vejamos que a sequência de comandos deve ser seguida na ordem correta, informando o modificador de acesso, palavra que define que é uma classe e o nome da classe. Após isso, temos um par chaves que definem o escopo da classe. É aqui que devemos colocar os atributos, métodos, variáveis e a lógica para a classe.
Figura 2. Sintaxe para criar uma classe Para escrever uma classe devemos seguir uma nomenclatura para a escrita do nome da classe e de seus atributos e métodos. Assim, a primeira letra do nome da classe deve estar em maiúsculo e, caso tenha palavras compostas, devemos colocar a primeira letra da palavra subsequente também em maiúsculo para diferenciá-las, ou também podemos separar as palavras pelo caractere “_”.
As primeiras letras dos atributos e métodos de uma classe devem ser escritas em minúsculo e o restante da composição do nome dos atributos e métodos pode seguir o mesmo conceito do nome de uma classe. A ideia da nomenclatura é que se possa diferenciar logo de cara, na hora da codificação, o que é uma classe e quais são atributos, métodos e instância de classe. No mercado existem ferramentas capazes de diferenciar os tipos objetos, como é caso do Visual Studio.
Modificadores de acesso As classes e seus membros internos, como os atributos e métodos, possuem um nível de acessibilidade ou visibilidade tanto para o acesso interno quanto externo, ou seja, é a forma de segurança de definir se os componentes do software poderão ser visíveis e usados apenas pelo próprio sistema ou se outros sistemas poderão fazer uso de componentes, como o conjunto de classes através de seus assemblies. Como estamos falando de orientação a objetos, logo pensamos na reutilização do código. Para fazer uso desse recurso sempre se deve levar em conta o aspecto da segurança para que o software (ou parte dele) não fique vulnerável. Pensando nisso, devemos definir a acessibilidade através de modificadores de acesso. A seguir veremos os tipos de modificadores: · public: Define que a acessibilidade de um tipo ou membro dele pode ser acessado pelo mesmo assemblie onde está contido. É possível que outro sistema possa fazer referência a ele e faça uso de seus recursos. · private: Define que o tipo ou membro dele pode ser acessado apenas pelo código na mesma classe. · protected: Define que o tipo ou membro dele pode ser acessado apenas pelo código na mesma classe ou uma classe derivada da classe base. · internal: Define que a classe pode ser acessada por qualquer código no mesmo assemblie, mas não através de outros assemblies. Para entender melhor este conceito imaginemos um sistema com diversas classes e cada uma com o propósito de executar uma tarefa específica. Assim, só será possível executar tarefas se o método main tiver instâncias de várias classes para executar as tarefas que lhe é designado e concluí-las, assim, precisamos definir o nível acesso e tornar visível as classes que método main irá precisar para cumprir seu papel em executar as tarefas. O conceito de acessibilidade de atributos e métodos em uma classes também é de suma importância quando aplicamos o encapsulamento. Veremos detalhes o encapsulamento mais a frente neste artigo.
Atributos
Os atributos de uma classe representam as características abstraídas do objeto do mundo real, assim, uma classe pode ter vários atributos (ou campos). Cada atributo em uma classe em C# deve ter um tipo predefinido no momento de sua criação, já que a linguagem é fortemente tipada. Uma característica importante a respeito dos atributos é que os mesmos sempre devem ter seu modificador de acesso definido como privado, já que é uma boa prática de programação encapsular, principalmente quando se programa com uma linguagem de programação orientada a objetos e o uso do recurso de encapsulamento de dados em uma classe se faz necessário." [...]
Trabalhando com Structures e Enum em C# Facebook Twitter (3) (0)
Veja nesse artigo o desenvolvimento de um projeto com o Visual Studio, mostrando boas práticas de programação no desenvolvimento de Structures e Enums. Esse artigo trata dois assuntos bastante importantes e que segue uma das boas práticas de programação que é o uso de “Structures” e “Enum”e mostrar como usar os mesmo em seu projeto. Vamos primeiramente falar um pouco sobre o que é um “Structures”. Muito semelhante a uma classe, a Structure requer menos memória que uma classe, instancia mais rapidamente e é considerada uma boa prática de programação para objetos menores, mas somente para objetos menores. Um tipo struct é um tipo de valor normalmente usado para encapsular pequenos grupos de variáveis relacionadas, como as coordenadas de um retângulo ou as características de um item em um inventário. Os Structs são tipos de dados definidos pelo usuário e que consistem em um conjunto de variáveis simples. Os tipos de valores são armazenados no stack e cada vez que alteramos seu valor, um novo endereço de memória é utilizado. Além disso, as structs se comportam como classes.
Apesar das funcionalidades semelhantes, as structs são geralmente mais eficientes do que classes em termos de consumo de recursos. Para decidir quando usar uma estrutura ao invés de classes, deve-se analisar se o objeto:
Possui instâncias com tamanho de até 16 bytes; Não é alterado com frequência após sua criação; Não é convertido para uma classe.
As structs podem implantar uma interface, mas não podem herdá-la de outra struct. Por esse motivo, as struct membros não podem ser declarados como protegidos. Uma estrutura possui as seguintes características:
Pode ter métodos, campos, indexadores, propriedades, métodos de operação e eventos; Pode ter construtores definidos, mas não destruidores. No entanto, você não pode definir um construtor padrão para uma estrutura, pois ele é definido automaticamente e não pode ser alterado; Não pode herdar outras estruturas ou classes; Não pode ser usada como uma base para outras classes ou estruturas; Pode implementar uma ou mais interfaces; Membros de estrutura não podem ser especificados como abstratos, virtuais ou protegidos; Quando se cria um objeto struct usando o operador ”New”, ele é criado e o construtor apropriado é chamado. Ao contrário de classes, estruturas podem ser instanciadas sem usar o novo operador; Se o operador New não é utilizado, o campo permanecerá não atribuído e o objeto não pode ser utilizado até que todos os campos são inicializados.
Uma estrutura em C# é simplesmente um tipo de dados composto que consiste em um número de elementos de outros tipos. Pode conter campos, métodos, constantes, construtores, propriedades, indexadores, operadores e até mesmo outros tipos de estrutura.
Veja Também
Curso Básico de C# Curso de C# Avançado Curso de Padrões de Projetos em C#
A palavra-chave struct pode ser usada para declarar uma estrutura. A forma geral de uma declaração de estrutura em C# é a mesma apresentada na Listagem 1. Listagem 1. Estrutura Declaração e criação de objetos struct
{
// membros Estrutura
}
Os objetos de uma struct podem ser criados usando o operador New, como se segue na Listagem 2. Listagem 2. Instanciando uma Struct. MinhaEstrutura ms = new MinhaEstrutura ();
Uma struct em C# podem conter vários campos que podem ser declarados como privados, públicos ou interno. Lembre-se que dentro de uma struct só podemos declarar um campo. Não podemos inicializar um campo dentro de uma estrutura. No entanto, podemos usar um construtor para inicializar os campos de estrutura. Uma struct também pode conter métodos estático ou não estático, mas os métodos estáticos podem acessar somente outros membros estáticos e não podem invocar usando um objeto da estrutura, mas apenas usando o nome da struct. Os operadores podem ser sobrecarregados dentro da struct e as mesmas regras aplicáveis para a classe C# também são aplicáveis neste caso. Ambos os operadores unários e binários podem ser sobrecarregados. A estrutura não pode herdar de outra classe ou estrutura e não podem ser a classe base para uma classe. Mas lembre-se que em C# todos os tipos são direta ou indiretamente herdando o objeto classe base super e, portanto, a estrutura também. As estruturas não suportam herança e não podemos usar as palavras-chave virtual, override, new, abstratic, com os métodos struct. Os tipos de struct nunca são abstratos e estão sempre implicitamente vedados. Os modificadores abstratos ou selados não são permitidos em uma declaração struct. Como a herança não é suportada por estruturas, a acessibilidade declarada de um membro do struct não pode ser protegido ou interno.
Todos os tipos struct são implicitamente herdados de classe de objeto e é possível substituir os métodos da classe de objeto dentro de um struct usando a palavra-chave override. Lembre-se que este é um caso especial em estruturas C#. Apesar das funcionalidades semelhantes, as structs são geralmente mais eficientes do que classes em termos de consumo de recursos. Para decidir quando usar uma estrutura ao invés de classes deve-se analisar se o objeto:
Possui instâncias com tamanho de até 16 bytes; Não é alterado com frequência após sua criação; Não é convertido para uma classe.
As Structs devem ser usadas para objetos com poucas informações e por objetos como tipo .NET, como Integer, String, Decimal, Date e etc. Podemos usá-las sempre que você precisar de uma estrutura para armazenar dados e não precisar criar heranças, polimorfismos e quando a quantidade de dados a armazenar seja até 24 bytes. Para acima desse valor use classes. A estrutura encapsula um pequeno grupo de variáveis relacionadas dentro de um único tipo de dados definido pelo usuário. Além disso, melhora o uso de velocidade e memória, além da melhora no desempenho e clareza de seu código. Uma estrutura pode ser definida em um namespace, independentemente de qualquer outro tipo definido pelo usuário (classe ou estrutura), ou pode ser definida como um tipo aninhado dentro da declaração de outro tipo. Quando for necessário criar um objeto de estrutura usando o operadornew, o construtor apropriado é chamado.Diferentemente das classes, as structs podem ser instanciadas sem usar o operador new.Em tais casos, não há nenhuma chamada de construtor, que faz a alocação mais eficiente. Vamos mostrar na prática como podemos usar uma estrutura em nossos projetos. Para começarmos, abra o Visual Studio e crie um projeto Windows Forms, como mostra a Figura 1.
Figura 1. Criando um novo projeto Depois do projeto ControlIT.SisPac estiver criado, vamos criar a estrutura com o código da Listagem 3. Listagem 3. Criando uma estrutura. public struct stEndereco
{
private string tipologra;
private string logradouro;
private int numero;
private string complemento;
private string bairro;
private string cidade;
private string uf;
private string cep;
}
A palavra Strucure identifica um código de declaração de uma estrutura. Em seguida foram definidos os campos que vão fazer parte junto com seus respectivos tipos. Não existe nenhuma herança para estruturas como há para classes.Uma estrutura não pode herdar uma outra estrutura ou classe, e também não pode ser base de uma classe.Estruturas, no entanto, herdam da classe baseObject. Na Listagem 4 temos a criação dos objetos criados a partir da Structure. Listagem 4. Criação de objetos public string Tipologra
{
set { tipologra = value; }
get { return tipologra; }
}
public string Logradouro
{
set { logradouro = value; }
get { return logradouro; }
}
public int Numero
{
set { numero = value; }
get { return numero; }
}
public string Complemento
{
set { complemento = value; }
get { return complemento; }
}
Na Listagem 5 temos a lista de parâmetros passados ao se criar o construtor. Esses parâmetros recebidos servem para iniciar os valores dos atributos. Listagem 5. Parâmetros public stEndereco(string ptl, string plo, int pnu, string pco)
{
tipologra = ptl;
logradouro = plo;
numero= pnu;
complemento = pco;
}
Depois disso vamos criar o formulário de cadastro de clientes para testar nossa aplicação, conforme mostra a Figura 2.
Figura 2. Tela de Cadastro de Cliente Repare que a tela é bem simples, apenas com os labels e os texts para recolher as informações e um único botão, o qual terá a mesma configuração presente na Listagem 6. Listagem 6. Botão salvar de nossa aplicação. private void bt_salvar(object sender, EventArgs e)
{
stEndereco ste = new stEndereco();
ste.Tipologra = cmbTipoVia.Text;
ste.Logradouro = txtLogradouro.Text;
ste.Numero = txtnumero.Text;
ste.Complemento = txtcomplemento.Text;
MessageBox.Show("ok");
}
Enum Uma das boas práticas de programação em C# é o uso do enum. Ele serve para substituirmos constantes nomeadas que são relacionadas mas ficam “perdidas” no código. A palavra-chave enum é usada para declarar uma enumeração, um tipo distinto que consiste em um conjunto de constantes. O Enum é um tipo de valor e não pode herdar ou ser herdado. Geralmente é melhor definir um enum dentro de um namespace para que todas as classes dele possam acessá-lo. No entanto, um enum também pode ser aninhado em classes ou Structures. O. NET Framework Class Library enums contém muitos exemplos de como eles são utilizados. Por exemplo, toda vez que você colocar um ícone em um MessageBox, você usa o MessageBoxIcon ENUM. Sempre existem situações em que você está usando um conjunto de números relacionados em um programa, então considere substitui estes números com enums. Ele é um objeto string cujo valor normalmente é escolhido de uma lista de valores permitidos e que são enumerados explicitamente na especificação da coluna na criação da tabela. O valor pode ser a string vazia ("") ou NULL sob as seguintes circunstâncias:
Se você inserir um valor inválido em um enum(isto é, uma string que não está presente na lista de valores permitidos), a string vazia é inserida no lugar como um valor especial de erro. Esta string pode se diferenciar de uma string vazia 'normal' pelo fato de que esta string tem o valor numérico 0. Veremos mais sobre este assunto mais tarde. Se um enum é declarado null, null é também um valor permitido para a coluna, e o valor padrão é null. Se um enum é declaarado not null, o valor padrão é o primeiro elemento da lista de valores permitidos.
Cada enumeração tem um índice, onde:
Valores da lista de elementos permitidos na especificação da coluna são números começados com 1; O valor de índice de uma string vazia que indique erro é 0.
Por padrão, o primeiro enumerador tem o valor 0 e o valor de cada enumerador sucessor aumente em 1. Os tipos aceitos para um enum são byte, sbyte, short, ushort, int, uint, long, ou ulong. Uma variável do tipo Dia pode ser atribuída a qualquer valor no intervalo do tipo subjacente, pois os valores não são limitados às constantes nomeadas. Porém, algumas vezes torna-se necessário fazer uma representação dos valores deste Enum na interface, mais comumente em um DropDownList. O Enum tem os métodos descritos a seguir:
String toString(), que retorna uma string com o nome da instância (em maiúsculas); valueOf (String nome), que retorna o objeto da classe enum cujo nome é a string do argumento e; int ordinal(), que retorna o número de ordem do objeto na enumeração.
Em se tratando do tempo de execução no projeto, um tipo de dado enumerado é geralmente implementado usando-se inteiros. Entretanto, comparando-se a usar somente inteiros, os tipos enumerados tornam o código fonte mais bem documentado do que através do uso explícito de "números mágicos". A seguir estão vantagens de usar um enum em vez de um tipo numérico: Você especifica claramente para o código do cliente que os valores são válidos para a variável; No Visual Studio, o IntelliSense lista os valores definidos. Os Enum são tipos que herdam a System.Enum na Base Class Library (BCL). Um requisito comum é converter entre o ENUM e uma variável do tipo sua base. Caso você esteja recebendo contribuições na forma de um int de um usuário ou um arquivo, então você pode lançá-la para um ENUM e utilizá-la de forma significativa em seu programa. Devemos considerar que a manipulação de variáveis do tipo valor oferece uma performance superior a variável do tipo referência, por não possuir a necessidade de alocação em heap (área de alocação dinâmica) e não ser acessada através de um ponteiro. Dependendo da linguagem, a representação de inteiros pode não ser visível ao programador, o que previne operações como aritmética. Uma das vantagens de se ter enum é que podem ser tratados como se fossem inteiros, ou seja, podemos usar em switchs e ifs, além de podermos comparar a tipagem ao invés de comparar strings.
Nas Listagens 7 a 9 temos exemplo de três Enums: Enum Status, Tamanho e TipoCliente. É possível criar qualquer tipo de lista enumerada e utilizá-la para referenciar internamente no código, deixando mais claro seu código e ações. Listagem 7. Lista enumerada de Status. public enum Status
{
Ativo = 1;
Inativo = 2;
Aguardando = 3;
Cancelado =4;
}
Como você pode observar, a sintaxe da declaração da estrutura acima é semelhante a de uma classe, ou seja, uma estrutura pode possuir construtores, porém não possui destrutores e os construtores têm de ser customizados. A tentativa de declarar o construtor default gera um erro de compilação. Listagem 8. Lista enumerada de Tamanho. public enum Tamanho{ pequeno, medio, grande, extragrande }
Listagem 9. Lista enumerada de TipoCliente public enum TipoCliente
{
Pessoafisica = 1;
Pessoajuridica = 2;
Orgaopublico = 3;
ONG = 4;
}
Veja que foi criada uma lista enum do tipo de cliente que será gravada no banco somente aquele que tiver o valor inteiro, e toda vez que for exibido ao usuário por meio de uma interface, será exibida a descrição ao invés do valor inteiro. Dentro do conceito que é chamado de programação robusta, ou como acontece com qualquer constante, todas as referências para os valores individuais de um enum são convertidas em numéricos em tempo de compilação.Isso pode criar possíveis problemas de versionamento. Atribuindo valores adicionais para novas versões de enums ou alterando os valores dos membros enum em uma nova versão, pode causar problemas para o código-fonte dependente.Valores de enumeração são frequentemente usados em alternar instruções.Se os elementos adicionais foram adicionados para o enum,pode ser selecionado o tipo e a seção padrão da instrução switch inesperadamente. O enum nos permitem criar listas para tornar mais fácil a vida do desenvolvedor e principalmente para evitar erros de comparação e ainda padronizar determinada opção dentro do sistema, pois são basicamente listas que são enumeradas. Desta forma, sempre terá uma Chave e um Valor que juntos formam um registro único, ou seja, não é possível criar um enumerador que tenha a mesma chave e valor. Essas listas geralmente são pequenas e atribuir valores adicionais para novas versões de enums ou alterar os valores dos membros em uma nova versão pode causar problemas para o código-fonte dependente. Valores de enumeração são frequentemente usados em alternar instruções. É possível também definir outros tipos numéricos para os elementos em vez de apenas int, por exemplo, é possível defini-los como byte e long,porém, nestes casos, você terá de fazer casta todo o momento que for usar o enumerador. Então, modificar o tipo do enumerador só é aconselhado quando realmente se faz necessário. Em um enumerador, quando não são atribuídos valores para as opções, este sempre irá iniciar com o valor zero e sempre o valor de um enumerador será um número. Não há como definir uma opção com valor string, por exemplo. No entanto, é liberado para que você mesmo atribua os valores desejados para cada elemento de um enumerador ou até mesmo que omita essa atribuição se achar necessário ou ver que é necessário em apenas alguns elementos. Se outros desenvolvedores usam seu código, você deve fornecer diretrizes sobre como o seu código deve reagir se novos elementos forem adicionados a qualquer enum.
Integração Contínua em .NET – Parte 1 Facebook Twitter (8) (0)
Veja neste artigo como automatizar a construção, documentação e entrega de seus sistemas em .NET utilizando a integração contínua. Demais posts desta série: Integração Contínua em .NET – Parte 2 Integração Contínua em .NET: Implementação – Parte 3 Artigo no estilo Curso
Fique por dentro A Integração Contínua (CI) deixa o desenvolvedor livre para se preocupar com o que realmente importa: criar e desenvolver software.
Não importa o tamanho do projeto, seja ele um pequeno sistema de cadastro de contatos ou um sistema especialista para simulações do clima terrestre ou da expansão do Universo: suas tarefas básicas consistem em compilar, testar, documentar, verificar qualidade e consistência do código e finalmente informar sobre erros. Nessa primeira parte do curso veremos uma introdução aos passos que a empresa precisa dar para implementar a CI em sua rotina de desenvolvimento. Uma das chaves para a melhoria da produtividade e, consequentemente das aplicações entregues, é a automatização da parte repetitiva do trabalho e a Integração Contínua é uma das melhores formas de se fazer isso. Não podemos crer que o processo de Integração Contínua seja a solução para todos os males, mas onde ela tem sido implementada tem-se tido grande sucesso com melhorias no processo de software para as equipes de desenvolvimento. A Integração Contínua, também conhecida como CI - Continuous Integration - é o ponto central do desenvolvimento de software atualmente. De fato, quando a CI é introduzida em uma empresa, torna-se o grande divisor de águas, pois ela altera
radicalmente a forma como as equipes pensam sobre todo o processo de desenvolvimento. A CI tem o potencial de executar uma série de melhorias contínuas e incrementais nos processos, indo de um processo de simples agendamento de construção (build) até o processo de entrega contínua que culmina no processo de publicação nos demais ambientes para teste, homologação ou produção. Uma boa infraestrutura que possibilita a Integração Contínua pode simplificar o processo de desenvolvimento até a entrega do software ou de sua iteração, auxiliando a identificação e correção de falhas rapidamente, afinal de contas, precisa integrar toda a equipe e precisa armazenar todas as versões do software. Também facilita a disponibilização de uma dashboard muito útil no Projeto, tanto para desenvolvedores quanto para equipes, por agregar mais valores ao negócio e ao produto. Todo time de desenvolvimento, não importando se grande ou pequeno, deve implementar e pensar como Integração Contínua.
O que é a Integração Contínua? Logo que uma alteração no código é identificada, o servidor automaticamente compila e testa o código de sua aplicação. Se algo dá errado, a ferramenta imediatamente notifica os desenvolvedores para que eles possam corrigir o problema imediatamente. Mas, a integração contínua pode fazer muito mais do que isso. A visibilidade das métricas de qualidade de código faz com que a equipe se preocupe mais com o desenvolvimento, pois o principal fundamento da Integração Contínua é facilitar a manutenção do código. Combinando testes de aceitação, a CI pode também funcionar como uma ferramenta de comunicação, mostrando um completo resumo do estado atual do projeto em andamento, dentro e fora da equipe. Pode simplificar e acelerar a entrega, auxiliando a automação do processo, deixando a publicação da última versão da aplicação totalmente automática ou permitindo a operação com apenas um aperto de botão. Em sua essência, a Integração Contínua age sempre na redução de riscos à aplicação e seu desenvolvimento nos dá uma resposta rápida. O uso da Entrega Contínua está ligado diretamente a Integração Contínua, pois o passo de compilação e testes é tarefa da Integração Contínua, enquanto que o Deploy da aplicação já fica sob o domínio da Entrega Contínua. Formalmente, a entrega contínua é definida como “um conjunto de práticas e princípios com o objetivo de compilar, testar e liberar software de forma mais rápida e frequente”. Observe que a definição em si não menciona programas específicos, mas sim uma filosofia a ser seguida. O processo de Integração Contínua, e em particular a Entrega Contínua, é muito mais" [...]
A exibição deste artigo foi interrompida :( Este post está disponível para assinantes MVP
Integração Contínua em .NET – Parte 2 Facebook Twitter (1) (0)
Veja nesse artigo como evitar erros comuns na implementação de projetos.Veremos na prática como implementar as boas práticas da IC. Demais posts desta série: Integração Contínua em .NET – Parte 1 Integração Contínua em .NET: Implementação – Parte 3 Artigo no estilo Curso
Fique por dentro A integração contínua (CI) é uma prática que vem se disseminando nas empresas como um todo, trazendo vantagens importantes como a automatização da parte repetitiva do trabalho do desenvolvedor, permitindo que se concentrem no que realmente importa: o desenvolvimento dos softwares. Como vimos na primeira parte dessa série, a implementação da CI pode trazer alguns problemas, muitas vezes devido a erros nessa implementação. Nessa segunda parte iremos exemplificar alguns desses erros e mostrar como eles podem ser evitados e/ou resolvidos.
A integração contínua traz grandes vantagens para as empresas no que diz respeito a integração das diferentes partes do software sendo criado em equipe. Quem trabalha em equipe, sabe que um dos principais gargalos de tempo dentro do projeto é a integração de todas as partes, ao final.
Muitas vezes, todas essas partes, que funcionam perfeitamente individualmente, quando juntas acabam não trabalhando da mesma forma. Tal problema traz um atraso bastante grande, porque um ou mais membros da equipe precisam entender o que está acontecendo e corrigir esses problemas para que o sistema completo possa ser dado como concluído.
É daí que vem talvez a principal vantagem da integração contínua. Ela traz uma ideia na qual as partes são integradas constantemente durante o desenvolvimento. Esse tipo de ação faz com que, quando o software é dado como concluído, ele funcione como um todo, e não apenas como partes. Essa atitude evita esse gargalo de tempo ao final do projeto, aumentando a produtividade e diminuindo o tempo de projeto, um ponto que todas as empresas procuram.
Porém, a CI não traz apenas benefícios e, ao longo desse artigo, iremos exibir alguns dos principais problemas que podem acontecer durante e depois da implementação desses processos. Veremos que todos eles podem ser evitados ou ao menos corrigidos, no caso de uma implementação correta. Alguns deles, inclusive, existem apenas por uma falta de entendimento do que é e como funciona profundamente a integração contínua, e como aplicá-la adequadamente dentro da empresa.
Na primeira parte dessa série trouxemos alguns elementos básicos sobre a integração contínua e alguns passos de como implementá-la corretamente dentro das empresas. Reparamos que a integração contínua consiste de uma série de fatores que precisam ser levados em conta, desde o estilo de trabalho da equipe de desenvolvimento até o hardware utilizado. Além disso, trouxemos informações a respeito dos processos de desenvolvimento para integração contínua, bem como os tipos de construção (build) utilizados (contínuo, diário/noturno, semanal, QA (BOX 1), staging/pré-produção, release). BOX 1. QA (Quality Assurance)
Essa sigla representa uma série de atividades que tem como objetivo final garantir a qualidade do software que está sendo avaliado. Como sabemos, a qualidade de um software não é medida apenas pelo fato de que ele faz o trabalho bem feito, e são medidos fatores para garantir que o software vá de encontro aos requerimentos do projeto. Esse tipo de teste é aplicado para garantir que as funcionalidades estejam funcionando corretamente, que não haja nenhum tipo de bug dentro do sistema. Essas atividades são aplicadas no software antes da produção final.
A QA é baseada em alguns princípios que irão estabelecer a qualidade do software, dependendo do que está sendo medido. Alguns deles são a verificação se o produto está dentro dos parâmetros requeridos para o seu propósito e outra, obviamente, é se o software está totalmente livre de erros. Esses dois princípios são aplicados em virtualmente todos os softwares que passam por QA.
Outro ponto importante apresentado na primeira parte dessa série foram os passos para implementação da CI dentro da empresa. Vimos que a implementação da integração contínua é baseada em sete ciclos, e é muito importante que esses ciclos sejam respeitados para que haja uma implementação gradual no dia-a-dia da empresa. Esses ciclos são resumidos a seguir: 1º) Ciclo inicial, onde não há um servidor de build disponível para a empresa, fazendo com que todos os passos de construção e integração do software sejam realizados manualmente. 2º) O time dispõe de um servidor de build, sendo que a construção é automatizada para períodos regulares, em momentos que o fluxo de trabalho é baixo. 3º) O servidor já é capaz de aplicar alguns testes ao código, além de o build contínuo já estar sendo aplicado (toda vez que o código é comitado no SCM). 4º) Aplicação da verificação de qualidade de código (QA). 5º) O time está mais organizado e a integração contínua já começa a se encaixar às práticas de teste (TDD). 6º) A maioria dos principais testes já estão incluídas na CI, bem como ferramentas de relatórios e comunicação entre os membros da equipe. 7º) A integração contínua está integralmente implementada dentro da rotina da empresa, com tudo que se espera dela. Agora que temos uma ideia mais clara sobre a integração contínua, vamos entender o que pode acontecer quando alguns dos passos são realizados de forma errada dentro das empresas. Além disso, vamos entender que, em um processo bem organizado e implementado, as vantagens da integração contínua são muito maiores do que eventuais distúrbios que ela possa trazer.
Objetivos e Vantagens da Integração Contínua Conforme foi comentado, a integração contínua, uma vez implementada no dia-a-dia da empresa pode trazer inúmeros benefícios. Ao conhecer trabalhos de grandes companhias, temos uma ideia melhor do quão bom pode ser ess" [...] A exibição deste artigo foi interrompida :( Este post está disponível para assinantes MVP
Integração Contínua em .NET: Implementação – Parte 3 Facebook Twitter (1) (0)
Veja nesse artigo como implementar os sete ciclos da Integração Contínua. Além disso, vamos aprender a usar o Git e Hudson e ver porque os dois são boas ferramentas para qualquer empresa que empregue I.C. Demais posts desta série: Integração Contínua em .NET – Parte 1 Integração Contínua em .NET – Parte 2 Artigo no estilo Curso
Fique por dentro A integração contínua (CI) é uma prática que vem se disseminando nas empresas como um todo, trazendo vantagens importantes como a automatização da parte repetitiva do trabalho do desenvolvedor, permitindo que se concentrem no que realmente importa: o desenvolvimento dos softwares.
Nos demais artigos desse curso focamos em detalhes teóricos da implementação da integração contínua nas empresas. Nesse artigo iremos começar a implementação da integração contínua em uma empresa totalmente do zero. Apresentaremos uma alternativa de servidor de CI, o Hudson, bem como o Git como servidor SCM, uma vez que apresentamos o SVN e o CruiseControl.NET (servidor CI) na segunda parte dessa série. A integração contínua é uma das principais ferramentas de integração no desenvolvimento de software em equipe atualmente. Esse tipo de técnica permite que as partes do software que estão sendo desenvolvidas por diferentes pessoas estejam integradas constantemente, evitando atrasos ao final do projeto. Porém, a implementação não é tão simples como possa parecer.
Nas primeiras partes de nossa série trouxemos os principais passos para a implementação da integração contínua e os principais erros que são cometidos. É muito importante que se tenha uma noção do que essas informações significam para a implementação correta das técnicas de CI. Como se trata de uma arte que, embora simples no contexto, traz uma série de nuances, e é muito fácil para desenvolvedores com pouca experiência se perderem nesses passos e acabarem cometendo alguns erros, como os que foram explicados na parte dois de nossa série. Inicialmente, falamos sobre o que é a integração contínua e como ela pode ser implementada dentro das empresas. Nessa primeira parte foram abordados temas como processos de desenvolvimento para integração contínua, que incluem os processos comuns à maioria das empresas mais alguns específicos para a ação do servidor de CI, onde ele observa as alterações no código do SCM, efetua o build, executa os testes unitários e envia os resultados do build e dos testes para algum tipo de sistema de comunicação. Vale ressaltar que esses passos somente são utilizados quando os processos de integração contínua estão completos, o que pode levar algum tempo. Nesse curso vimos detalhes de tipos de build, os passos pelos quais a empresa precisa passar para corretamente implementar a integração contínua e alguns pontos de custo benefício e possível resistência a mudança por parte dos membros da equipe de desenvolvimento. Além disso, no curso também foram pontuados os principais objetivos para a implementação da integração contínua. É muito importante que tenhamos essa ideia de qual é o objetivo final da implementação da IC, e de qualquer outro processo, bem como dos objetivos parciais a serem alcançados. Esse tipo de atitude traz um grande benefício para a equipe de desenvolvimento, fazendo com que a mudança não seja tão brusca e que todos tenham uma noção do lugar para onde se está indo. Nessa segunda parte, vimos também a implementação do servidor de CI, com poucos detalhes e com alguns erros criados propositalmente para entendermos o que comumente pode acontecer durante a implementação. Esses erros ocorrem, principalmente, devido à falta de conhecimento dos processos de integração contínua, acarretando muitos problemas. Durante esse artigo iremos começar a implementação de nosso servidor de integração contínua. Serão seguidos alguns passos rígidos - vistos brevemente na parte um da série - que serão vistos a seguir para termos, ao final de nossa série, uma ideia muito clara do que é a integração contínua, os erros que podem acontecer quando a implementamos e, obviamente, como implementá-la da forma mais correta com as principais ferramentas do mercado. Para isso, utilizaremos uma estrutura fictícia composta por uma empresa que está começando no mercado e quer desde o começo possuir uma estrutura de integração contínua, o que irá evitar alguns dos problemas que comentamos nas partes anteriores de nossa série.
Passos para implementação da Integração Contínua
A integração contínua é uma ferramenta muito útil para construção de softwares em equipe. É uma das chaves para a melhoria de produtividade nos dias atuais pelo fato de automatizar a parte repetitiva do trabalho. A sua implementação, entretanto, requer bastante atenção e uma série de passos que devem ser seguidos para que o impacto da implementação não seja muito grande nem que hajam erros na mesma. Isso é muito importante, uma vez que, normalmente, os softwares criados pelas empresas possuem prazos muito rígidos que podem ser prejudicados no caso de uma integração contínua inconfiável. Esses passos que serão apresentados a seguir foram apresentados na parte 1 de nossa série sob uma ótica um tanto diferente, mais didática e menos prática. O objetivo aqui é mostrar o que será feito em cada ciclo para que, quando a empresa atingir o sétimo ciclo, todo o sistema de integração contínua dentro da empresa esteja implementado. Atualmente, podemos colocar nossa empresa fictícia em um ciclo 0, uma vez que não há nenhum tipo de infraestrutura nem processos ainda disponíveis na empresa. · Ciclo 1: No primeiro ciclo, a empresa não possui nenhum tipo de servidor de build centralizado. Isso faz como que todos os builds sejam realizados manualmente na máquina do desenvolvedor. Com isso, a integração de todas as peças do software é realizada somente ao final, o que pode e deve gerar muita dor de cabeça para a equipe, além de possíveis atrasos. Nossa empresa fictícia não irá passar por esse ciclo, uma vez que iremos implementar inicialmente um servidor de build e adaptar o time de desenvolvedores para trabalhar dessa forma. · Ciclo 2: Há um servidor de build automatizados, agendados para serem operados em um período " [...] A exibição deste artigo foi interrompida :( Este post está disponível para assinantes MVP