pontoNETpt
A comunidade PontoNetPT está direccionada a todos os programadores que trabalhem com a plataforma .NET.
MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte II
Artigo anterior: MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte I

2.     Tipos por Referência

 

Mais uma vez, é importante referir que um dos mais comuns erros no início da programação em .NET tem a ver com confundir Tipos de Valor ou de Referência com passar argumentos para métodos por Valor ou por Referência. Volto a frisar que são conceitos diferentes e para o qual qualquer pessoa deve ter cuidado.

Os Tipos por Referência armazenam o endereço da sua informação na stack e também são conhecidos como apontadores (pointers). E o que quer isto dizer? Quer dizer que os dados propriamente ditos estão armazenados na heap, mas existe uma referência à sua localização na stack (um apontador),  uma área da memória que está acessível mais rapidamente pelo código em excução.

O ambiente de execução gere a memória armazenada na heap utilizando um processo denominado Garbage Collection (“Recolha de Lixo”), que consiste em ir libertando memória periodicamente, eliminando objectos à medida que estes deixam de ser referenciados.

Uma vez que os Tipos por Referência representam o endereço de memória de um pedaço de informação em vez da informação propriamente dita, assignar uma variável por referência a outra variável por referência terá como resultado a cópia do endereço de memória e não dos dados em si, como seria o caso dos Tipos de Valor. Neste caso, ficaríamos com duas variáveis a apontar para os mesmos dados.

2.1.  Tipos por Referência Nativos

 

Existem cerca de 2500 Tipos por Referência incluídos por defeito na .NET Framework, sendo que todos os tipos que não derivam de System.ValueType são Tipos por Referência. Os Tipos por Referência abaixo são os mais comuns, sendo que muitos outros Tipos por Referência derivam destes:

·         System.Object. Este é o tipo mais genérico existente na .NET Framework. Qualquer tipo pode ser convertido para este Tipo.

·         System.String. Este é um dos tipos mais utilizados na .NET Framework e serve para armazenar dados de texto.

·         System.Text.StringBuilder. Armazena dados de texto de uma forma dinâmica.

·         System.Array. É utilizado para arrays de dados sendo a classe base para todos os arrays.

·         System.IO.Stream. Trata-se de um buffer para operações de Input/Output de ficheiros, dispositivos e rede. Trata-se de uma classe base abstracta.

·         System.Exception. É utilizada para tratar excepções. Esta classe gere sobretudo excepções de Sistema, sendo que excepções específicas de tarefas em execução herdam deste tipo.

 

2.2.  Strings e Construtores de Strings (StringBuilders)

 

Os Tipos são mais do que contentores de informação e fornecem meios para manipular os dados, através dos seus membros. O Tipo System.String disponibiliza um conjunto de  membros para trabalhar com texto. O próximo pedaço de código mostra como se pode efectuar uma rápida operação de substituição de texto:

string s = “Texto que é para substituir!”;

Console.WriteLine(s);

s = s.Replace(“é para substituir”, “foi substituido!”);

Console.WriteLine(s);

Este pedaço de código iria produzir o seguinte resultado:

Texto que é para substituir!

Texto que foi substituido!

 

Contudo, objectos do tipo System.String são imutáveis, o que significa que qualquer mudança a uma string vai fazer com que o ambiente de execução crie uma nova string e abandone a anterior, de uma forma invisível para o programador.

string s = “Esta ”;

s += “operação vai “;

s += “criar 5 “;

s += “strings diferentes “;

s += “em memória!”

O código acima, utilizado várias vezes por muitos programadores com experiência em .NET, na realidade cria 5 strings diferentes em memória. Apenas a última string terá uma referência para os dados, sendo que as quatro anteriores serão descartadas no processo de Garbage Collection. Esta operação é, portanto, menos performante que utilizar os métodos Concat, Join ou Format da Classe String ou ainda a Classe StringBuider.

A opção pela Classe StringBuilder é a mais eficiente e flexível, porque permite criar strings dinâmicas (mutáveis). O construtor de defeito da Classe cria um buffer de 16 bytes que se expande à medida que vai necessitando, embora seja possível especificar um tamanho mínimo e máximo:

System.Text.StringBuilder sb = new System.Text.StringBuilder();

sb.Append(“Esta ”);

sb.Append(“operação vai “);

sb.Append(“criar apenas “);

sb.Append(“uma string “);

sb.Append(“em memória!”);

string s = sb.ToString();

Console.WriteLine(s);

 

Outra funcionalidade importante da Classe String é que esta sobrepõe os seus operadores aos da Classe System.Object:

·         Adição:  +

·         Igualdade(comparação):  ==

·         Desigualdade (comparação): !=

·         Atribuição de valor:  =

 

2.3.  Criar e Ordenar Arrays

 

Um array não é mais que uma sequência de apontadores para valores de dados armazenados em memória, ou seja, um conjunto de apontadores de referência para variáveis do mesmo tipo.

Os arrays são declarados utilizando os nomes dos tipos seguidos de parênteses rectos ([]) como parte da declaração da variável. O próximo bloco de código, por exemplo, cria um array e ordena-o:

int[] ar = {3, 4, 1, 2 };

Array.Sort(ar);

Console.WriteLine(“{0}, {1}, {2}, {3}, ar[0], ar[1], ar[2], ar[3]);

 

De notar que, em C#, quando se pretende aceder ao primeiro elemento do array, ou seja, ao elemento em primeiro lugar no índice do array, se deve utilizar o índice 0 (ar[0]). No caso acima, a declaração do array foi acompanhada pela definição dos valores que o array deveria conter, o que não é obrigatório. Aliás, a grande utilidade dos arrays reside exactamente no facto de se poder utilizar arrays para criar uma referência para dados que serão gerados ou calculados mais á frente, garantindo à partida (com a criação do array) que estas referências estão agrupadas.

int[] numeros = new numeros[3];

for (int i=0;i<3;i++) {

numeros[i] = i+1;

}

2.4.  Streams

 

As streams são utilizadas para ler e escrever para o disco e comunicar através de uma rede. Existem vários tipos de streams, nomeadamente:

·         FileStream, para ler e escrever a partir de ficheiros.

·         MemoryStream, para ler e escrever a partir da memória.

·         StreamReader, para ler dados de uma stream.

·         StreamWriter, para escrever dados para uma stream.

Estas streams derivam de System.IO.Stream, sendo que streams para aceder a recursos de rede podem ser encontradas no namespace System.Network.Sockets e streams de cifragem/decifragem encontram-se no namespace System.Security.Criptography.

As classes mais simples são o StreamReader e o StreamWriter, que nos permitem ler e escrever em ficheiros de texto. Pode-se passar o nome do ficheiro como parte do construtor, permitindo assim a abertura do ficheiro com apenas uma linha de código. De relembrar que, após o processamento do ficheiro, deve ser executado o método Close para que o ficheiro não permaneça trancado. Por exemplo:

using System.IO;

 

StreamWriter sw = new StreamWriter(“texto.txt”);

sw.WriteLine(“Olá Mundo!”);

sw.Close();

 

StreamReader sr = new StreamReader(“texto.txt”);

Console.WriteLine(sr.ReadToEnd());

sr.Close();

 

Mais adiante, no capítulo sobre Input e Output, falaremos mais em detalhe destas operações.

 

2.5.  Utilização de Excepções

 

Excepções são eventos inesperados que interrompem o decurso normal de execução de um programa. Por exemplo, se um programa está a ler um ficheiro a partir de uma localização na rede e o utilizador desliga o cabo de rede ou pura e simplesmente a rede falha por algum motivo, o ambiente de execução irá lançar uma excepção. Isto faz sentido na perspectiva em que não existem condições para que o programa continue a ler o ficheiro a partir da rede.

Contudo, as excepções não devem causar que o programa deixe de funcionar completamente. Ao invés, as excepções devem ser previstas e a sua captura deve ser planeada, para que se possa definir convenientemente como o programa deve agir em resposta a essa excepção.

using System.IO;

 

try {

   StreamWriter sw = new StreamWriter(“texto.txt”);

   sw.WriteLine(“Olá Mundo!”);

sw.Close();

} catch (Exception ex) {

   Console.WriteLine(ex.Message);

}

Neste exemplo, o ambiente de execução iria tentar executar o código incluído no bloco Try{ } e se encontrasse uma excepção, iria executar o código incluído no bloco Catch{ }. Caso não encontrasse nenhuma excepção, o programa iria continuar na linha seguinte ao bloco Catch{ }, ignorando-o.

Qualquer que fosse a excepção gerada, ela iria ser capturada pelo bloco Catch{ }, mas é possível (e desejável) que se utilize as várias classes de excepção existentes na .NET Framework para permitir uma arquitectura de manuseamento de excepções mais eficiente e performante. É ainda possível que cada programador defina as suas próprias classes de manuseamento de excepções para maior detalhe na identificação e tratamento das mesmas, criando classes derivadas de System.ApplicationException ou de System.Exception. No entanto, é considerado boa prática efectuar a definição de excepções próprias derivadas de System.Exception, uma vez que se acabou por verificar que derivar as excepções a partir de System.ApplicationException não trazia valor acrescentado.

Para além dos blocos Try{ } e Catch{ }, o manuseamento de excepções também suporta um terceiro bloco, denominado de bloco Finally{ }, que executa após a execução do bloco Try{ } e de qualquer Catch{ } que tenha sido executado, independentemente de ter ou não sido encontrada alguma excepção. Assim, a utilização do bloco Finally{ } faz sentido para executar tarefas de limpeza ou qualquer outra tarefa que tenha de ser executada sempre, haja ou não excepções a tratar.

using System.IO;

 

StreamWriter sw = new StreamWriter(“texto.txt”);

try {

   sw.WriteLine(“Olá Mundo!”);

} catch (Exception ex) {

   Console.WriteLine(ex.Message);

} finally {

sw.Close();

}

De notar que a declaração do objecto StreamWriter foi movida para fora do bloco Try{ } no exemplo anterior, porque o bloco Finally{ } não consegue aceder a variáveis declaradas no âmbito do bloco Try{ }. Isto faz todo o sentido, porque, dependendo do local onde as eventuais excepções ocorrem, as declarações de variáveis dentro do bloco Try{ } podem ainda não ter sido executadas.

É fortemente recomendado que todo o código, com excepção da declaração de variáveis, deva ser executado entre blocos Try{ }/Catch{ }/Finally{ } para melhorar a experiência do Utilizador e também para melhorar a depuração (debugging) das aplicações. De seguida (no próximo artigo) irei falar-vos de Classes.

?>

?>


Posted 25-4-2007 16:05 por Pedro Honório Silva

Comments

Pedro Honório Silva wrote re: MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte II
on 26-4-2007 12:30
Obrigado pelo teu feedback.

Não és nada "chato", antes pelo contrário. Ajudas-me a melhorar os meus posts. E eu agradeço isso! ;)

Vou fazer algumas alterações ao texto, derivadas do teu comentário.

Obrigado.
Anonymous wrote re: MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte II
on 1-7-2009 2:31
Bom dia,

Não querendo ser demasiado chato, deixo aqui alguns comentários sobre o texto.

"[o] stack [é] uma área da memória que está acessível mais rapidamente pelo código em execução [que o] heap"

Não estou a dizer que isto está incorrecto. Não sei é razão, porque também não percebo muito disto, e gostaria de discutir sobre isso. Talvez se possa dizer que os processadores (alguns) têm em conta o princípio da localidade e coloquem parte do stack em cache. Ou então que a memória do stack está numa página de memória que se garante com uma maior probabilidade de estar presente e não em swap. É alguma coisa por aqui? A um nível superior, pode-se dizer que é mais fácil arranjar um espacinho para no stack que no heap. Mas aqui estamos apenas a falar da criação e não do acesso e, de qualquer das formas, tendo em conta o algoritmo de alocação no heap utilizado, não sei se essa diferença será assim tão significativa.

"Esta operação é, portanto, menos performante que utilizar os métodos Concat, Join ou Format da Classe String ou ainda a Classe StringBuider."

De notar que a concatenação de strings (+, +=) utiliza o Concat, por isso nenhuma delas é mais eficiente que a outra. Já o Join funciona de forma diferente, criando um buffer interno e, por isso, é provavelmente mais eficiente.

"Um array não é mais que uma sequência de apontadores para valores de dados armazenados em memória, ou seja, um conjunto de apontadores de referência para variáveis do mesmo tipo."

É verdade se for um array de um tipo referência. Se for um array de um tipo valor, o array é um sequência de instâncias desse tipo.

"As streams são utilizadas para ler e escrever para o disco e comunicar através de uma rede. Existem vários tipos de streams, nomeadamente:"

Atenção que o StreamReader e o StreamWriter não são streams. São tipos que recebem streams e efectuam leituras e escritas ao nível do carácter e sequência de caracteres (as streams funcionam ao nível do byte). Por outras palavras, servem para inserir e extrair texto das streams de uma forma mais fácil.

"criando classes derivadas de System.ApplicationException."

Inicialmente foi esse o objectivo do ApplicationException. No entanto, verificou-se que não trazia valor acrescentado e a própria Microsoft sugere que as excepções da aplicação derivem directamente de Exception. Tirado dos Best Pratices for Handling Exceptions: http://msdn2.microsoft.com/en-us/library/seyhszts.aspx

"For most applications, derive custom exceptions from the Exception class. It was originally thought that custom exceptions should derive from the ApplicationException class; however in practice this has not been found to add significant value."
Anonymous wrote re: MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte II
on 2-7-2009 2:27
Bom dia,

Não querendo ser demasiado chato, deixo aqui alguns comentários sobre o texto.

"[o] stack [é] uma área da memória que está acessível mais rapidamente pelo código em execução [que o] heap"

Não estou a dizer que isto está incorrecto. Não sei é razão, porque também não percebo muito disto, e gostaria de discutir sobre isso. Talvez se possa dizer que os processadores (alguns) têm em conta o princípio da localidade e coloquem parte do stack em cache. Ou então que a memória do stack está numa página de memória que se garante com uma maior probabilidade de estar presente e não em swap. É alguma coisa por aqui? A um nível superior, pode-se dizer que é mais fácil arranjar um espacinho para no stack que no heap. Mas aqui estamos apenas a falar da criação e não do acesso e, de qualquer das formas, tendo em conta o algoritmo de alocação no heap utilizado, não sei se essa diferença será assim tão significativa.

"Esta operação é, portanto, menos performante que utilizar os métodos Concat, Join ou Format da Classe String ou ainda a Classe StringBuider."

De notar que a concatenação de strings (+, +=) utiliza o Concat, por isso nenhuma delas é mais eficiente que a outra. Já o Join funciona de forma diferente, criando um buffer interno e, por isso, é provavelmente mais eficiente.

"Um array não é mais que uma sequência de apontadores para valores de dados armazenados em memória, ou seja, um conjunto de apontadores de referência para variáveis do mesmo tipo."

É verdade se for um array de um tipo referência. Se for um array de um tipo valor, o array é um sequência de instâncias desse tipo.

"As streams são utilizadas para ler e escrever para o disco e comunicar através de uma rede. Existem vários tipos de streams, nomeadamente:"

Atenção que o StreamReader e o StreamWriter não são streams. São tipos que recebem streams e efectuam leituras e escritas ao nível do carácter e sequência de caracteres (as streams funcionam ao nível do byte). Por outras palavras, servem para inserir e extrair texto das streams de uma forma mais fácil.

"criando classes derivadas de System.ApplicationException."

Inicialmente foi esse o objectivo do ApplicationException. No entanto, verificou-se que não trazia valor acrescentado e a própria Microsoft sugere que as excepções da aplicação derivem directamente de Exception. Tirado dos Best Pratices for Handling Exceptions: http://msdn2.microsoft.com/en-us/library/seyhszts.aspx

"For most applications, derive custom exceptions from the Exception class. It was originally thought that custom exceptions should derive from the ApplicationException class; however in practice this has not been found to add significant value."

Add a Comment

(requerido)  
(opcional)
(requerido)  
Remember Me?
If you can't read this number refresh your screen
Enter the numbers above:  
Powered by Community Server (Commercial Edition), by Telligent Systems