I just read this article about how introducing surprise events improves the gamer's learning process. This is the result of a study led by Erik Van der Spek and his colleagues at the University of Utrecht in the Netherlands who used a mod of the game Half Life 2: Episode Two to create a training scenario in which students undertake the role of a medical first responder. The study was published in the British Journal of Educational Technology.
Deep considerations on self considerative thoughts about different ways of thinking!
Tuesday, July 31, 2012
Surprise your players and they will love your games
I just read this article about how introducing surprise events improves the gamer's learning process. This is the result of a study led by Erik Van der Spek and his colleagues at the University of Utrecht in the Netherlands who used a mod of the game Half Life 2: Episode Two to create a training scenario in which students undertake the role of a medical first responder. The study was published in the British Journal of Educational Technology.
Wednesday, July 25, 2012
Write the game you want to tatoo!
NOTICE: I HAVE MOVED MY BLOG TO MY PERSONAL WEBSITE!
Please check http://www.pmhsilva.com for more details. You can red this blog post here.
I just read Gamasutra's newsletter and I couldn't help noticing one article where Game designer David Jones (Grand Theft Auto, Crackdown, Lemmings) points out something that is quite true when you think about it.
He says that "If I forget about an idea after 4 or 5 months I know it was a good thing we never started it, but if after two years it's still there and you're still excited by it, then that's the time to start thinking about putting it into production."
When you think of it, this is the exact same principle as making a tatoo. I mean, you should only tatoo something that you envision in your head for several years. Then you know that you really would like to have that tatoo'ed in your body permanently. Exactly because it is supposed to stay there forever.
I was already thinking about writing down some of the ideas I have in my head, but now I'm sure that I should start by writing those who have been in my head the longest. On to creative writing we go!
World of Warcraft: Mists of Pandaria to release on September 25th 2012!
Please check http://www.pmhsilva.com for more details. You can red this blog post here.
World of Warcraft: Mists of Pandaria now has an official release date (check this link). It will be released on September 25th 2012. This is my favorite game ever and I already upgraded my version, how about you? (check picture)
Game Design Challenge - Bring it on!
NOTICE: I HAVE MOVED MY BLOG TO MY PERSONAL WEBSITE!
Please check http://www.pmhsilva.com for more details. You can red this blog post here.
I was browsing Gamasutra the other day and I came across an initiative they have called "Game Design Challenge". According to them " GameCareerGuide.com's Game Design Challenge is an exercise in becoming a game developer, asking you to look at games in a new way -- from the perspective of a game creator, producer, marketer, businessperson, and so forth.
Every other Wednesday we'll present you with a challenge about developing video games. You'll have two weeks to brainstorm a brilliant solution (see below for how to submit your answers). After the two week submission period elapses, the best answers and the names of those who submitted them will be posted.".
I thought, why not? I have many Game Design ideas, why not challenge myself into actually putting pen to paper? It's not about the competition, its more about actually starting to write stuff down. The initiative is awesome, if you ask me. Last challenge was to design a game for the recently announced Microsoft Surface (check this) and I was curious to see the outcome. The top 3 ideas were just amazing. I liked the one on 2nd place more than the 1st placed design, but hey, I'm not in the panel of judges! :)
Looking forward to see what is the next challenge and I will enter the competition. Bring it on! :)
Thursday, July 19, 2012
My passion for games... lives on!
Please check http://www.pmhsilva.com for more details. You can red this blog post here.
In early 2007, me and a long time friend also in the IT area found out that we shared an immense passion about Video Games and decided to put to use our already considerable experience in the field of IT to try and create a Games Studio in Lisbon, Portugal.
We had several brilliant ideas and we were almost certain we would change the world of video games with them and make great, fun things. I still do.
My role was to do Product and Project Management, Game Design of the ideas I had in my head to create games and since I was fluent in English and had strong communication skills, I was also appointed to do Business Development, i.e. make sure our games got published.
During the time our Game Studio was able to survive the lack of investment from anyone else but ourselves, I was able to work on some brilliant titles, with fantastic ideas and extremely skilled individuals.
The first game we produced was called Steam, a Match 3/Action/Puzzle full 3D casual game, where you would have to help a cute young girl travel across the land of Sweetopia, fighting “the bad guys” with her train who fired colourful marble balls to unveil the secret of the stolen candies! We successfully managed to get the game published with Oberon Media, Big Fish Games, Boonty and a couple other publishers. The game can still be played today here as it still is being sold.
At the same time as with Starwheels, we had a different team working on a game called Netliga (a Web game). It was a Soccer Management Game and the user had to pick a team of players based on the official players who played in the Portuguese Premier League. The user would then define his team’s starting line-up and the game engine would rate the players depending on their performance in real life. It would then compute the results and determine each user team’s score. In this game, I designed the game concept, gameplay and other game elements, the general layout guidelines and also did some development on the starting line-up UI. I also managed to have it published by Sapo.pt, the largest portuguese web portal, on its game section and at its highest peak we had over 10000 active players registered and playing.
Later on, we created another game called Ignite Factory, an Online Action Game. The storyline was intended to show the day-to-day work of our studio and the characters of the game represented each of the studio members. Each one of us was supposed to do something, from creating the raw materials to transforming them with the goal of producing lighters (our games!). My character was responsible to make sure the bits and pieces got together moving from one production phase to the other (kind of mimicking what I did in real life). The game is still playable here and its one of the things I liked doing the most, despite being a very simple flash game.
The last big project I worked on was Portucale, a Real Time Strategy Web Game similar to the very popular Travian or Tribal Wars. The concept was to have a player impersonate one Knight, servant to the first King of Portugal and help the Kingdom grow, always respecting the real events of the History of Portugal. The game started in 1143 (when Portugal was created) and ended in 1910 (when the Monarchy was abolished and the Republic implemented in the country) and the player would evolve with time, accordingly to Portugal and the world’s history and technological evolutions as well. The storyline was very rich and full of historical events and I tried to always keep a pedagogical approach to every aspect of the game as I wanted to be a way for young people to learn a little bit more about their country’s history while having fun! I was also very careful to make sure the environment of the game evolved as the years passed by and major historical events took place (the Arab Wars, the Sea Conquest, the Napoleon Invasions, The Industrial Revolution, etc.).
Sadly, this game came in a late phase of our game studio’s life and we didn’t get any funding for it so it eventually followed the studio’s demise and had to be stopped.
Before our studios had to close, I had the opportunity to work on the design of a game that never got past the initial concept and prototype phase code-named “Pet Doctor”. It was a game aimed for the casual games biggest audience (women in their mid-30s/40s) and the idea was to have a pet with some kind of condition (never anything too serious) and the player would control a cute female veterinarian that would have to use mouse gestures to heal the pet. My idea was to have a cool subject (save/help animals) with innovative technologies (gesture recognition) as I was already looking at having a mobile/tablet touchable version of this game as well. Unfortunately, the studio had to close and the game never got to be done.
Nevertheless, my unending passion for games continues and I still dream of having a career in the gaming industry. Maybe one of these days... ;)
Sunday, July 01, 2012
A new beginning...
But things in Portugal are becoming increasingly unbearable and many people took the same route I did. Actually, according to official numbers, 180.000 people left the country in 2010, 200.000 in 2011 and apparently, in 2012 we'll reach 250.000 people leaving Portugal in search for a better life elsewhere. A friend of mine told me the other day "This is the most well educated and technically prepared generation in the last 50 years and everyone is leaving the country." So, the question is, why is everyone leaving? That is a good yet very complicated question with political and economic implications and this blog is not about politics or economics, so I'll leave it up to you to think about it.
Moving on, I decided to leave the country and the first question was: where to? I considered several options: Germany, Netherlands, France, Sweden, Denmark, UK and Ireland. You might think, all are countries with good economy and quality of living except maybe Ireland, as the country was also in a delicate situation, just like Portugal, Greece and now Spain. Well, it turned out that from all the mentioned countries, it was the one I chose and I honestly believe it is the best choice. I am an IT professional and Ireland is the place where every single multinational IT company has its European HQ. In Ireland you have Microsoft, Google, Apple, Facebook, IBM, Oracle and many many other IT "giants". Besides that, some of the biggest Gaming companies (games are my unending passion) also are established in Ireland somehow (Activision, Electronic Arts, Riot Games, etc). On top of that, salaries are in par with all the other countries and the cost of living (rent, etc) is a bit cheaper, as well as taxes. Seemed a natural choice.
So, here I am! I've been living and working in Dublin since April 2012 and all I can say is that I totally love the city and its people. Yeah, sure, the weather sucks big time, but hey, the sun doesn't pay bills at the end of the month, right?
Also, since I've been here, I have way more time to do so many things I love doing and didn't had the time to do when I was in Portugal. Worse part is: I miss my son and daughter. A lot. There's no day that goes by that I don't miss them dearly. But hey, that's life.
And my life has just gave me a new beginning.
Thursday, February 24, 2011
Back on track!
Monday, July 09, 2007
MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte V
Este capítulo fala de um tipo de operações mais comum em programação, ou seja, a manipulação de ficheiros e/ou do sistema de ficheiros. As características básicas do sistema de I/O da .NET Framework 2.0 incluem aceder a ficheiros e pastas no sistema de ficheiros, trabalhar com streams de leitura e escrita, a utilização de streams de compressão e utilizar dispositivos de armazenamento.
1.1. Quais são as classes do Sistema de Ficheiros?
Dentro do namespace System.IO existe um conjunto de classes que são utilizados para navegar e manipular ficheiros, pastas e drives. As classes do sistema de ficheiros estão divididas em dois tipos de classes: informativas e utilitárias.
As classes utilitárias disponibilizam um conjunto de métodos estáticos para realizar determinadas operações em objectos do sistema de ficheiros tais como ficheiros, pastas e caminhos do sistema de ficheiros. Estas classes utilitárias incluem as classes File, Directory e Path, por exemplo.
1.2. A classe FileSystemInfo
A classe FileSystemInfo disponibiliza funcionalidades básicas para todas as classes informativas do sistema de ficheiros. As suas propriedades mais importantes são:
· Attributes: Obtém ou define FileAttributes de um ficheiro ou directório;
· CreationTime: Obtém ou define a hora em que um dado ficheiro ou directório foi criado;
· Exists: Verifica se um ficheiro ou directório existe no sistema de ficheiros;
· Extension: Obtém uma string com a extensão de um ficheiro ou directório;
· FullName: Obtém o caminho completo no sistema de ficheiros para um dado ficheiro ou directório;
· LastAccessTime: Obtém ou define a hora do último acesso a um ficheiro ou directório;
· LastWriteTime: Obtém ou define a hora da última escrita num ficheiro ou directório;
· Name: Obtém o nome simples de um ficheiro ou directório. Para um ficheiro, retorna o nome de um ficheiro num directório, para um directório retorna o último nome de directório na hierarquia do directório.
Para além destas propriedades, os métodos mais importantes desta classe são:
· Delete: Elimina um ficheiro ou directório do sistema de ficheiros;
· Refresh: Actualiza a informação da classe, com a informação mais actualizada sobre o sistema de ficheiros.
1.3. A classe FileInfo
A classe FileInfo disponibiliza funcionalidades que permitem aceder e manipular um ficheiro do sistema de ficheiros. As suas propriedades mais importantes são:
· Directory: Obtém o objecto DirectoryInfo que representa o directório em que o ficheiro está armazenado;
· DirectoryName: Obtém o nome do directório em que o ficheiro está armazenado;
· IsReadOnly: Obtém ou define a possibilidade do ficheiro ser alterado e/ou apagado;
· Length: Obtém o comprimento do ficheiro.
Os métodos mais importantes desta classe são:
· AppendText: Cria um novo objecto do tipo StreamWriter que permite a adição de texto ao ficheiro;
· CopyTo: Copia o ficheiro para uma nova localização;
· Create: Cria um novo ficheiro baseado na informação corrente sobre o ficheiro;
· CreateText: Cria um novo objecto do tipo StreamWriter e um novo ficheiro para escrita de texto;
· Decrypt: Decifra um ficheiro que foi cifrado pelo utilizador actual;
· Encrypt: Cifra um ficheiro de forma a que apenas o utilizador actual possa decifrar a informação nele constante;
· MoveTo: Move um ficheiro para uma nova localização;
· Open: Abre um ficheiro com privilégios específicos;
· OpenRead: Abre um ficheiro apenas com permissões de leitura;
· OpenText: Abre um ficheiro e devolve um objecto do tipo StreamReader para permitir a leitura do texto do ficheiro;
· OpenWrite: Abre um ficheiro apenas com permissões de escrita;
· Replace: Substitui um ficheiro com a informação no objecto FileInfo corrente.
if (oMeuFicheiro.Exists())
{
Console.WriteLine(“Nome do Ficheiro : {0}”, oMeuFicheiro.Name);
Console.WriteLine(“Caminho : {0}”, oMeuFicheiro.FullName);
}
oMeuFicheiro.CopyTo(“c:\boot.bak”);
A classe DirectoryInfo disponibiliza funcionalidades que permitem aceder e manipular um directório do sistema de ficheiros. As suas propriedades mais importantes são:
· Parent: Obtém o objecto DirectoryInfo que representa o directório-pai do directório actual;
· Root: Obtém a parte da raíz do caminho do directório numa string.
Os métodos mais importantes desta classe são:
· Create: Cria um novo directório baseado na informação corrente sobre o directório;
· CreateSubdirectory: Cria um novo directório como directório-filho do directório corrente;
· GetDirectories: Obtém um array de objectos DirectoryInfo que representam os subdirectórios do directório corrente;
· GetFiles: Obtém um array de objectos FileInfo que representam todos os ficheiros constantes no directório corrente;
· GetFileSystemInfos: Obtém um array de objectos FileSystemInfo que representam todos os ficheiros e subdirectórios constantes no directório corrente;
· MoveTo: Move um directório para uma nova localização.
Console.WriteLine(“Directório : {0}”, oMeuDirectorio.FullName);
foreach (FileInfo ficheiro in oMeuDirectorio.GetFiles())
{
Console.WriteLine(“Ficheiro : {0}”, ficheiro.Name);
}
A classe DriveInfo disponibiliza funcionalidades básicas que permitem aceder e manipular uma drive do sistema de ficheiros. As suas propriedades mais importantes são:
· AvailableFreeSpace: Obtém o tamanho de espaço disponível na drive. O espaço retornado pode ser diferente do valor retornado pela propriedade TotalFreeSpace, dependendo das quotas de disco;
· DriveFormat: Obtém o formato da drive (FAT32 ou NTFS);
· DriveType: Obtém o tipo de drive na forma da enumeração DriveType;
· IsReady: Obtém o estado da drive, indicando se está pronta a ser utilizada;
· Name: Obtém o nome da drive;
· RootDirectory: Obtém um objecto DirectoryInfo que representa o directório raíz da drive;
· TotalFreeSpace: Obtém o espaço total livre da drive;
· TotalSize: Obtém o tamanho total da drive;
· VolumeLabel: Obtém ou define a etiqueta da drive. Apenas pode ser definido para drives que não são apenas de leitura (read only).
A enumeração DriveType disponibiliza os tipos possíveis de drives que podem ser representados por um objecto DriveInfo. Esta é composta pelos seguintes membros:
· CDRom: Uma drive óptica. Pode ser um CD-Rom um DVD, etc.;
· Fixed: Um disco rígido;
· Network: Uma drive de rede mapeada;
· NoRootDirectory: Uma drive que não possui directório raíz;
· Ram: Uma drive RAM;
· Removable: Uma drive amovível;
· Unknown: Uma drive para a qual não se consegue determinar o tipo.
foreach (DriveInfo drive in drives)
{
Console.WriteLine(“Drive: {0}”, drive.Name);
Console.WriteLine(“Tipo: {0}”, drive.DriveType);
}
A classe Path disponibiliza métodos que permitem aceder e manipular um caminho do sistema de ficheiros. Os métodos mais importantes desta classe são:
· ChangeExtension: Utiliza um caminho existente e retorna um novo caminho com a extensão do nome do ficheiro alterada. De notar que apenas a string do caminho é alterada, a extensão do ficheiro mantém-se;
· Combine: Combina duas strings de caminho compatíveis;
· GetDirectoryName: Retorna o nome do directório do caminho especificado;
· GetExtension: Retorna o nome da extensão de um ficheiro no caminho especificado;
· GetFileName: Retorna o nome de um ficheiro no caminho especificado;
· GetFileNameWithoutExtension: Retorna o nome de um ficheiro sem a extensão, no caminho especificado;
· GetFullPath: Retorna o caminho completo para o caminho especificado. Este método pode ser utilizado para resolver caminhos relativos;
· GetPathRoot: Retorna o nome do directório raíz no caminho especificado;
· GetRandomFileName: Gera um nome de ficheiro aleatoriamente;
· GetTempFileName: Gera um ficheiro temporário no sistema e retorna o caminho completo para esse ficheiro;
· GetTempPath: Retorna o caminho do directório de ficheiros temporários para o utilizador ou sistema correntes;
· HasExtension: Indica se o nome do ficheiro de um determinado caminho possui extensão;
· IsPathRooted: Indica se o caminho especificado inclui um directório raíz.
string oMeuCaminho = @“C:\boot.ini”;
Console.WriteLine(oMeuCaminho);
Console.WriteLine(“Extensão: {0}, Path.GetExtension(oMeuCaminho));
Console.WriteLine(“Alterado: {0}, Path.ChangeExtension(oMeuCaminho, “bak”));
Uma das tarefas mais comuns em programação é monitorizar o sistema de ficheiros para determinar quando acontecem alterações e reagir de acordo com elas. A classe FileSystemWatcher disponibiliza funcionalidades básicas que permitem monitorizar um sistema de ficheiros e verificar alterações. As suas propriedades mais importantes são:
· EnableRaisingEvents: Obtém ou define se o objecto de monitorização deve despoletar eventos. Normalmente, esta propriedade é utilizada para ligar ou desligar a monitorização de ficheiros e/ou directórios;
· Filter: Obtém ou define o filtro de ficheiros que é utilizado para determinar quais as alterações de ficheiros a monitorizar. Um filtro vazio indica todos os ficheiros;
· IncludeSubDirectories: Obtém ou define se a monitorização de um directório deve ou não estender-se aos seus subdirectórios;
· NotifyFilter: Obtém ou define o tipo de alterações a monitorizar. Por defeito, todas as alterações (criação, eliminação, renomeação e modificação) são notificados;
· Path: Obtém ou define o caminho do directório a monitorizar.
· Changed: Ocorre quando um ficheiro ou directório foram alterados no directório monitorizado;
· Created: Ocorre quando um ficheiro ou directório foram criados no directório monitorizado;
· Deleted: Ocorre quando um ficheiro ou directório foram eliminados no directório monitorizado;
· Renamed: Ocorre quando um ficheiro ou directório foram renomeados no directório monitorizado.
FileSystemWatcher bigbrother = new FileSystemWatcher();
bigbrother.Path = @”C:\”;
// Registar os eventos
bigbrother.Created += new FileSystemEventHandler(bigbrother_changed);
bigbrother.Deleted += new FileSystemEventHandler(bigbrother_changed);
// Começa a monitorização
bigbrother.EnableRaisingEvents = true;
// Event Handler
static void bigbrother_changed(object sender, FileSystemEventArgs e)
{
Console.WriteLine(“O directório foi alterado ({0}): {1}”,
e.ChangeType, e.FullPath);
}
Sunday, July 01, 2007
MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte IV
4. Conversões entre tipos
Conversões entre tipos é uma das tarefas mais comuns em programação orientada a objectos. Várias vezes é necessário converter uma variável de um tipo para outro antes de a passar como parâmetro de um método ou para executar determinado cálculo.
int i = 1;
double d = 1.0001;
d = i; // Conversão implícita permitida
Se o espectro de conversão ou precisão do tipo de origem excede o do tipo de destino, a operação denomina-se de conversão de estreitamento (narrowing conversion) e normalmente necessita que a conversão seja explícita. Existem várias formas de realizar uma conversão explícita:
• (tipo) cast: Converte entre tipos que definem operadores de conversão;
• tipo.ToString e tipo.Parse: Converte entre objectos do tipo string e objectos de tipos base. Se a conversão não for possível, a operação resulta numa excepção;
• tipo.TryParse e tipo.TryParseExact: Converte entre um objecto do tipo string e um objecto de um tipo base. Se a conversão não for possível, devolve um valor false;
4.1. O que é Boxing e Unboxing?
Boxing acontece quando se converte um tipo de valor para um tipo por referência e Unboxing é, naturalmente, o processo inverso, ou seja, a conversão de um tipo de referência para um tipo de valor. No exemplo seguinte podemos ver um exemplo de Boxing através da conversão de um Integer (tipo de valor) para um Object (tipo por referência):
int i = 123;
object o = (object)i; // Conversão através da utilização de um type cast
int i = (int)o;
• Implementar versões específicas por tipo (overloads) de métodos que aceitem vários tipos de valor. É uma boa prática criar várias versões do mesmo método com parâmetros diferentes e definitivamente é melhor solução do que implementar apenas uma versão desse método que aceite um parâmetro do tipo Object;
• É preferível utilizar genéricos sempre que possível em vez de utilizar argumentos do tipo Object;
• Quando estamos a criar as nossas próprias estruturas, devemos fazer as nossas próprias versões (override) dos métodos virtuais ToString, Equals e GetHash.
4.2. Como implementar conversão em tipos customizados?
Existem várias formas de definir conversões entre tipos definidos por nós. A técnica a escolher depende do tipo de conversão que pretendemos executar:
• Deve definir-se operadores de conversão para simplificar conversões implícitas e explícitas entre tipos numéricos. De notar que os operadores de conversão são uma novidade na .NET Framework 2.0;
• Deve implementar-se versões customizadas (override) do método ToString para conversões onde o objecto de destino é do tipo string e do método Parse para conversões em que o objecto de origem é do tipo string;
• Deve implementar-se o interface IConvertible para permitir conversões utilizando System.Convert, especialmente quando se implementam conversões específicas de uma cultura (Culture);
• Deve implementar-se uma classe TypeConverter para permitir conversão em Design Time, que será utilizada na janela de propriedades do Visual Studio (para mais informações, visitem http://msdn2.microsoft.com/en-us/library/37899azc.aspx).
{
public int Value;
// Permite conversão implícita de um inteiro
public static implicit operator TipoA(int arg)
{
TipoA res = new TipoA();
res.Value = arg;
return res;
}
// Permite conversão explícita para um inteiro
public static explicit operator int(TipoA arg)
{
return arg.Value;
}
// Permite conversão para String (evita Boxing)
public override string ToString()
{
return this.Value.ToString();
}
}
O exemplo anterior também efectua uma costumização do método virtual ToString par evitar o processo de Boxing. Agora seria possível atribuir inteiros a este tipo costumizado, como se pode ver abaixo:
int i;
// Conversão implícita é aceite
a = 42; // Em vez de se utilizar a.Value = 42
// Conversão explícita
i = (int)a; // Em vez de i = a.Value
Console.WriteLine(“a = {0}, i = {1}”, a.ToString(), i.ToString());
Para se implementar o interface System.IConvertible é necessário especificar isso na definição do tipo costumizado. A implementação deste interface implica definir implementação para 17 métodos, incluindo GetTypeCode, ChangeType e ToType para cada tipo base com o qual se pretenda efectuar operações de conversão. Não é necessário implementar todos os métodos e alguns, como por exemplo ToDateTime, serão provavelmente inválidos. Para métodos inválidos, basta criar uma excepção. Após a implementação do interface IConvertible, pode-se converter a nossa classe utilizando System.Convert:
TipoA a;
bool b;
a = 1;
// Conversão utilizando ToBoolean
b = Convert.ToBoolean(a);
Console.WriteLine(“a = {0}, b = {1}”, a.ToString(), b.ToString());
Tuesday, May 15, 2007
MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte III
3. Classes
A .NET Framework possui milhares de classes e cada uma delas possui diferentes métodos e propriedades. Contudo, a .NET Framework está implementada de uma forma bastante consistente, que permite que diferentes classes implementem os mesmos métodos e/ou propriedades da mesma forma.
3.1. Herança
A herança de classes deve ser utilizada quando se pretende criar novas classes a partir de classes existentes, ou seja, quando se pretende criar uma classe nova que reaproveite funcionalidades de uma classe existente.
public override string Message {
get { return “Ocorreu uma excepção na Aplicação!”; }
}
}
throw new DerivedException();
} catch (DerivedException dex) {
Console.WriteLine(“Origem: {0}, Erro: {1}”, dex.Source, dex.Message);
}
3.2. Interfaces
Os Interfaces, também conhecidos como contratos, definem um conjunto de membros que todas as classes que implementem o interface devem disponibilizar. Por exemplo, o interface IComparable define um método CompareTo, que permite que duas instâncias de uma classe sejam comparadas para verificar semelhança. Assim, todas as classes que implementem este interface, sejam elas classes costumizadas ou classes pertencentes à .NET Framework, devem implementar funcionalidade para o método CompareTo, de forma a serem comparáveis por semelhança.
// Envia a mensagem. Retorna true se bem sucedida, false se falhar.
bool Send();
// A mensagem a enviar.
string Message { get; set; }
// O endereço para onde enviar.
string Address { get; set; }
}
// Temos de implementar os membros do interface
public bool Send() {
.... // Implementação
}
public string Message {
get { ... }
set { ... }
}
public string Address {
get { ... }
set { ... }
}
// Outros métodos
.... // Implementação
}
3.3. Classes Parciais
Classes parciais são um conceito novo na .NET Framework 2.0. Basicamente, as classes parciais permitem que a definição de uma classe (e a sua implementação) seja dividida por diferentes ficheiros de código-fonte. A vantagem desta aproximação está no facto de se esconderem detalhes da definição de uma classe, permitindo que as classes derivadas se concentrem nas partes da implementação mais relevantes para si.
3.4. Genéricos
Genéricos também são um conceito novo da .NET Framework 2.0. Basicamente são uma parte do Sistema de Tipos da .NET Framework que permite a definição de um tipo deixando alguns detalhes por especificar. Em vez de se especificar os tipos dos parâmetros ou classes membros, pode-se permitir que o código que usa o nosso tipo que especifique esses detalhes. Isto permite que o código que consome os tipos especifique o tipo dos membros que usa de acordo com as suas necessidades.
- Menor número de erros de execução de código (runtime) – O compilador não consegue detectar erros de conversão de tipos de e para objectos do tipo Object. De qualquer forma, podem especificar-se restrições para as classes que usam genéricos, permitindo assim ao compilador que detecte tipos incompatíveis.
- Melhor performance – Efectuar conversões de tipos requer encapsulamento (mais informação no ponto seguinte: Conversões entre tipos), que requer tempo de processador e abranda a performance. A utilização de genéricos não precisa de conversão ou encapsulamento, aumentado a performance.
3.4.1. Como criar Tipos Genéricos
Vamos observar a diferença entre duas classes, uma normal (Obj) e outra genérica (Gen):
class Obj {
public Object t;
public Object u;
// Construtor
public Obj(Object _t, Object _u){
t = _t;
u = _u;
}
}
class Gen<T, U> {
public T t;
public U u;
// Construtor
public Gen(T _t, U _u){
t = _t;
u = _u;
}
}
Como se pode observer, a classe Obj tem dois membros do tipo Object. A classe Gen, por sua vez, tem dois membros do tipo T e U. O código que consumir esta classe genérica é que vai determinar os tipos de T e de U. Dependendo da forma como esse código irá usar a classe Gen, T e U podem ser do tipo string, int, uma classe qualquer ou qualquer combinação daí resultante.
Contudo, existe uma limitação importante na criação de uma classe genérica. Esta será válida apenas e só se compilar com todas as possíveis construções do genérico, sejam elas do tipo int, string ou de qualquer outra classe. Basicamente, estamos limitados ao objecto de base Object quando escrevemos código genérico. Estas limitações não se aplicam ao código que consome o genérico, uma vez que este declara os tipos do código genérico.
3.4.2. Como consumir Tipos Genéricos
Quando se consome um tipo genérico, deve-se especificar os tipos de cada genérico utilizado. Pegando no exemplo anterior, poderíamos ter:
// Adicionar duas strings utilizando a classe Obj
Obj oa = new Obj(“Olá,”, “ Mundo!”);
Console.WriteLine((string)oa.t + (string)oa.u);
// Adicionar duas strings utilizando a classe Gen
Gen<string, string> ga = new Gen<string, string>(“Olá,”, “ Mundo!”);
Console.WriteLine(ga.t + ga.u);
// Adicionar um double e um int utilizando a classe Obj
Obj ob = new Obj(10.125, 2005);
Console.WriteLine((double)ob.t + (int)ob.u);
// Adicionar um double e um int utilizando a classe Gen
Gen<double, int> gb = new Gen<double, int>(10.125, 2005);
Console.WriteLine(gb.t + gb.u);
Como se pode facilmente observar do código acima, ambas as classes produzirão exactamente o mesmo resultado, contudo a classe Gen executará mais rapidamente devido ao facto de não necessitar de encapsulamento (boxing e unboxing) a partir da classe Object.
3.4.3. Como utilizar restrições em Tipos Genéricos
Os genéricos seriam extremamente limitados se apenas se pudesse escrever código que compilasse para qualquer classe, uma vez que estaríamos limitados às capacidades da classe base Object. Para superar esta limitação, os genéricos podem utilizar restrições para definir requerimentos nos tipos que o código que consome o genérico usa para esse mesmo genérico. Os genéricos suportam quatro tipos de restrições:
- Interface – Determina que apenas tipos que implementem interfaces possam consumir o genérico.
- Classe Base – Apenas tipos que equivalem ou herdam de uma determinada classe base podem consumir o genérico.
- Construtor – Requer que o código que consome o genérico implemente um construtor sem parâmetros.
- Tipo de Referência ou Valor – Requer que o código que consome o genérico seja um tipo de referência ou de valor.
Para definir uma restrição a um genérico, deve usar-se a cláusula where na definição do genérico:
class LimGen<T>
where T: IComparable {
public T t1;
public T t2;
// Construtor
public LimGen(T _t1, T _t2){
t1 = _t1;
t2 = _t2;
}
public Max(T _t1, T _t2){
if (t2.CompareTo(t1) <>
return t1;
} else {
return t2;
}
}
}
Esta classe irá compilar correctamente. Contudo, se removermos a cláusula where, o compilador irá retornar um erro indicando que o tipo genérico T não contém uma definição para o método CompareTo. Com esta restrição, garante-se que o método CompareTo estará sempre disponível.
3.5. Eventos
Um evento é uma mensagem enviada por um objecto para notificar a ocorrência de uma acção. A acção pode ser causada por intervenção do utilizador ou por qualquer outra situação da lógica de um programa. O objecto que despoleta o evento é denominado por event sender (originador do evento). O objecto que captura o evento e responde ao mesmo é denominado de event receiver (receptor do evento).
Na comunicação de eventos, o objecto que despoletou o evento não sabe que objecto ou método irá capturar o evento. Assim, é necessário um intermediário (um mecanismo do tipo apontador) entre a origem e o destino do evento. A .NET Framework define um tipo especial que proporciona a funcionalidade de um apontador de funções – o Delegate.
3.5.1. Delegates
Um Delegate é uma classe que pode armazenar uma referência para um método. Ao invés das outras classes, um Delegate possui uma assinatura e pode armazenar referências apenas para métodos que possuam a mesma assinatura (que receba o mesmo número de parâmetros e do mesmo tipo).
Enquanto os Delegates podem ser utilizados para outros fins, a sua principal utilidade tem a ver com a interligação entre objectos que geram eventos e métodos que os capturam e tratam. A declaração de um Delegate é suficiente para definir uma classe do tipo Delegate. A declaração define a assinatura e o CLR (Common Language Runtime) trata da implementação. A seguir temos um exemplo de uma declaração de um Delegate:
public delegate void DelegateDeUmEvento(object sender, EventArgs e);
A assinatura normal de um evento define um método que não retorna valor nenhum, recebe um parâmetro do tipo Object (que referencia a instância do objecto que despoletou o evento) e outro parâmetro derivado do tipo EventArgs que armazena os dados do evento.
EventHandler é um tipo de Delegate pré-definido que representa especificamente um método de tratamento de um evento que não gera dados. Se tivermos a necessidade de criar um evento que gere dados, temos que definir o nosso próprio tipo de dados do evento e, ou criar um delegate cujo segundo parâmetro seja do tipo de dados criado por nós, ou então usar a classe delegate com EventHandler pré-definido e substituir o nosso tipo de dados do evento pelo tipo de dados genérico definido no EventHandler de defeito.
3.5.2. Como responder a um evento
Para responder a um evento é necessário efectuar dois passos:
- Criar um método que responda ao evento. Este método tem de possuir a mesma assinatura do delegate do evento:
public void button1_click(object sender, EventArgs e) {
// Implementação do método
} - Adicionar uma referência indicando qual o método que trata o evento
this.button1.Click += new System.EventHandler(this.button1_Click);
Desta forma, quando o evento ocorrer, o método especificado como aquele que trata o evento, será chamado e executará o código que implementa.
3.5.3. Como despoletar um evento
Quando se pretende despoletar um evento são necessários, pelo menos, três passos:
- Criar um delegate:
public delegate void OMeuEventHandler(object sender, EventArgs e); - Declarar um evento:
public event OMeuEventHandler OMeuEvento; - Invocar o delegate dentro de um método quando é necessário despoletar o evento:
OMeuEventHandler handler = OMeuEvento;
EventArgs e = new EventArgs();
if (handler != null) {
// Invoca o delegate
handler(this, e);
}
De notar que, em C#, é necessário que se verifique se o EventHandler é nulo antes de o chamar.
3.6. Atributos
Atributos descrevem um tipo, método ou propriedade de uma forma que estes possam ser programaticamente acedidos através de Reflection (Reflexão). Alguns dos cenários comuns onde se usam atributos incluem:
- Especificar que previlégios de segurança uma classe requer;
- Especificar que previlégios de segurança estão proibidos para reduzir riscos de segurança;
- Declarar capacidades como, por exemplo, suporte para serialização;
- Descrever características da assembly, fornecendo um título, descrição e informação de copyright.
[assembly: AssemblyTitle("Executável da Aplicação.")]
[assembly: AssemblyDescription("Um software que faz coisas.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Easytronic,Lda.")]
[assembly: AssemblyProduct("App v1.2")]
[assembly: AssemblyCopyright("Copyright © 2006 by Easytronic,Lda., Lisboa, Portugal, EU. All rights reserved.")]
[assembly: AssemblyTrademark("App™ is a trademark of Easytronic,Lda., Portugal, EU.")]
Os atributos fazem muito mais do que descrever assemblies para outros programadores, elas podem inclusivamente declarar requisitos ou capacidades. Por exemplo, pode-se definir características relacionadas com Globalização, se está acessível a código COM, entre outros:
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("8a19a392-aa02-4b2d-a784-7da16cfbbee8")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]
Ou ainda, para permitir que uma classe seja serializada, é necessário adicionar o atributo Serializable a essa classe:
[Serializable]
class UmaClasse {
}
Sem este atributo, a classe do exemplo acima não seria serializável. Da mesma forma, o código seguinte utiliza atributos para declarar que necessita de aceder ao ficheiro C:\boot.ini. Devido a este atributo, o código em execução vai lançar uma excepção anterior ao acesso ao ficheiro, se não tiver previlégios suficientes para aceder ao ficheiro:
using System;
using System.Security.Permissions;
[assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read=@”C:\boot.ini”)]
namespace ExemploDeclarativo {
class Classe1 {
[STAThread]
static void Main(string[] args) {
Console.WriteLine(“Olá Mundo!”);
}
}
}
3.7. Reencaminhamento de Tipos (Type Forwarding)
O Reencaminhamento de Tipos é uma nova funcionalidade da .NET Framework 2.0 e não é mais do que um atributo (implementado através de TypeForwardedTo) que nos permite mover um tipo de uma assembly (Assembly A), para outra assembly (Assembly B), mas executado de uma forma que não seja necessário recompilar os clientes que consomem a Assembly A. Após um componente ser finalizado e consumido por aplicações cliente, é possível utilizar o Reencaminhamento de Tipos para mover um tipo de uma assembly para outra e depois reenviar o componente actualizado para as aplicações clientes, que estas continuarão a funcionar sem necessidade de serem recompiladas.
O Reencaminhamento de Tipos funciona apenas para componentes referenciados por aplicações existentes. Quando se recompila a aplicação, têm de existir referências apropriadas para todos os tipos utilizados nessa aplicação.
Para executar Reencaminhamento de tipos, devem seguir-se os seguintes passos:
- Adicionar um atributo TypeForwardedTo à assembly de origem;
- Cortar a definição do tipo da assembly de origem;
- Colar a definição do tipo da assembly de destino;
- Recompilar ambas as assemblies.
O código seguinte demonstra a declaração de um atributo utilizado para mover o TipoA para a biblioteca LibDestino:
using System.Runtime.CompilerServices;
[assembly:TypeForwardedTo(typeof(LibDestino.TipoA))]
Próximo artigo: Conversões Entre Tipos
Wednesday, April 25, 2007
MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte II
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, num artigo 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.
Tuesday, April 10, 2007
MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte I
Este artigo foi desenvolvido a partir do meu próprio estudo para o exame de certificação Microsoft Technology Specialist e trata-se, sobretudo, de expor pelas minhas palavras aquilo que assimilei durante este processo.
Tem também referências a trabalhos de outros autores que eu acho que acrescentam valor ao conteúdo aqui exposto, sendo que este artigo é essencialmente baseado no MCTS Self-Paced Training Kit para o exame 70-536 – Microsoft .NET Framework 2.0 —Application Development Foundation.
Um outro objectivo deste artigo é também ajudar todos os que tiverem interesse em efectuar o referido exame mas, por qualquer motivo, tenham decidido não adquirir o livro que está na base deste artigo.
Convenções
Texto em Itálico – Conteúdo em língua estrangeira
Texto em Courier New (10pt) – Conteúdo que representa pedaços de código-fonte
Capítulo 1 - Aspectos Fundamentais da .NET Framework 2.0
A .NET Framework 2.0, resumidamente, é uma plataforma de desenvolvimento de aplicações criada pela Microsoft com um foco muito grande no desenvolvimento de aplicações de um modo rápido (RAD) e com o objectivo de proporcionar uma plataforma independente de linguagem e de plataforma.
Neste capítulo irei introduzir alguns dos aspectos mais básicos da .NET Framework , como Tipos, Atributos e Encapsulação, focando também algumas novidades da .NET Framework 2.0 relativamente à 1.1 e 1.0, como Genéricos, entre outros.
Eu assumo que quem está a ler este artigo tem pelo menos 2 anos de experiência de programação em .NET, independentemente da plataforma.
Todos os exemplos de código neste artigo serão em C#, excepto quando for necessário referir alguma especificidade da Framework que seja diferente em VB.
1. Tipos de Valor
Um dos erros mais comuns que a maioria dos programadores cometem quando aprendem (e programam com) a .NET Framework tem a ver com a confusão que se faz, por um lado, entre Tipos de Valor e Tipos de Referência e, por outro, com a passagem de valores por Referência ou por Valor.
Tipos de Valor são os tipos mais simples que existem na .NET Framework e incluem todos os tipos de dados numéricos, booleanos, Char e Date, estruturas (mesmo que os seus membros sejam Tipos por Referência) e enumerados (uma vez que os seus tipos intrínsecos são sempre Byte, Short, Integer e/ou Long).
1.1. Tipos de Valor Nativos
Estes tipos são os tipos base disponibilizados pela .NET Framework e são a base de construção de outros tipos. Derivam da Classe System.ValueType que, por sua vez, deriva de System.Object.
A principal característica destes tipos tem a ver com o facto de o seu valor estar armazenado na Stack em vez do Heap, ou seja, estão acessíveis mais rapidamente pelo código em excução.
Existem mais de 300 tipos de valor na .NET Framework, mas de entre os mais comuns destacam-se os seguintes:
· Byte – 1 Byte. Armazena valores de bytes entre 0 e 255, sem sinal.
· Sbyte - 1 Byte. Armazena valores de bytes entre -128 e 127.
· Int16 (ou apenas short) – 2 Bytes. Armazena valores inteiros entre – 32768 e 32767.
· Int32 (ou apenas int) -4 Bytes. Armazena valores inteiros entre – 2147483648 e 2147483647.
· Uint32 (uint em C#) - 4 Bytes. Armazena valores inteiros entre 0 e 4294967295, sem sinal.
· Int64 (ou apenas long) -8 Bytes. Armazena valores inteiros entre – 9223372036854776808 e 9223372036854776807.
· Single (float em C#) - 4 Bytes. Armazena valores de vírgula flutuante entre – 3.402823E+38 e 3.402823E+38.
· Double - 8 Bytes. Armazena valores de vírgula flutuante entre – 1.79769313486232E+308 e 1.79769313486232E+308.
· Decimal - 16 Bytes. Armazena valores de vírgula flutuante entre – 79228162514264337593543950335 e 79228162514264337593543950335.
· Char - 2 Bytes. Armazena um único caracter Unicode.
· Boolean (ou apenas bool) - 4 Bytes. Armazena valores verdadeiro/falso.
· DateTime (ou apenas date) - 8 Bytes. Armazena momentos no tempo entre 1/1/0001 e 31/12/9999.
Em tempo de execução, o desempenho dos tipos Int32 (int e uint) são optimizados, por isso é aconselhado o seu uso para contadores e outras variáveis que são utilizadas frequentemente. No caso de operações que necessitem de Tipos de vírgula flutuante, é recomendado o uso de Double, uma vez que o seu desempenho é optimizado ao nível do Hardware.
1.2. Decalaração de Tipos de Valor
Para usar um Tipo de Valor é necessário declarar uma variável do tipo desejado. Estes Tipos possuem um construtor implícito, ou seja, ao declarar uma variável deste tipo estamos a instanciá-la imediatamente. Contudo, é recomendado que este tipo de variáveis seja inicializada logo na sua declaração:
int x = 0;
Uma das funcionalidades novas na .NET Framework 2.0 são os Tipos de Valor Nulos, ou seja, agora é possível criar variáveis de Tipos de Valor que aceitam valores nulos (por exemplo booleanos nulos). Para declarar variáveis de Tipos de Valor que aceitem valores nulos deve usar-se a seguinte sintaxe:
Nullable <int>x = null;
Ou ainda:
int? x = null;
Desta forma evitamos o (mais que) comum erro “Cannot convert null to 'int' because it is a value type”. Ao declararmos uma variável como Nullable torna disponíveis os membros HasValue e Value, que tornam possível determinar se esta variável tem um valor nulo e, no caso de não ter, ler o seu valor.
if (x.HasValue) // Boa, o valor de x não é nulo!
Console.WriteLine(“O valor de x é {0}”), x.Value);
else // Ooops, o valor de x é nulo!
Console.WriteLine(“x é nulo!”);
1.3. Tipos de Valor Definidos pelo Utilizador (Estruturas)
Estruturas (structs) são Tipos de Valor definidos pelo programador, sendo um composto de outros Tipos de Valor que facilitam a utilização de dados inter-relacionados. Tal como os outros Tipos de Valor, são armazenados na Stack, o que os torna bastante eficientes (às vezes mais eficientes que classes). Para se definir estruturas deve fazer-se algo semelhante ao seguinte:
struct SerHumano
{
public float altura;
public float peso;
public short idade;
public SerHumano (float _altura, float _peso, short _idade)
{
altura = _altura;
peso = _peso;
idade = _idade;
}
public override string ToString()
{
return “Este Ser Humano mede “ + altura + " m, pesa " + peso + " kg e tem " + idade + “ anos.”;
}
}
De notar ainda um pormenor. As estruturas não podem ter um Construtor de defeito (sem parâmetros). O motivo tem a ver com o facto de, para Tipos de Valor, o compilador não gera um construtor de defeito nem uma chamada ao construtor de defeito na instanciação. Ou seja, mesmo que definissemos um construtor de defeito, ele não seria chamado. Para evitar isso, o compilador de C# não permite a sua definição.
Contudo, se utilizarmos o código abaixo, não existe uma chamada a um construtor sem parâmetros. Mas o compilador também não rejeita esta instrução. O que acontece é que os campos da Estrutura são todos inicializados a nulo ou zero, através da utilização do Opcode .InitObj do IL (Intermediate Language).
SerHumano k = new SerHumano();
As estruturas devem possuir as seguintes características:
· Representar logicamente um valor único
· Ter um tamanho de instância inferior a 16 Bytes
· Não irão ser alteradas após a sua criação
· Não irão ser convertidas para um Tipo de Referência
1.4. Enumerados
Enumerados (Enums) são tipos especiais de Tipos de Valor. São símbolos relacionados com valores fixos e imutáveis. Ou seja, pode dizer-se que se tratam de constantes com nomes mais facilmente acessíveis.
O seu objectivo é simplificar a programação através da atribuição de nomes a valores fixos de uma lista de possíveis valores. Assim, em vez de se referenciar o seu valor, referencia-se o seu nome, tornando o código mais legível e bem estruturado.
public enum Titulos : int { Sr, Sra, Dr, Dra, Prof };
...
Titulos t = Titulos.Prof;
string name = “José Pedras”;
Console.WriteLine(“{0}. {1}”, t, name);
De seguida (no próximo artigo) irei falar-vos de Tipos por Referência.