Fala galera, blz?

Este post é uma continuação do anterior. Estamos falando sobre membros de tipos na série Introdução ao .Net Framework. Nessa publicação falarei sobre métodos. Para quem não está acompanhando, segue os links do que já foi falado até agora:

1 - Introdução ao .Net Framework - Parte 1 - Do que é composto, compialçao.

2 - Introdução ao .Net Framework - Parte 2 - CLR, CTS, FCL, CLS.

3 - Introdução ao .Net Framework - Parte 3 - Tipos.

4 - Introdução ao .Net Framework - Parte 4 - Conversão/Casting de tipos.

5 - Introdução ao .Net Framework - Parte 5 - Namespace e Assemblies.

6 - Introdução ao .Net Framework - Parte 6 - Boxing e Unboxing.

7 - Introdução ao .Net Framework - Parte 7/1 - Membros de tipos - Constantes, Campos.

Introdução

No .net toda instrução é executada dentro de um método. A declaração de um método contém obrigatoriamente um modificador de acesso (que pode ser implícito), o nome do método e seu tipo de retorno, sendo que, existem métodos que não retornam nada, porém, isso também deve ser informado explicitamente através da palavra chave  void. Veja abaixo um exemplo de declaração de método:

C#

[sourcecode language="csharp"]
public class Programa
public class Programa
{
public void ExemploMetodo()
{
Console.WriteLine("Alo Mundo");
}
}
[/sourcecode]

No exemplo acima, o método ExemploMetodo() não possui retorno, este está chamando um outro método que escreverá uma string na tela. Outra obrigatoriedade dos métodos é que eles sejam criados dentro de uma classe/tipo ou struct. Agora, veja mais um exemplo de declaração de método:

C#

[sourcecode language="csharp"]
public class Programa
{
public void ExemploMetodo(string Palavra)
{
Console.WriteLine(Palavra);
}
}
[/sourcecode]

Veja que algo mudou na declaração de método do ultimo exemplo. Dentro dos parênteses do método foi declarado um tipo. Quando uma declaração é feita dessa maneira, quer dizer que para chamar esse método é necessário que uma informação seja passada para ele. Se compararmos o primeiro e o segundo exemplo, podemos ver que o segundo é mais flexível se pensarmos que agora podemos definir ou fazer com que o usuário defina o que será escrito na tela pelo método.  Vejamos outro exemplo de declaração de método:

C#

[sourcecode language="csharp"]
public class Programa
{
public decimal ExemploMetodo(decimal Numero1, decimal Numero2)
{
return Numero1 + Numero2;
}
}
[/sourcecode]

Repare que nesse exemplo não estamos usando a palavra chave void, mas sim um tipo, decimal. Isso quer dizer que esse método obrigatoriamente deverá retornar um valor decimal. Outra diferença do exemplo anterior a esse é a quantidade de parâmetros, quando queremos que um método receba mais de um parâmetro, devemos separa-los por vírgula. Para fazer o retorno de informação usa-se a palavra chave return. É importante saber que ao usar um return, tudo que estiver abaixo não será executado e o método será finalizado.

Executando um método

Existe várias formas de executar um método, seria difícil listar todas aqui, aprendemos novas maneiras de acordo com a necessidade. Para utilizar um método que está no mesma classe/struct só precisamos informar sua assinatura seguido de parênteses e caso haja algum parâmetro obrigatório, deve-se informa-lo dentro dos parênteses e se requerido mais de um, esses devem ser separados por virgula. Veja abaixo um exemplo de chamada de método:

C#

[sourcecode language="csharp"]
public void Calculadora()
{
decimal N1 = 0, N2 = 0, Total;
Total = Calculo(N1, N2);
}

public decimal Calculo(decimal Numero1, decimal Numero2)
{
return Numero1 + Numero2;
}
[/sourcecode]

No código acima podemos observar que no método Calculadora() foram declaradas três variáveis do tipo decimal. Logo abaixo da declaração foi feita uma chamada ao método Calculo() que recebe dois parâmetros também do tipo decimal. Repare que não é necessário que os valores passados por parâmetro tenham o mesmo nome de declaração dos parâmetros. O método Calculo() executa uma soma entre os dois parâmetros recebidos e retorna o resultado desse calculo. O retorno desse método será armazenado na variável Total do método Calculadora(). É importante saber que nesse exemplo os valores das variáveis N1 e N2 não serão alterados após o calculo. Isso ocorre por dois motivos: primeiro, não está sendo atribuído nenhum valor a eles na execução do método. Segundo, eles foram passados por parâmetro de tipo valor. Parâmetro de tipo valor? Sim! Parâmetros podem ser por valor ou por referência.

Parâmetros por valor

Quando declaramos os parâmetros que um método receberá, se não informarmos se será um parâmetro por referencia ou valor por padrão, ele será por valor. Veja abaixo um exemplo de chamada de método com parâmetros por valor:

C#

[sourcecode language="csharp"]
public void Inicio()
{
string Mensagem = "Alô";
Console.WriteLine(AloMundo(Mensagem));
Console.WriteLine(Mensagem);
}

public string AloMundo(string Alo)
{
Alo += " Mundo";
return Alo;
}
[/sourcecode]

No exemplo acima estamos aprendendo duas novas formas de executar um método. Quando um método está em uma classe diferente da que estamos fazendo sua execução devemos criar uma nova instância da classe onde este método foi declarado. Quando um método é declarado como static, como no caso do WriteLine, para chama-lo só é necessário usar como prefixo a classe onde foi declarado, como mostrado no ultimo exemplo. Ainda analisando o ultimo código, veja que dentro do local onde deveríamos informar o parâmetro do método WriteLine foi chamado um outro método, AloMundo(). Nesse caso, o compilador entende que primeiro deverá ser executado o método AloMundo() e o retorno desse método será usado como parâmetro pelo WriteLine(). O método AloMundo() recebe por parâmetro um tipo string, como não foi informado explicitamente se é um parâmetro por valor ou por referência o compilador entende que é por valor. Dentro desse método está sendo realizada uma concatenação (entenda concatenação como uma junção de duas ou mais strings) ao valor recebido por parâmetro. O parâmetro recebido foi uma string com o valor “Alô” e dentro do método ela foi concatenada ao valor “Mundo” resultando em uma string com o valor “Alô Mundo”. Ao final da concatenação o valor do parâmetro foi retornado. Repare que método Inicio() foram feitas duas chamadas do método WriteLine propositalmente para mostrar a saída desse método, veja na Figura 1.

imagem1

Figura 1 - Saída do exemplo anterior

A primeira chamada do método WriteLine tem a saída “Alô Mundo”, a segunda chamada tem a saída “Alô”. Nesse exemplo foi mostrado claramente que parâmetros de valor não sofrem alteração na execução de um método. Mas, essa regra tem uma exceção. Se passarmos um objeto por referência a um método, mesmo que essa referência seja por valor, os valores desse objeto que forem alterados dentro do método será mantido, isso ocorre por que um objeto é uma referência a um ponteiro que aponta direto para o local de armazenamento dos valores, lembram?  Agora veremos como funcionam os parâmetros por referência.

Parâmetros por referência

Diferente dos parâmetros por valor, quando criamos um método que recebe parâmetros por referência, toda alteração realizada nos valores desses parâmetros serão refletidos em seus valores informados na chamada do método. Por exemplo, vejamos o exemplo anterior, agora com parâmetros por referência:

C#

[sourcecode language="csharp"]
public void Inicio()
{
string Mensagem = "Alô";
Console.WriteLine(AloMundo(ref Mensagem));
Console.WriteLine(Mensagem);
}

public string AloMundo(ref string Alo)
{
Alo += " Mundo";
return Alo;
}
[/sourcecode]

Repare no parâmetro da declaração de método AloMundo(), ref Mensagem. Quando usamos a palavra chave ref em c# ou ByRef em Vb.Net antes do tipo do parâmetro, estamos indicando que aquele é um parâmetro por referência. Veja na Figura 2 a saída do ultimo exemplo:

figura 2

Figura 2 - Saída do exemplo anterior

Observando a Figura 2 fica claro entender que a variável Mensagem teve seu valor alterado dentro do método AloMundo().

Agora que já nos familiarizamos com métodos, vamos entender um pouco sobre alguns métodos com finalidades específicas e que são muito usados.

Métodos construtores de instância

            Um construtor é um método que é executado automaticamente toda vez que uma nova instância de uma classe ou struct é criada. Em C# o nome de um método construtor corresponde ao nome do tipo onde ele foi declarado. Vale lembrar que em VB.Net o nome dos construtores são definidos como new, sendo assim, é possível que sejam declarados métodos com o mesmo nome do tipo, porém, não é uma boa prática. Uma classe/estrutura pode ter um ou mais construtores. Se uma classe/estrutura for criada sem um método construtor o próprio compilador cria um por default. Mas qual a função desse construtor? É bem simples, métodos construtores permitem que uma classe ou estrutura já seja iniciada com valores padrões. Se o construtor for construído pelo compilador, os valores padrões também serão definidos por ele. Para saber os valores padrões definidos pelo compilador para cada tipo consulte o link http://msdn.microsoft.com/pt-br/library/83fhsxwc.aspx. Veja um exemplo de método construtor:

C#

[sourcecode language="csharp"]
public class ExemploConstrutor
{
public int Id { get; set; }
public string Nome { get; set; }
public string Descricao { get; set;

public ExemploConstrutor()
{
Descricao = "Sem descrição";
}
}
[/sourcecode]

Podemos observar no código acima que na classe ExemploConstrutor foram declaradas três propriedades, falarei sobre propriedades ainda na explicação de membros de tipos, por enquanto você precisa saber que propriedades são informações de um tipo.  Duas das três propriedades criadas não tem valor definido, sendo assim, um valor padrão será atribuído a elas. Já para a propriedade descrição foi atribuído o valor “Sem descrição”, isso quer dizer que, sempre que a classe ExemploContrutor for instanciada essa propriedade será carregada com esse valor.

Podemos também criar um construtor mais dinâmico, permitindo que, parâmetros sejam passados no momento da criação de uma instância do tipo. Veja abaixo um exemplo:

C#

[sourcecode language="csharp"]
public class ExemploConstrutorParametro
{
public int Id { get; set; }
public string Nome { get; set; }
public string Descricao { get; set; }

public ExemploConstrutorParametro(string Descric)
{
Descricao = Descric;
}
}
[/sourcecode]

A diferença do exemplo acima com o anterior é que em um os valores que serão atribuídos as propriedades são fixos e no outro, já existe uma flexibilidade de definir os valores através de parâmetros. Um tipo pode ter mais que um construtor e o que os difere são suas assinaturas, ou seja, a quantidade e o tipo de seus parâmetros. Imagine um tipo com dois construtores, sendo que, um recebe um parâmetro do tipo Int32 e o outro um parâmetro do tipo string, quando uma instância desse tipo for criada passando um parâmetro do tipo Int32 o compilador saberá qual construtor deverá ser executado. Esta forma de criar métodos é chamada de sobrecarga.

Sobre métodos construtores, falamos até agora sobre construtores de tipo-referência, mas, também existem construtores de tipo-valor, porém, não entrarei a fundo sobre construtores de tipos-valor, mas, é importante saber que a maioria dos compiladores que geram códigos para o CLR não permite que sejam criados construtores sem parâmetros para tipo-valor, entretanto, o CLR permite.

Construtores de tipo

Além de construtores de instância o CLR também suporta construtores de tipo. A função dos dois é basicamente a mesma, inicializar um tipo com valores pré-definidos ou dinâmicos. No caso dos construtores de tipos, valores dinâmicos não seriam possíveis, pois, métodos construtores de tipos não podem ser declarados com parâmetros. Veja abaixo um método construtor de tipo:

C#

[sourcecode language="csharp"]
public class ExemploConstrutorTipo
{
static string NomeProgramador;

static ExemploConstrutorTipo()
{
NomeProgramador = "Wennder Santos";
}
}
[/sourcecode]

A assinatura de um construtor de tipo é bem parecida com a de um construtor de instância. A diferença entre construtores de instância e de tipo é que, o primeiro inicializa o estado inicial de uma instância de tipo, já o segundo inicializa o estado inicial do tipo. Por padrão, construtores de tipo são privados, ou seja, não pode ser acessado fora do arquivo onde foi declarado, se tentarmos alterar isso o compilador nos retornará um erro, mesmo se colocarmos explicitamente como private, o mesmo erro será retornado. Diferente de construtores de instância, um construtor de tipo é único. Um construtor de tipo é chamado somente uma vez durante a execução de uma aplicação, dessa forma, toda vez que a propriedade inicializada pelo construtor for acessada terá o mesmo valor, quem define o melhor momento para o método ser executado é o compilador, que por sua vez, insere essa informação nos metadados para que o CLR possa verificar. Observe o código abaixo:

C#

[sourcecode language="csharp"]
public class ExemploConstrutorTipo
{
static string NomeProgramador = "Wennder Santos";
}
[/sourcecode]

Veja que não foi definido nenhum construtor para o tipo criado no código acima. Quando isso acontece o próprio compilador cria implicitamente um construtor de tipo, assim, o código acima ficaria exatamente como o exemplo anterior a esse.

Métodos de sobrecarga

O .Net é totalmente orientado a objeto, sendo assim, possui herança de tipos. Quando um tipo herda outro existe, a possibilidade de alterar um método na classe pai para que quando a classe filha for instanciada o método alterado seja usado, isso se chama sobreposição/sobrecarga de métodos. Veja um exemplo de sobreposição:

C#

[sourcecode language="csharp"]
public class ExemploSobreposicao
{
public virtual void ImprimirTexto(string Nome)
{
Console.WriteLine(Nome);
}
}

public class HerdandoExemploSobreposicao : ExemploSobreposicao
{
public override void ImprimirTexto(int a, int b)
{
Console.WriteLine(a + b);
}
}
[/sourcecode]

No código acima temos a classe ExemploSobreposicao que possui o método ImprimirTexto() que recebe um parâmetro do tipo string e imprime na tela esse parâmetro. Logo abaixo foi criado a classe HerdandoExemploSobreposicao que herda a classe ExemploSobreposicao. Dentro da classe HerdandoExemploSobreposicao foi criado um método ImprimirTexto que sobrepõe o método de mesmo nome da classe ExemploSobreposição. Neste exemplo, quando o programador criar uma instância da classe HerdandoExemploSobreposicao e usar o método ImprimirTexto, ele usará o método definido na classe filha. Se você reparou na diferença de declaração de método em ambas as classes você é esperto! Na classe pai foi usada a palavra chave virtual,  essa palavra chave é querida para dizer que o método poderá ser sobreposto em classes filha. Na declaração do método na classe filha também foi usada uma palavra chave, override. Essa palavra chave é usada quando queremos indicar que um método irá sobrepor outro. Além de sobrepor métodos, é possível também sobrepor operadores.

Métodos de sobrecarga de operadores

Cada compilador define o que cada operador realizará. Alguns compiladores permitem que seja definido como um operador será manipulado dentro de uma instância, essa prática é chamada de sobrecarga de operadores.

Em C#, quando utilizamos o operador “+” em tipos numéricos o compilador gerará código que somará os números. Se o mesmo operador for usado com tipos de texto, o compilador gerará código que realizará a concatenação desses. O CLR não entende os operadores como nós entendemos. O CLR trata a sobrecarga de operadores como simples métodos, cada compilador define se suportará ou não esse tipo de método. Para exemplificar um exemplo de sobrecarga de operadores imagine que em uma aplicação seja necessário somar dois objetos do mesmo tipo diversas vezes. Eu poderia fazer isso conforme o exemplo abaixo:

C#

[sourcecode language="csharp"]
public class SobreCargaOperador
{
public decimal a, b;

public SobreCargaOperador()
{

}
}

public class SomandoObjeto
{
public void Somando()
{
SobreCargaOperador Ex1 = new SobreCargaOperador();
Ex1.a = 1;
Ex1.b = 1;

SobreCargaOperador Ex2 = new SobreCargaOperador();
Ex1.a = 1;
Ex1.b = 1;

SobreCargaOperador Ex3 = new SobreCargaOperador();
Ex3.a = Ex1.a + Ex2.a;
Ex3.b = Ex1.b + Ex2.b;

Console.WriteLine(Ex3.a + ", " + Ex3.b);
}
}
[/sourcecode]

Veja uma outra forma de se fazer a soma feita no exemplo acima, utilizando a sobrecarga de operadores:

C#

[sourcecode language="csharp"]
public class SobreCargaOperador
{
public decimal a, b;

public SobreCargaOperador()
{

}

//Sobrecarregando o operador '+'
public static SobreCargaOperador operator +(SobreCargaOperador Ex1, SobreCargaOperador Ex2)
{
SobreCargaOperador Ex3 = new SobreCargaOperador();
Ex3.a = Ex1.a + Ex2.a;
Ex3.b = Ex2.b + Ex2.b;

return Ex3;
}
}

class Program
{
static void Main(string[] args)
{
SobreCargaOperador i = new SobreCargaOperador(10, 20, 30);
SobreCargaOperador j = new SobreCargaOperador(5, 10, 15);

SobreCargaOperador k = new SobreCargaOperador();
k = i + j;

Console.WriteLine( k.a + "," + k.b + "," + k.c);
}
}
[/sourcecode]

Da forma mostrada acima, deixamos o código mais organizado, principalmente em um caso onde essa operação será realizada diversas vezes. Deve-se tomar cuidado ao usar sobreposição de operador quando estamos criando tipos que serão consumidos por outras linguagens, não são todas as linguagens que fornecem código para o CLR que permitem a sobreposição de operadores. Também existem as linguagens que aceitam, porém, a chamada do método será diferente de acordo com a linguagem que foi criado o método, mais pra frente farei uma publicação explicando essas diferenças pelo menos nas principais linguagens.

É isso aê pessoal! Finalizo esse post aqui.

Até o próximo post!

REFERÊNCIAS

Métodos (Guia de Programação em C#) - http://msdn.microsoft.com/pt-br/library/ms173114.aspx

Programação aplicada com Microsoft .Net Framework – Jeffrey Richter