Sunday, July 01, 2007

MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte IV

Artigo anterior: MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte III

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.
Existem dois tipos de conversões: implícitas e explícitas. Em C# só é possível efectuar conversões implícitas entre tipos se não existir perda de precisão. Isto significa que é possível efectuar conversão implícita em C# apenas e só se o tipo de destino puder aceitar todos os valores possíveis para o tipo de origem. Isto é denominado de conversão generalista (widening conversion):

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:
System.Convert: Converte entre tipos que implementem o Interface System.IConvertible;
(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;
Os métodos TryParse, TryParseExact e TryCast são novos na .NET Framework 2.0. Conversões explícitas falham se o valor de origem exceder o espectro de valores aceites pelo tipo de destino ou se a conversão entre os tipos não estiver definida, por isso este tipo de conversões deve ser incluído entre blocos Try ou utilizando os métodos TryCast ou TryParse para ser possível verificar o valor de retorno.

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
Da mesma forma, se pode exemplificar o processo inverso, ou seja, Unboxing:
object o = 123;
int i = (int)o;
As operações de Boxing e Unboxing implicam um acréscimo de processamento (overhead), por isso devem ser evitados quando se programam tarefas que se repetem de uma forma intensa. Boxing também ocorre quando são chamados métodos virtuais de uma estrutura que herde de System.Object como, por exemplo, o método ToString(). Assim, para evitar procedimentos de Boxing desnecessários deve ter-se as seguintes sugestões em consideraçã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).
A definição de operadores de conversão permitem a atribuição directa de um tipo de valor para um tipo customizado. Deve usar-se a instrução implicit para conversões que não impliquem perda de precisão e a instrução explicit para conversões que estão sujeitas a perda de precisão:
struct TipoA
{
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:
TipoA a;
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

Artigo anterior: MAIS UMA INTRO À .NET FRAMEWORK 2.0 - Parte II


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.
Esta consistência é possível devido à existência dos conceitos de herança e interfaces, de que vou falar de seguida.

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.
Por exemplo, a classe Bitmap herda da classe Image, mas a classe Bitmap implementa funcionalidades que não existem na classe Image. Isto significa que é possível utilizar uma instância da classe Bitmap da mesma maneira que se utilizaria uma instância da classe Image. Contudo, a classe Bitmap disponibiliza métodos adicionais que permitem ao programador realizar outro tipo de tarefas com imagens.
Outro exemplo é a criação de excepções customizadas que herdem da classe System.ApplicationException ou da classe System.Exception:
class DerivedException : System.Exception {
public override string Message {
get { return “Ocorreu uma excepção na Aplicação!”; }
}
}
É possível capturar e lançar excepções utilizando esta classe, visto que ela herda o seu comportamento da sua classe base (System.Exception):
try {
throw new DerivedException();
} catch (DerivedException dex) {
Console.WriteLine(“Origem: {0}, Erro: {1}”, dex.Source, dex.Message);
}
De notar que, para além desta classe suportar o comportamento de lançamento e captura de excepções, também suporta o membro Source (entre outros), uma vez que este foi herdado da classe System.Exception.
Outro benefício da herança é a possibilidade de utilizar classes derivadas (outro termo para classes herdadas) numa perspectiva de intercâmbio. Por exemplo, existem cinco classes derivadas de System.Drawing.Brush: HatchBrush, LinearGradientBrush, PathGradientBrush, SolidBrush e TextureBrush. O método Graphics.DrawRectagle requer um objecto do tipo Brush como um dos seus parâmetros. Mas, neste caso, nunca se passa um objecto do tipo da classe base (Brush), mas sim um objecto do tipo de uma das suas classes derivadas. Uma vez que todos eles herdam da mesma classe base, o método Graphics.DrawRectangle aceitará qualquer um deles. Da mesma forma, se criarmos uma classe derivada de Brush, esta poderia ser utilizada para passar para o método Graphics.DrawRectangle.

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.
É ainda possível criarmos os nossos próprios interfaces. Os interfaces são úteis quando é necessário criar múltiplas classes que têm um comportamento semelhante e podem ser utilizadas numa perspectiva de intercâmbio. Por exemplo, este bloco de código define um interface com três membros:
interface IMessage {
// 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; }
}

Depois, se uma classe implementasse este interface, teríamos algo do género:
class EmailMessage : IMessage {
// 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
}
As classes podem implementar vários interfaces. Assim, é possível uma classe implementar os interfaces IComparable e IDisposable, entre outros.

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.
Um exemplo de classes parciais são as classes que definem o inteface gráfico (GUI) de um Windows Form. Num ficheiro (normalmente com o nome [Nome do Form].Designer.cs) temos a definição e declaração de todos os aspectos relacionados com o desenho e propriedades dos controlos que o Form contém. Noutro ficheiro (para o mesmo exemplo teríamos [Nome do Form].cs) teremos a outra parte da classe parcial, em que se definiria a implementação propriamente dita das funcionalidades do Form (Event Handlers, métodos internos, variáveis de trabalho, etc.).

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.
A .NET Framework 2.0 inclui várias classes genéricas no namespace System.Collections.Generic, incluindo o Dictionary, Queue e SortedList. Estas classes funcionam da mesma forma que os seus equivalentes não genéricos no namespace System.Collections mas oferecem melhor performance e segurança de tipos.
Entre as vantagens de utilizar genéricos, pode-se destacar:
  • 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

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, 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

Introdução

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.

Thursday, August 18, 2005

SI Games finally released the database structure files!!!

Well, after a number of weeks of busting my ass off trying to make some sense out of FM2005 game files, Sports Interactive finally released the database structure files!
Now I can leave this hex hacking stuff behind and concentrate on finding the info I really need to build my Assistant Trainer utility.
My many thanks to the SI team for the courage to post the database structure files. With this attitude they only achieve to gather even more and more fans around the FM2005 scene.
For those interested, here is the SI post. You can download the files there.
Keep posted for more news in a near future.
Cheers.

Wednesday, July 27, 2005

Day Four!!! Back In Bussiness!

Hi All!
Sorry about the last few days, but I got no time at all to work on my utility for FM 2005.
I've left where I already could read Awards, First Names, Second Names and Common Names, Regions and Stadiums based on Stefan de Vogelaere's structures.
I will redesign the initial architecture of the utility as I now have access to much more information than I thought I would have. I'll try to relate all of this on a more efficient way.
Expect some news from me only a few days from now, as I will have lesser time to blog on the next weeks. But keep posted and PLEASE contribute on my blog and on Stefan's also (http://fm2005objects.blogspot.com/).
Cheers.

Friday, July 22, 2005

Day Three! Hex Hacking is HELL ON EARTH!

Hi all!
Ok, let's review day two...Yesterday, I got home with a migrane headache. Why? Because staring at an hex file for at least 6 hours has this weird effect on people... :S
Well, at the end I understood and read the Awards structure with a little (HUGE) help from Stefan. I've ran some tests and managed to get the info from the hex file into my test application. And with Stefan's structure info, the data starts to make sense.
Now you may wonder, "But Pedro, why on earth you need the Awards info to build an assistant manager program?", to which I reply "Wouldn't you like to be able to search your game and find out that striker that scores 100 goals a season, costs 1K AND has won the FIFA World Player award 15 times?". I believe your answer will be..."Yeah, sure! But there's no such player!"...Ok, but imagine he exists. This effort has the purpose of allowing you to search players and sort them by awards received, for instance. And you might check WHICH awards did he receive. So, i'm cool with awards. You people should also be... :D
Moving on to Day Three!
Today I'll try to give some time to this, but it seems I'll have just a few hours to dedicate to this.
Nevertheless, I'll try to understand the Clubs structure. So, stick with me, feed some info if you have it, and don't forget to check out Stefan's blog (http://fm2005objects.blogspot.com/).
Oh, and by the way, did you see the game between my Sporting and Celtic yesterday? Man, I believe this season we're gonna rock seriously!
Well, you guys go and have a nice day. It's party time! Today's friday, ffs!

Thursday, July 21, 2005

Ok. Day two!
I will continue to discover the pre-game file structure (server_db.dat) of Football Manager. Most of it is already done by Stefan, LittleBlue and Allan, so I'm just trying to keep up with them. I'll continue to check out Stefan's Blog since this is the place where we all are converging our efforts's results. Starting with Database Class and Awards.
Enough talk! Let's get to work!

Wednesday, July 20, 2005

Football Manager 2005 Assistant Trainer

One of my all-time favorite games is Championship Manager. It is, as far as I am concerned, the best game I've ever played. And I've played it since the old Championship Manager 93/94 for my (very) old Amiga. One day, I was playing one of my Sporting Clube de Portugal's memorable seasons in CM00/01 and stumbled across an utility for CM called CM Scout (http://www.nygreen.net). From this point forward my life really changed... :D
This utility allowed me to find a 16 year old striker in the botswana premier league which would have the ability to become the best player in the world! And it got better! He's price was only something like 1.000€!!! All I had to do is to buy him and keep him in the B-Squad for a year or two and the promote him to the A-Squad and he scored 50 goals a season! :D:D:D Ok, the player was not from Botswana, but from Argentina, and he costed 100.000€, but nevertheless it was a good deal.
At the same time I realised that when I searched a player in CM he returned far less results matching my criteria than CM Scout! And in CM the argentinian striker (the 50-goals-a-season-one) wasn't there! There I pledged that I would ALWAYS play CM only if I had a copy o CM Scout with me! I really never looked into this kind of tools 'cause I didn't like to use editors. With editors I feel like I'm changing the one thing that distinguishes CM from other games: it is SO REAL! But CM Scout was different! I wasn't adultering the game's database! I was just using the database at it's full power!
But soon I figured out CM Scout was only the starting point for something greater, more powerfull! Since I became a developer I decided I was going to develop some application to use with CM (now with FM) that extended the CM Scout's abilities! And I will!
Today I was thriving through the SI Forums to see if I could find some usefull info and stumbled across a post from a fellow named Stefan de Vogelaere (Bless you Stefan!). He intends to do something similar to my project, or at least, I thought his work might be the basis of my own project. So, I will begin serious development on my project and stay in touch with Stefan's work. Both of us can only benefit from the mutual contribution on eachother's work, right?
So, let's get to work now! ;)

Blog Up and Running!!!

Hi there!
Yup! That's right! I finally have decided to create a blog of my own! Me! The guy who is a developer for 4 years now (mainly in web applications), the guy who attends to almost every Software Development Conference available in Lisbon (Microsoft and Sun mainly), the guy who comments 1 million blog post a day (well, almost) and the guy who surfs the web much better than the real waves finally has a blog of his own!
Ok, cut the crap! What is this blog's purpose!?
The objective is to post the main ideas and difficulties I go through in my life as a developer! And some other interesting stuff aswell! At least interesting stuff to me ;), eheheh...
Well, let's get into it then!
Your help as a reader will be crucial, so PLEASE, contribute! :D:D In portuguese or in english, doesn't matter! Just contribute.
Yours Faithfully!!!