.NET

Afinal: o que é de fato o LINQ?

Certamente, uma das features mais interessantes e legais que o .NET e seu ecossistema oferece é o LINQ. LINQ é um acrônimo para Language Integrated Query, ou Consulta Integrada à Linguagem . Trata-se de um “framework” dentro do .NET destinado a auxiliar os desenvolvedores a escrever expressões de consulta diretamente em C# de maneira agnóstica.

Mas o que exatamente é o LINQ?

Para entender melhor: pense nas seguintes hipóteses dentro de um software:

  • Se o software em questão precisar realizar consultas em um banco de dados relacional, provavelmente será necessário escrever consultas SQL;
  • Se o software em questão precisar realizar consultas em um arquivo XML, provavelmente será necessário utilizar expressões XPath e/ou XQuery;
  • Se o software em questão precisar realizar consultas em coleções de objetos, provavelmente será necessário utilizar blocos de código com a linguagem na qual o software está sendo desenvolvido.

Essa complexidade é adicionada pelo fato de estarmos tratando de fontes de dados heterogêneas de dados, cada qual com suas linguagens de manipulação específicas. Ou seja: se seu software se conecta a uma base de dados relacional e manipula objetos, você provavelmente precisará desenvolver e dar manutenção em pelo menos três linguagens diferentes: SQL, XPath (ou XQuery) e a linguagem utilizada para desenvolver a aplicação em si.
O LINQ tem como objetivo justamente remover essa complexidade, pois ele abstrai a complexidade envolvida na utilização de diferentes linguagens de consulta, como SQL, xPath e xQuery. Essa abstração é feita em cima de uma API de alto nível compatível com as linguagens integrantes do .NET Framework. Ou seja: você consegue consultar uma base de dados relacional, um arquivo XML uma coleção de objetos através de uma API unificada, invocada através de uma linguagem integrante do .NET Framework. Trazendo para um exemplo mais palpável: você consegue unicamente com código C# fazer consultas a conjuntos de objetos, bases de dados relacionais e arquivos XML, sendo o LINQ o encarregado de fazer a devida “tradução” para cada uma das fontes a serem consultadas.

Providers LINQ e árvores de expressão (expression trees)

O LINQ consegue trabalhar de maneira agnóstica (ou seja, a mesma API do LINQ consegue trabalhar em cima de várias fontes de dados diferentes) principalmente por causa de dois pilares: os providers e as árvores de expressão – ou expression trees. Para entendermos cada um deles, vamos a partir deste ponto adotar o C# como a linguagem de referência.

De maneira geral e simplificada, uma expression tree é um trecho de código organizado como uma árvore onde cada nó pode corresponder a um valor, uma chamada de método ou até mesmo um operador aritmético ou lógico. No .NET, as árvores de expressão são definidas pela classe Expression, advinda do namespace System.Linq.Expressions.
Vamos montar uma árvore de expressão simples: uma árvore que retorna true caso receba um número menor que 5 ou retorna false no caso contrário. Para isso, precisamos criar um delegate que seja capaz de realizar essa análise… No .NET, existem dois tipos de delegate especializados e já disponibilizados pela API do LINQ:

  • Func: é um delegate que indica que um valor vai ser retornado. Delegates do tipo Func podem não receber parâmetros, mas sempre tem que prover algum retorno;
  • Action: é um delegate que indica que nenhum valor será retornado. Delegates do tipo Action podem não receber parâmetros e nunca irão retornar um valor.

Se verificarmos a situação acima, veremos que um delegate do tipo Func com um parâmetro de entrada do tipo int e uma saída do tipo boolean é o mais adequado. Sendo assim, utilizando expressões-lambda, podemos escrever esta definição:

Func<int, bool> expr = num => num < 5;

Para que este delegate se torne uma árvore de expressão de verdade, é necessário que ele seja envolvido pela classe Expression:

Expression<Func<int, bool>> expr = num => num < 5;

Agora, temos uma árvore de expressão completa com três ramificações:

  • O argumento do tipo int de entrada;
  • O operador de comparação >, revelando que se trata de uma árvore de expressão binária;
  • O 5, um valor constante que deve ser utilizado para comparação.

Qualquer expressão LINQ é decomposta nestas árvores de expressão. Neste momento, entra em cena os providers LINQ.
Os providers LINQ são utilizados basicamente para fazer a tradução das árvores de expressão para cada uma das fontes de dados na qual a expressão LINQ está conectada. Os principais providers LINQ são:

  • LINQ to objects: utilizado quando a fonte de consulta é um conjunto de objetos;
  • LINQ to XML: utilizado quando a fonte de consulta é um XML;
  • LINQ to SQL: utilizado quando a fonte de consulta é um banco de dados relacional.

As principais estruturas dos providers LINQ são as interfaces IQueryProvidere IQueryable. Todos os providers LINQ e suas variações são obrigados a implementar estas interfaces.
Através das implementações dos providers LINQ, as expressões de consulta podem ser traduzidas para as suas respectivas fontes de dados… Por exemplo: vamos considerar a árvore de expressão anterior (Expression expr = num => num < 5) com o número 4 como entrada.

Se estivéssemos falando de LINQ to Objects, cada nó que faz parte da expressão seria traduzido para o código C# correspondente. Teríamos o resultado abaixo:

Se estivéssemos falando de LINQ to SQL, cada nó que faz parte da mesma expressão seria traduzido para o código SQL correspondente. Teríamos o resultado abaixo:

Já se estivéssemos falando de LINQ to XML, cada nó que faz parte da mesma expressão seria traduzido para o código xPath/xQuery correspondente. Teríamos o resultado abaixo:

Veja que o LINQ, de acordo com a fonte de dados que está sendo analisada, utiliza o provider para realizar uma “tradução” da API de alto nível em C# para o código correspondente ao tipo da fonte de dados que está sendo utilizada. E esse processo torna a API completamente independente da fonte de dados a qual esta se conecta: quem fica responsável por dizer ao LINQ como cada nó da árvore de expressão tem que ser traduzido é o provider.

Existem muito mais coisas do LINQ a serem exploradas!

O LINQ é de fato uma ferramenta muito poderosa quando utilizada corretamente (especialmente quando estamos falando do LINQ to SQL). Este primeiro post na verdade tem a intenção de mostrar os princípios básicos e o funcionamento interno do LINQ. Nos próximos posts, iremos começar a analisar alguns pontos interessantes para quem utiliza o LINQ no dia-a-dia.

Como começar com C# (C Sharp)?

Atualmente são tantas as opções de linguagens de programação à nossa disposição que ficamos perdidos em qual estudar. Para lhe ajudar nessa escolha, vamos apresentar a linguagem C#.

O C# é uma linguagem de programação muito popular, sendo uma excelente escolha devido a sua baixa curva de aprendizado e simplicidade (mas sem deixar de ser uma linguagem poderosa). Além disso, ela é a linguagem principal do .NET Framework, o framework para desenvolvimento da Microsoft.

Se você não tem nenhum conhecimento sobre programação, não tem problema: o C# é ótimo para você começar seus estudos, pois aprendê-lo é muito mais fácil do que parece. Caso você já tenha uma bagagem em alguma linguagem (como por exemplo Java ou C++), você não encontrará problemas em se ambientar com ele.

O que é C#?

O C# é uma linguagem de programação multiparadigma criada pela Microsoft, sendo a principal da plataforma .NET. Por ser uma linguagem que suporta, entre outros paradigmas, a orientação à objetos, ela suporta conceitos comuns como encapsulamento, herança e polimorfismo. Trata-se também de uma linguagem fortemente tipada e case-sensitive, ou seja, faz diferenciação entre letras minúsculas e maiúsculas.

O C# veio para facilitar o processo de desenvolvimento, tendo inúmeros recursos que proporcionam uma grande produtividade para os desenvolvedores que a utilizam.

O que pode ser feito?

O C# é uma linguagem multiplataforma. Sendo assim, você pode utilizá-la para desenvolver para plataformas web, dispositivos móveis e aplicações desktop. Com a praticidade dessa linguagem, você pode, de forma relativamente fácil, desenvolver desde projetos mais simples até projetos complexos e multiplataforma.

Sintaxe do C#(C Sharp)

A sintaxe do C# é simples. Veja o tradicional “Hello World”:

public class Exemplo
{
    public static void Main()
      {
        System.Console.WriteLine("Hello World!");
      }
 }

Utilizar estruturas de tomada de decisão com C# é muito fácil. Veja o tradicional “if/else”.

int idade = 18;
if ( idade >= 18 )
{
    Console.WriteLine( "Você é maior de idade“ );
}
else
{
    Console.WriteLine( "Você é menor de idade" );
}

Você também pode utilizar estruturas de repetição, como a estrutura “for”, sem problemas… Veja o código abaixo, onde fazemos uma verificação e imprimimos somente os números que forem menores ou iguais a 5:

class Exemplo
{
    static void Main() 
    {
        for (int i = 1; i <= 5; i++)
        {
            Console.WriteLine(i);
        }
    }
}

Mercado de trabalho

Você pode escolher uma linguagem de programação que mais te agrade, mas também é importante observar como anda o mercado de trabalho. O C# é uma das linguagens mais utilizadas no mundo e o mercado está em alta para essa linguagem.

Segundo o site TIOBE (Programming Community Index Definition), o C# é uma das 5 linguagens mais utilizadas no mundo! Além disso, grandes empresas utilizam o C# em seus produtos, empresas como Microsoft (em uma infinidade de soluções: desde soluções Web até soluções mobile multiplataforma através do Xamarin), Amazon, StackOverflow (toda a stack web do StackOverflow é construída em cima de C# e .NET) e nós aqui no TreinaWeb (algumas de nossas aplicações foram escritas com C# e .NET Framework Full e .NET Core). Você pode até mesmo desenvolver jogos com C# através da Unity.

Então, se você estudá-la e se especializar, certamente não ficará fora do mercado. =D

Como aprender C#?

Aqui no TreinaWeb temos uma trilha voltada para C#. O curso de “C# Básico” é ótimo para você dar os primeiros passos nessa linguagem de fácil compreensão e com recursos poderosos. No curso básico, além de você aprender todos os conceitos básicos dessa linguagem, você acompanhará a construção de um projeto com todo o seu passo a passo.

Conheça nossa metodologia

Nosso ambiente de aprendizagem é 100% online. Nossa metodologia de ensino é baseada em vídeo aulas, apostilas, exercícios aula-a-aula e exemplos completos. Você estuda no seu tempo e ao final pode baixar o seu certificado digital ou solicitar o certificado impresso.

Na parte escrita, fornecemos um material totalmente atualizado e completo para que você possa usufruir do conteúdo, utilizando-se de textos e imagens. Já as vídeo-aulas são bem explicativas e práticas, abordando todo o conteúdo passo-a-passo, para que você entenda tudo da melhor maneira possível.

Além disso, você encontrará exercícios para poder fixar, praticar e aplicar todo o conteúdo aprendido. São exercícios que são propostos em cada aula do curso. Eles podem variar entre alguns formatos, como por exemplo questões de escolha única, questões de múltipla escolha e complemento de trechos de código.

Se ainda assim você tiver alguma dúvida, você poderá pedir ajuda aos nossos professores através de tickets de atendimento. Funciona como um canal direto entre você e o professor, onde você poderá realizar perguntas que serão respondidas muito brevemente!

Além disso, ainda pelo sistema suporte, você também poderá consultar todas as perguntas que já foram respondidas a outros alunos! =)

Quais conhecimentos preciso ter para iniciar este curso?

O curso é recomendado para você iniciar seus estudos na linguagem C#, no entanto, é interessante saber as bases necessárias para aprender qualquer linguagem de programação: “Lógica de Programação” e “Lógica de Programação Orientada a Objetos”.

Também ministramos esses cursos:

Gostou do que viu?

Se entusiasmou com a linguagem? Gostaria de conhecê-la com mais detalhes? Sim, você pode dar uma espiadinha. Acesse o link do curso de C# Básico e veja algumas aulas de exemplo. =D

Bons estudos!

Aplicações self-contained com o .NET Core

Algumas aplicações necessitam que as plataformas utilizadas sejam instaladas nas máquinas hosts. Exemplos disso são o Java Virtual Machine e o .NET Framework. Se você já programou para estas plataformas, sabe que a aplicação pode requerer que o .NET ou JVM estejam instalados para que ela rode.

Esse comportamento pode ser contornado criando-se uma aplicação self-contained. Aplicações self-contained, são as que já contêm o ambiente necessário para que ela seja executada.

Neste artigo veremos como criar uma aplicação self-contained com o .NET Core.

Criando a aplicação

Para exemplificar uma aplicação self-contained, primeiro temos que criar uma aplicação de exemplo. Ela poderia ser qualquer um dos tipos de aplicações suportados pelo .NET Core, mas para este artigo vou criar uma aplicação MVC:

dotnet new mvc -n App

Na aplicação criada é importante aplicar o restore:

dotnet restore

Compilar:

dotnet build

E executar para ver se está tudo certo:

dotnet run

Com a aplicação funcionando, podemos criar uma versão self-contained dela.

Criando uma aplicação self-contained

Para se definir que uma aplicação é self-contained é necessário fazer apenas uma pequena modificação nela. No arquivo .csproj, é necessário adicionar no elemento <PropertyGroup> a plataforma alvo. Por exemplo:

<PropertyGroup>
  <TargetFramework>netcoreapp1.1</TargetFramework>
  <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
</PropertyGroup>

Acima é definido que a plataforma alvo da aplicação é o ambiente Windows 10 de 64bits. Após isso, é necessário aplicar o restore na aplicação:

dotnet restore

E informar esta plataforma na hora da publicação dela:

dotnet publish -c release -r win10-x64

Com isso, será gerado um arquivo .exe:

Que ao ser executado, irá iniciar a aplicação:

Caso queria definir outros ambientes para a aplicação, eles podem ser informados à tag <RuntimeIdentifiers> separados por ponto e vírgula:

<PropertyGroup>
  <TargetFramework>netcoreapp1.1</TargetFramework>
  <RuntimeIdentifiers>win10-x64;osx.10.12-x64;ubuntu.17.04-x64</RuntimeIdentifiers>
</PropertyGroup>

E depois de aplicar o restore:

dotnet restore

Basta informar para qual plataforma a aplicação deve ser gerada:

Mac OS X:

dotnet publish -c release -r osx.10.12-x64

Ubuntu:

dotnet publish -c release -r ubuntu.17.04-x644

Só é importante ficar atento se a plataforma alvo é suportada pelo .NET Core, caso contrário não vai funcionar.

Recursos de linha de comando para o Entity Framework Core

É inegável o quão poderoso é o Visual Studio. Ele possui vários recursos que facilitam (e muito) o desenvolvimento. Mas, com o advento do .NET Core, e a possibilidade de criar a aplicação em qualquer plataforma, alguns desses recursos fazem falta em algumas delas.

Um desses recursos é o “Package Manager Console”, que além de permitir adicionar packages NuGet, também permite executar comandos para certos pacotes, como habilitar as migrations do Entity Framework.

Caso deseje desenvolver a sua aplicação em uma plataforma diferente do Windows (ou não queira utilizar o Visual Studio), o “Package Manager Console” deve ser substituído por comandos do .NET CLI, e neste artigo conheceremos os comandos do Entity Framework Core

Organizando a casa

Os comandos do .NET CLI só estão disponíveis no SDK do .NET Core, então para executar os comandos que apresentarei a seguir, é necessário instalar este SDK no seu computador.

Além disso, os comandos só estão disponíveis para projetos definidos para uma das versões abaixo do .NET:

  • .NET Framework 4.5.1 ou superior (“net451”, “net452”, “net46”, etc.)
  • .NET Core App 1.0 ou superior (“netcoreapp1.0”, “netcoreapp1.1”, etc.)

Preparando o palco

Com o ambiente organizado, você já poderá criar uma aplicação por linha de comando utilizando o .NET Core:

dotnet new console -o ExemploEntityCore

A aplicação criada com o comando acima, é uma aplicação de console simples. Ao acessar a pasta dela, é possível adicionar um pacote do NuGET pelo terminal:

dotnet add package Microsoft.EntityFrameworkCore.Design

O pacote Microsoft.EntityFrameworkCore.Design é quem adiciona as ferramentas de linha de comando para o Entity Framework Core. Assim, para elas funcionarem, é necessário executar o comando restore:

dotnet restore

Colocando a mão na massa

Adicione uma entidade na aplicação, como a abaixo:

public class Cliente {
    public int Id { get; set; }
    public string Nome { get; set; }
    public int Idade { get; set; }
}

Assim, é possível criar o DBContext com o comando abaixo:

dotnet ef dbcontext scaffold "Data Source=clientes.db" Microsoft.EntityFrameworkCore.SQLite -c "ClientesDbContext"

Ele irá criar a classe ClientesDbContext, que herdará DBContext, além de já configurar a string de conexão "Data Source=clientes.db", para o provider Microsoft.EntityFrameworkCore.SQLite.

Observação: Os pacotes Microsoft.EntityFrameworkCore.SQLite e Microsoft.EntityFrameworkCore.SQLite.Design deve ser adicionados na aplicação.

Se o comando “dotnet-ef” apresentar algum problema, verifique se foi adicionada a referência abaixo. Se não, adicione-a manualmente:

<ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0 " />
</ItemGroup>

Com o DbContext criado, adicione nele a entidade:

public DbSet<Cliente> Clientes { get; set; }

Que será possível adicionar migrations na aplicação:

dotnet ef migrations add CriaBase

Caso não tenha gostado do resultado, e queira desfazer a ação, pode ser utilizado comando:

dotnet ef migrations remove

Ele sempre irá remover a última migration criada.

Com uma migration definida, é possível executá-la com o comando:

dotnet ef database update

Como a nossa migration inicial cria a base de dados, ele pode ser desfeito com o comando abaixo:

dotnet ef database drop

Que exclui a base de dados.

Caso a entidade seja alterada:

public class Cliente {
    public int Id { get; set; }
    public string Nome { get; set; }
    public int Idade { get; set; }
    public char Sexo { get; set; }
}

É possível adicionar uma nova migration:

dotnet ef migrations add AddColumnSexo

E atualizar novamente a base de dados:

dotnet ef database update

Conclusão

Como foi possível notar, o CLI do Entity Core é muito similar ao realizado no “Package Manager Console”. Então, caso você já esteja acostumado a utilizá-lo no Visual Studio, não terá dificuldade para fazer uso dele pelo terminal. Mas em caso de dúvida, você pode informar o parâmetro --help para obter mais informações sobre cada comando.

Pattern Matching no C# 7.0

O operador is existe desde a primeira versão do C# e ele sempre foi utilizado para verificar o tipo de um objeto em tempo de execução.

Neste tipo de verificação é muito comum o uso de operações “cast“. Por exemplo, vamos supor uma classe base, com duas classes filhas:

public class Forma() {}

public class Triangulo : Forma{
    public int Largura { get; set; }
    public int Altura { get; set; }
    public int Base { get; se; }

    public Triangulo(int largura, int altura, int base) {
        this.largura = largura;
        this.altura = altura;
        this.base = base;
    }

    public void Perimetro (){
        Console.WriteLine("O perímetro do triângulo é {0}", largura + altura + base);
    }
}

public class Retangulo : Forma {
    public int Largura { get; set; }
    public int Altura { get; set; }

    public Retangulo(int largura, int altura) {
        this.largura = largura;
        this.altura = altura;
    }

    public void Area(){
        Console.WriteLine("A área do retângulo é {0}", largura * altura);
    }
}

Caso houvesse uma array com essas classes, para chamar cada método delas, seria necessário realizar uma conversão:

public class Program {
    public static void Main(){
        Forma[] formas = { new Triangulo(10, 12, 10), new Retangulo(7, 4), new Triangulo(17, 15, 16), new Retangulo(25, 12) };

        foreach (var item in formas)
        {
            if (item is Triangulo)
                ((Triangulo)item).Perimetro();
            if (item is Retangulo)
                ((Retangulo)item).Area();
        }
    }
}

Repare que ao descobrir o tipo da variável, ainda é necessário realizar o cast:

if (item is Triangulo)
    ((Triangulo)item).Perimetro();

Para evitar este cast, no C# 7.0 foi introduzido o conceito de Pattern Matching:

Pattern Matching

O Pattern Matching adiciona mais poder ao operador is e a cláusula switch.

Agora com o operador is, após realizar uma verificação de tipo, é possível atribuir o resultado a uma variável:

public class Program {
    public static void Main(){
        Forma[] formas = { new Triangulo(10, 12, 10), new Retangulo(7, 4), new Triangulo(17, 15, 16), new Retangulo(25, 12) };

        foreach (var item in formas)
        {
            if (item is Triangulo t)
                t.Perimetro();
            if (item is Retangulo r)
                r.Area();
        }
    }
}

Com isso, não é mais necessário realizar o cast do objeto dentro do condicional. Este tipo de situação também nos permite uma verificação mais elaborada:

if (item is Retangulo r and r.Largura > 0)
    r.Area();

Assim, apenas se item for um objeto de Retangulo e a propriedade Largura deste objeto for maior que 0, que o bloco do condicional será executado.

Não temos essas situações no exemplo acima, mas agora com o operador is também é possível verificar um valor literal:

if(item is 10)
    Console.WriteLine("Item é 10");

Null:

if(item is null)
    Console.WriteLine("Item é nulo");

Ou mesmo aplicar o objeto a uma variável:

if(item is var i)
    Console.WriteLine("Item é do tipo {0}", i?.GetType().Name);

Desta forma, é possível descobrir o tipo do objeto quando esta informação não é conhecida.

Pattern Matching com switch

Além do operador is, o switch também recebeu novos recursos para ser aplicado Pattern Matching. Agora é possível comparar o tipo de um objeto dentro de um bloco switch:

foreach (var item in formas)
{
    switch(item){
        case Triangulo t:
            t.Perimetro();
            break;
        case Retangulo r:
            r.Area();
            break;
        case 10:
            Console.WriteLine("Item é 10");
            break;
        case null:
            Console.WriteLine("Item é nulo");
            break;
        case Retangulo r when r.Largura > 0:
            r.Area();
            break;
        case var i:
            Console.WriteLine("Item é do tipo {0}", i?.GetType().Name);
            break;
    }
}

Algumas das opções acima não se aplicam ao nosso exemplo, mas elas foram adicionadas para mostrar que as mesmas opções que vimos com o condicional if, podem ser aplicadas ao switch.

Conclusão

Agora o processo de checagem de tipo e cast de objetos está mais simples e dinâmico. Assim, caso esteja utilizando a versão 7.0 do C#, não hesite em utilizar este recurso. Ele irá melhorar a legibilidade do seu código.

Ref Returns no C# 7

Continuando com a série de artigos sobre novidades do C# 7, hoje conheceremos o Ref Returns.

Passagem por valor e por referência

Desde de o seu início, o C# possui duas formas de passagem de variáveis para um método, por valor e por referência.

Passagem por valor significa que apenas o valor da variável será passado para o método. Caso este valor seja alterado dentro do método, isso não irá refletir na variável passada:

static void Main(string[] args)
{
    int i = 10;
    PassagemPorValor(i);
    Console.WriteLine($"Após chamar o método, {nameof(i)} = {i}");
}

static void PassagemPorValor(int j)
{
    j = 20;
}

Já na passagem por referência, é passada a referência da variável para o método. Ou seja, é passado o endereço da memória, assim, dentro desse contexto, caso ela seja alterada, isso será refletido na variável passada:

static void Main(string[] args)
{
    int i = 10;
    PassagemPorValor(ref i);
    Console.WriteLine($"Após chamar o método, {nameof(i)} = {i}");
}

static void PassagemPorValor(ref int j)
{
    j = 20;
}

Você provavelmente já conhece essas duas formas de passagem de variáveis, pois isso é abordado (ou deveria ser) em todo curso básico da linguagem.

É necessário revisar este ponto, pois uma coisa precisa ficar bem clara: quando o ref é utilizado, trabalha-se com a referência da variável, o seu espaço de memória. Podemos até dizer que ref funciona como os ponteiros de C/C++ (para quem conhece).

Ref Local

Antes de vermos a parte principal deste artigo, é bom mencionar que agora no C# 7, o ref pode ser utilizado na declaração de uma variável:

int i = 1;
ref int j = ref i;
j = 2;

Console.WriteLine($"A variável {nameof(i)} foi alterada para: {i}");

Assim como na passagem por referência, a variável j está recebendo o endereço de memória de i. Dessa forma, quando o valor de j for alterado, o valor de i também será.

Ref Return

O último recurso que o C# 7 adicionou ao ref foi a possibilidade de retornar a referência de uma variável.

Vamos a um exemplo para ficar mais claro:

public class Exemplo
{
    private int variavel = 0;

    public int ObterValor() => variavel;

    public ref int ObterReferencia()
    {
        return ref variavel;
    }
}

Note que dentro do método ObterReferencia o método está retornando à referência da variável:

return ref variavel;

Assim, quem chamar este método para utilizar uma variável Ref Local para armazenar a referência retornada e modificar o valor da variável:

var exemplo = new Exemplo();

ref int referencia = exemplo.ObterReferencia();
referencia++;

Console.WriteLine(exemplo.ObterValor());

Como as variáveis locais são removidas da memória quando o método é finalizado, não é possível aplicar o ref return em uma variável local:

public ref int RetornarPorReferencia()
{
    int x = 10;
    return ref x;
}

Mas, objetos que são armazenados na memória heap, como um array, podem ter suas referências retornadas, mesmo que eles sejam declarados dentro do método:

public ref int RetornarPorReferencia()
{
    int[] arr = { 1, 2, 3, 4 };
    return ref arr[0];
}

O código acima também poderia ser da seguinte forma:

public ref int RetornarPorReferencia()
{
    int[] arr = { 1, 2, 3, 4 };
    ref int x = ref arr[0];
    return ref x;
}

Que o resultado seria o mesmo.

Por fim, um parâmetro passado por referência, pode ser retornado por referência:

public ref int RetornarPorReferencia(ref int x)
{
    x = 2;
    return ref x;
}

Conclusão

Apesar de ser antigo na linguagem, o modificador ref estava sendo subutilizado. Com os recursos ref local e ref return, ele ganha mais poder, permitindo que os programadores tenham acesso a memória, sem que isso torne a aplicação menos segura.

Mesmo sendo recursos úteis, há de se reconhecer que ref local e ref return são limitados a situações especificas. Então, caso você tenha um problema que se enquadre no seu uso, claro, não hesite em utilizá-lo.

As mudanças de Expression-bodied members no C# 7

Na versão 3.0 do C# surgiram as expressões lambda, como uma parte do LINQ. Na prática, elas não passam de funções anônimas, que dispensam a implementação no corpo da classe de operações simples, geralmente com apenas uma instrução.

Concebidas para simplificar e facilitar a codificação das aplicações, até a versão 5.0 do C#, elas eram utilizadas apenas dentro do corpo dos métodos de uma classe. Mas na versão 6.0 foi introduzido um novo recurso chamado Expression-bodied Members.

Em um passado não muito distante …

O Expression-bodied Members introduzido no C# 6.0 passou a permitir que expressões semelhantes às expressões lambdas fossem utilizadas para definir métodos ou propriedades somente leitura que contenham apenas uma instrução.

Por exemplo, vamos supor uma classe Pessoa que possua a estrutura abaixo:

public class Pessoa
{
    private string nome;
    private string sobrenome;
    private DateTime dataNascimento;

    public string Nome
    {
        get { return nome; }
        set { nome = value; }
    }

    public string Sobrenome
    {
        get { return sobrenome; }
        set { nome = value; }
    }

    public DateTime DataNascimento
    {
        get { return dataNascimento; }
        set { dataNascimento = value; }
    }

    public string NomeCompleto
    {
        get { return nome + " " + sobrenome; }
    }

    public int Idade()
    {
        return (DateTime.Now.Year - dataNascimento.Year - 1) +
            (((DateTime.Now.Month > dataNascimento.Month) ||
            ((DateTime.Now.Month == dataNascimento.Month) && 
            (DateTime.Now.Day >= dataNascimento.Day))) ? 1 : 0);
    }
}

Com o expression-bodied properties, uma propriedade somente leitura:

public string NomeCompleto
{
    get { return nome + " " + sobrenome; }
}

Pode ser alterada para:

public string NomeCompleto => nome + " " + sobrenome;

E com o expression-bodied methods, o método Idade:

public int Idade()
{
    return (DateTime.Now.Year - dataNascimento.Year - 1) +
        (((DateTime.Now.Month > dataNascimento.Month) ||
        ((DateTime.Now.Month == dataNascimento.Month) && 
        (DateTime.Now.Day >= dataNascimento.Day))) ? 1 : 0);
}

Pode ser modificado para:

public int Idade() => (DateTime.Now.Year - dataNascimento.Year - 1) +
                (((DateTime.Now.Month > dataNascimento.Month) ||
                ((DateTime.Now.Month == dataNascimento.Month) && 
                (DateTime.Now.Day >= dataNascimento.Day))) ? 1 : 0);

É importante frisar que em ambos os exemplos acima os blocos continham return, mas em um método sem isso:

public void Imprimir()
{
    Console.WriteLine(Nome + " " + Sobrenome);
}

Também pode ser aplicado o expression-bodied:

public void Imprimir() => Console.WriteLine(Nome + " " + Sobrenome);

O importante é que o método e propriedade somente leitura contenha uma instrução de uma linha.

Voltando ao presente

Já na versão 7 do C# foram introduzidos mais três recursos ao expression-bodied. Vamos modificar a classe apresentada anteriormente para mostrá-los:

public class Pessoa
{
    private string nome;
    private string sobrenome;
    private DateTime dataNascimento;

    public string Nome
    {
        get { return nome; }
        set { nome = value; }
    }

    public string Sobrenome
    {
        get { return sobrenome; }
        set { nome = value; }
    }

    public DateTime DataNascimento
    {
        get { return dataNascimento; }
        set { dataNascimento = value; }
    }

    public string NomeCompleto => nome + " " + sobrenome;

    public Pessoa() { }

    public Pessoa(string nome)
    {
        this.nome = nome;
    }

    ~Pessoa()
    {
        Console.WriteLine("Classe sendo destruída!");
    }

    public int Idade() => (DateTime.Now.Year - dataNascimento.Year - 1) +
            (((DateTime.Now.Month > dataNascimento.Month) ||
            ((DateTime.Now.Month == dataNascimento.Month) && 
            (DateTime.Now.Day >= dataNascimento.Day))) ? 1 : 0);
}

Note que agora temos métodos construtores e o destrutor da classe. E é nesses métodos que a partir da versão 7, pode ser aplicado, respectivamente, o expression-bodied constructors, e o o expression-bodied destructors.

O processo é o mesmo, ambos os métodos precisam conter apenas uma instrução:

public Pessoa(string nome)
{
    this.nome = nome;
}

~Pessoa()
{
    Console.WriteLine("Classe sendo destruída!");
}

Para expression-bodied ser aplicado:

public Pessoa(string nome) => this.nome = nome;

~Pessoa() => Console.WriteLine("Classe sendo destruída!");

Além do construtor e destrutor, o expression-bodied agora pode ser aplicado aos acessores das propriedades:

public string Nome
{
    get => nome;
    set => nome = value;
}

Agora que a auto propriedade (disponível desde a versão 2 do C#) ainda é a melhor opção para reduzir código:

public string Nome { get; set; }

Mas, caso seja implementada a interface INotifyPropertyChanged, o uso deste recurso é bom para reduzir o código:

public string Nome
{
    get => nome;
    set => SetProperty(ref nome, value);
}

Não tem esse exemplo na classe acima, mas este mesmo princípio que permite o uso do expression-bodied nos acessores das propriedades, pode ser aplicado aos eventos:

private EventHandler _someEvent;
public event EventHandler SomeEvent
{
    add => _someEvent += value;
    remove => _someEvent -= value;
}

Mas este também tem a sua versão mais simples:

public event EventHandler SomeEvent

Introduzida na versão 6.

Para finalizar, aplicando os conceitos acima à classe Pessoa, ela ficará com a estrutura abaixo:

public class Pessoa
{
    private string nome;
    private string sobrenome;
    private DateTime dataNascimento;

    public string Nome
    {
        get => nome;
        set => SetProperty(ref nome, value);
    }

    public string Sobrenome
    {
        get => sobrenome;
        set => nome = value;
    }

    public DateTime DataNascimento
    {
        get => dataNascimento;
        set => dataNascimento = value;
    }

    public string NomeCompleto => nome + " " + sobrenome;

    public Pessoa() { }

    public Pessoa(string nome) => this.nome = nome;

    ~Pessoa() => Console.WriteLine("Classe sendo destruída!");

    public int Idade() => (DateTime.Now.Year - dataNascimento.Year - 1) +
            (((DateTime.Now.Month > dataNascimento.Month) ||
            ((DateTime.Now.Month == dataNascimento.Month) && 
            (DateTime.Now.Day >= dataNascimento.Day))) ? 1 : 0);
}

Conclusão

Assim como demonstrado com as tuplas, o C# 7.0 está introduzindo recursos que visam melhorar a produtividade dos desenvolvedores, sejam novos, como as já referidas tuplas ou com melhorias de recursos já existentes, como é o caso do expression-bodied.

Criando repositórios para o NuGet

Existem situações onde o NuGet não pode ser acessado livremente. Mesmo que os desenvolvedores desejem ter acesso irrestrito a este gerenciador de pacotes, políticas de segurança da rede, ou mesmo limitações do ambiente (um local sem internet) podem limitar este acesso.

Felizmente o NuGet indica soluções para essas situações.

Servidor local

Geralmente quando estamos sem internet, em uma longa viagem, ou em um cliente que não libera o acesso à rede, a instalação dos pacotes NuGet fica comprometida. Não tem como instalar um pacote sem ter acesso ao nuget.org, ou tem?

Ao acessar um pacote no nuget.org, é possível ver que ele pode ser baixado:

Se ele possibilita o download do pacote, então deve ter uma forma de listá-lo offline, certo? Sim, tem, podemos criar uma pasta na rede ou no computador local, com os pacotes mais comuns:

E no Visual Studio, em Tools > NuGet Package Manager > Package Sources, é possível definir a pasta como uma fonte para o NuGet:

Com isso, quando o computador não tiver acesso à internet, ele irá procurar os pacotes no servidor local.

NuGet.Sever

O NuGet.Server é um pacote que pode ser adicionado a uma aplicação ASP.NET, e a transforma em um servidor de pacotes.

A sua configuração é simples, basta criar uma aplicação web do ASP.NET no Visual Studio, e nela adicionar o pacote NuGet.Server:

Ao fazer isso, será criada uma pasta chamada Packages no projeto:

É nesta pasta que deve ser adicionados os pacotes:

Só é importante definir nas propriedades do arquivo que ele sempre deve ser copiado quando a aplicação for executada ou publicada:

E ao executar a aplicação:

Serão mostradas as URLs do servidor NuGet, que você pode utilizar para configurar no Package Sources, ou mesmo para enviar novos pacotes.

A apikey que deve ser informada para se enviar novos pacotes, pode ser configurada no arquivo web.config da aplicação, em:

<appSettings>
    <add key="apiKey" value="" />
</appSettings>

Mas você desabilitar esta exigência no atributo requireApiKey:

<appSettings>
    <add key="requireApiKey" value="false" />
</appSettings>

Na página do NuGet, você pode ver mais opções de configuração.

NuGet Gallery

Caso não queria criar um projeto, você pode copiar do GitHub o NuGet Gallery, e publicá-lo em um servidor IIS. As configurações dele serão iguais do NuGet.Server.

Haverá uma pasta Packages onde os pacotes poderão ser adicionados e uma ApiKey deve ser definida na chave apiKey do arquivo web.config.

Serviços de terceiros

Caso não queira utilizar nenhuma das opções acima, você pode utilizar algum serviço de terceiro, como os abaixo:

Eles permitem a criação de um servidor de pacote privado. A vantagem deste método, é que o serviço que se encarregará de manter os pacotes do servidor privado atualizados.

Conclusão

O NuGet possui várias funcionalidades que não são muito conhecidas, mas que podem ser utilizadas quando os desenvolvedores quiserem ter mais controle sobre os pacotes, ou quando houver certos receios quanto ao seu uso irrestrito.

Tuplas no C# 7

Há pouco tempo saiu a versão 6 do C#. Nessa versão, as maiores mudanças ocorreram nos bastidores, foi a partir dela que o .NET passou a ser open source. Assim, pode parecer estranho falar sobre a versão 7 da linguagem, mas como os desenvolvedores não param, uma nova versão está por vir.

Desta forma, neste artigo vou falar de um dos recursos que será adicionado na linguagem que mais está me interessando, que são as tuplas (ou tuples).

O que são tuplas?

Caso programe em outra linguagem, já deve se deparado com este conceito, já que tuplas não são novidades na programação.

Podemos definir as tuplas como um conjunto temporário de valores. Você pode compará-las com uma classe POCO simples, só que no lugar de criar toda a estrutura da classe, as tuplas podem ser declaradas de forma simples e rápida.

Então, no lugar de definir uma classe assim:

class Counter
{
    public int Sum {get; set;}
    public int Count {get; set;}
}

var res = new Counter { Sum = 0, Count = 0};

Isso poderia ser definido com uma tupla da seguinte maneira:

var res = (sum: 0, count: 0);

Quando utilizá-las?

Mesmo que exemplo acima mostre uma tupla inline, este recurso geralmente será utilizado como tipo de retorno de um método.

Assim como eu, você deve ter passado pela situação onde necessitava retornar mais de um valor em um método. Hoje para fazer isso temos algumas alternativas:

  • Parâmetros de saída:
public void GetCounter(IIEnumerator<int> list, out int sum, out
int count) { ... }

int sum, count;

GetCounter(list, out sum, out count);
Console.WriteLine($"Sum: {sum}, Count: {count}");

A desvantagem é que os parâmetros de saída não funcionam com métodos assíncronos.

  • A classe System.Tulpe<T, T, ...>:
public Tuple<int, int> GetCounter(IEnumerator<int> list)
{ ... }

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Item1}, Count: {counter.Item2}");

A classe Tuple não tem a desvantagem dos parâmetros de saída, mas requer muita escrita e propriedades como Item1 e Item2 e isso tira um pouco da legibilidade do código.

  • Definir uma classe/struct ou tipo anônimo:
struct Counter { public int Sum; public int Count;}

public Counter GetCounter(IEnumerator<int> list) { ... }

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Sum}, Count: {counter.Counter}");

Esta situação não tem as desvantagens das soluções anteriores, mas gera uma sobrecarga de código desnecessária.

Assim, no C# a melhor solução será com tuplas.

  • Utilizando tuplas:
public (int sum, int count) GetCounter(IEnumerator<int> list)
{ ... }

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Sum}, Count: {counter.Counter}");

Note que no código acima, o retorno do método foi obtido normalmente, e que a partir dele foi obtido cada um dos valores de retorno.

Acima cada propriedade da tupla só foi reconhecida porque no método elas foram especificadas:

public (int sum, int count) GetCounter(IEnumerator<int> list)
{
    int s=0, c=0;
    foreach (var v in list) { s += v; c++; }
    return (s, c);
}

Mas isso não é algo obrigatório:

public (int, int) GetCounter(IEnumerator<int> list) 
{
    int s=0, c=0;
    foreach (var v in list) { s += v; c++; }
    return (s, c);
}

Assim, as propriedades da tupla poderiam ser acessadas com as propriedades Item1, Item2, …, ItemN:

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Item1}, Count: {counter.Item2}");

Mas isso não é algo que eu encorajo. Então o autocomplete nem irá mostrar este tipo de opção.

Desconstrução da tupla

Como a tupla será geralmente utilizada para se retornar mais de um valor de um método, por que continuar utilizando-a depois se se obter o retorno?

Claro que poderíamos atribuir os valores de cada propriedade da tupla para uma variável e trabalhar com esses valores separadamente. Para evitar ter todo este trabalho, o C# 7 terá a opção de desconstruir a tupla:

(var sum, var count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

Assim, as propriedades da tupla já serão atribuídas para as variáveis. Note que pode ser utilizado o var em cada variável, ou mesmo fora dos parentes:

var (sum, count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

O resultado será o mesmo.

Também pode ser definido o tipo primitivo:

(int sum, int count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

Ou mesmo declarar as variáveis fora e definí-las nos parênteses:

int sum, count;

(sum, count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

Desta desconstrução não estará disponível apenas para as tuplas. Qualquer tipo de dado pode ser desconstruído, desde que ele implemente um método desconstrutor, com a sintaxe abaixo:

public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

Assim, quando o objeto for desconstruído, este método será chamado:

class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) { X = x; Y = y; }

    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

(var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);

Conclusão

As tuplas são um recurso muito bem-vindo para a linguagem, facilitará o desenvolvimento e com o tempo serão muito utilizadas.

Para que servem os métodos ToString(), Equals() e GetHashCode()?

Muitas pessoas costumam se confundir com três métodos que qualquer classe possui no .NET e no Java: os métodos ToString(), Equals() e GetHashCode(). Os dois primeiros são mais ou menos claros até certo ponto, mas o último vive causando confusão e pânico entre desenvolvedores Java e C#, ainda mais na hora em que necessitamos sobrescrevê-los… Que tal entendermos de uma vez por todas para que estes três métodos servem?

Importante: os conceitos que veremos aqui servem tanto para a plataforma Java quanto para a plataforma .NET! 😀

Por que toda classe tem estes métodos?

No Java e no .NET, toda classe tem um ancestral por “padrão”: a classe Object. Dessa maneira, qualquer objeto de qualquer classe que criarmos em Java ou .NET, pelo Princípio da Substituição de Liskov, também será uma instância de Object. E a classe Object já expõe por padrão pelo menos os três métodos que estão em discussão.

O método ToString()

Talvez este seja o método que é mais claro com relação ao seu propósito. Seu objetivo é trazer uma representação textual de uma instância de um objeto.
Essa representação textual de um objeto vem a ser muito útil principalmente em situações de debugging e de logging. Isso ocorre porque os métodos de saída para o streamming padrão (os famosos System.out.print[ln]() ou Console.Write[Line]()), assim como os principais métodos de praticamente todas as APIs de log (métodos como o debug() e info()) sempre chamam por padrão o método ToString() de instâncias de objetos que tenham sido passadas para eles.

O exemplo abaixo está escrito em C#, mas o princípio é exatamente o mesmo para o Java também, rs.

Imagine o código abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Console.WriteLine(p);
// ou
Console.WriteLine(p.ToString());

Perceba que não foi realizada nenhuma sobrecarga do método ToString(), o que faz com que a chamada deste método para a classe Pessoa encaminhe a chamada para a classe ancestral, ou seja: na verdade será chamado o método Object.ToString().

Não se esqueça: ==todo e qualquer objeto/classe sempre vai ter o método ToString() por causa da herança da classe Object==. Se a própria classe não implementa este método, a chamada será encaminhada para o ToString() “padrão”, ou seja, o método ToString() da classe Object.

No caso do C#, a saída do código abaixo seria a representada abaixo, independente da chamada explícita ao método ToString() ou não:

TreinaWeb.Exemplo.Pessoa

A saída tanto para a chamada explícita ao método ToString() como para a chamada ocultando-se a chamada é a mesma. Isso ocorre porque os método Console.Write[Line]() chama o método ToString() da instância repassada como parâmetro por padrão. 😉

O que o .NET emite como saída nesses casos é um elemento conhecido como Full Qualified Name, ou simplesmente FQN. Apesar de o nome assustar, ele é muito simples: trata-se somente do “nome completo” e único da classe, que é composto por namespace + nome da classe.

Se tivéssemos o código equivalente em Java, teríamos uma saída similar à abaixo:

TreinaWeb.Exemplo.Pessoa@1033203

A saída é muito similar ao FQN do .NET, com o acréscimo deste número precedido por @. Costumam dizer por aí que este número logo após o @ é a posição de memória do objeto. Daqui a pouco vamos ver que não é “beeem” assim, hehe.

O grande ponto é que essa saída fornecida pelo método ToString() não tem nenhuma utilidade prática para nós, tanto no streamming padrão de saída, quanto em um arquivo de log. Daí vem a necessidade da sobrescrita deste método.

A idéia é que o método ToString() forneça uma representação simplificada e direta do estado do objeto em questão. Uma maneira de atingir este objetivo é fazer com que a saída forneça o valor atual dos atributos do objeto.

Poderíamos sobrescrever o método ToString() da nossa classe Pessoa da seguinte maneira:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override string ToString()
        {
            return string.Format("Pessoa [Nome = {0}]", this.Nome);
        }
    }
}

Nossa saída agora, tanto em Java quanto em .NET, seria a mesma abaixo:

Pessoa [Nome = TreinaWeb]

Agora temos uma representação “palpável” e que faça sentido para nós. Essa é com certeza uma representação muito melhor do que as representações padrão da classe Object.

O método Equals()

Antes de falarmos do método Equals(), precisamos relembrar um pouco sobre manipulação de memória, principalmente sobre stack e heap.

Se você quer relembrar como funciona a manipulação de memória, tanto em Java quanto em .NET, você pode ver este nosso artigo, onde tratamos sobre manipulação de memória, stack, heap, value-types e reference-types.

Quando criamos um objeto da classe Pessoa, este objeto será armazenado na memória heap:

Pessoa minhaPessoa = new Pessoa();

Alocação de memória: reference-type

Não se esqueça de que o compilador não acessa os objetos na heap de maneira direta por questões de performance. Sendo assim, o acesso a esse objeto armazenado na heap é feito através de uma referência dentro da stack para o objeto minhaPessoa, apontando onde na memória heap que este objeto está de fato guardado! Sim: estamos falando de ponteiros.

Acesso à memória: reference-types

É importante entendermos estes conceitos para entendermos melhor como o método Equals() funciona.

Quando comparamos objetos, é considerada uma boa prática utilizarmos o método Equals() para fazer a comparação de igualdade. E, quando a classe a qual os objetos em questão pertencem não sobrescreve o método Equals(), o método Object.Equals() será chamado.

Vamos verificar o código abaixo, considerando ainda a classe Pessoa:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = new Pessoa { Nome = "TreinaWeb" }; 

Console.WriteLine(p.Equals(p2));

Faria muito sentido se esse código fornecesse como saída true, afinal, os objetos aparentemente são iguais, certo? Mas esse código irá produzir false, apesar de o nome nos dois objetos serem iguais… Por que isso acontece?

Aí entra em cena a relação entre o método Object.Equals() e a maneira como a memória é manipulada no Java e no .NET.

A cada vez que utilizamos a keyword new, nós estamos instruindo o compilador a reservar um espaço na memória heap e criar um ponteiro na stack para que seja possível acessar esta área da heap. Sendo assim, no código acima, são criadas duas áreas de memória na heap distintas. Estas áreas são gerenciadas, respectivamente, pelos ponteiros p e p2.

O que ocorre quando utilizamos o Equals() baseado na implementação de Object.Equals() é que este por padrão verifica se os ponteiros ==apontam para a mesma área de memória na heap!== Como temos dois objetos instanciados de maneira distintas (chamamos o new para cada um dos ponteiros), nós temos também duas posições de memória distintas para cada um dos objetos p e p2. Por isso, por padrão, temos como resposta false para o código acima.

Agora, vamos imaginar o código abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = p; 

Console.WriteLine(p.Equals(p2));

Agora sim! Perceba que para p2 nós não instruímos o compilador a criar uma nova área de memória na heap, e sim o instruímos a fazer com que p2 aponte para a mesma posição de memória na heap para a qual p aponta. Desta maneira, neste último código, obteremos true como resposta quando utilizamos a implementação do método Equals() baseada em Object.Equals().

Agora, não faz muito sentido para nós esta implementação do método Equals() baseado em Object.Equals(). Para nós, faria muito mais sentido que o método Equals() na primeira situação deste tópico retornasse true, afinal, ambos os objetos Pessoa possuem o mesmo nome. Por isso, é importantíssimo para nós sobrescrevermos de maneira adequada o método Equals().

Antes de partirmos para a sobrecarga, é importante entendermos algumas premissas para o método Equals():

  • Se ambos os objetos que estão sendo comparados apontam para a mesma posição de memória, é mandatório que Equals() retorne true, baseado na implementação de Object.Equals();
  • x.Equals(x) sempre tem que retornar true;
  • x.Equals(y) sempre tem que retornar o mesmo que y.Equals(x). Este princípio é conhecido como princípio de simetria;
  • Se x.Equals(y) e y.Equals(z), z.Equals(x) tem que retornar true também. Isso ocorre por decorrência do princípio da simetria;
  • x.Equals(null) sempre será falso. O método Equals() por definição não pode retornar exceções do tipo NullReferenceException ou NullPointerException. Isso faz sentido: se um ponteiro é nulo, na verdade ele não aponta para nenhuma área da heap e, portanto, é impossível fazer uma comparação coerente entre os objetos.

Tendo todos estes princípios em vista, temos 3 pontos importantes a serem observados quando sobrescrevermos o método Equals() para que este atenda a todos estes requisitos:

  • Precisamos ver se um dos participantes da chamada do método Equals() é nulo;
  • Precisamos ver se os dois objetos apontam para a mesma área de memória;
  • Precisamos comparar o estado interno dos participantes da chamada do Equals().

Poderíamos sobrescrever o método Equals() da nossa classe Pessoa() da seguinte maneira, afim de que a nossa sobrecarga atenda aos requisitos essenciais:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override bool Equals(Object obj)
        {
            // Verificando se o segundo participante está nulo
            if (obj == null)
            {
                return false;
            }
            Pessoa p2 = obj as Pessoa;
            // Verificando se o cast foi realizado com sucesso. 
            // Caso não foi, obj nem é um objeto do tipo Pessoa 
            // e automaticamente o método tem que retornar false
            // NOTA: o operador de cast "as" retorna
            // null caso o cast não seja possível
            if (p2 == null)
            {
                return false;
            }
            // Vamos agora verificar se ambos apontam para a mesma posição 
            // de memória utilizando Object.Equals()
            if (base.Equals(obj))
            {
                return true;
            }
            // Agora comparamos o estado interno dos objetos!
            return this.Nome == p2.Nome;
        }
    }
}

Agora nós temos o método Equals() devidamente sobrescrito e respeitando todas as condições necessárias. Considerando esta sobrecarga, se chamarmos o código abaixo…

Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = new Pessoa { Nome = "TreinaWeb" }; 
Pessoa p3 = p2;

Console.WriteLine(p.Equals(p2));
Console.WriteLine(p2.Equals(p3));
Console.WriteLine(p.Equals(p3));

… obteremos true em todas as saídas, o que faz muito sentido!

No caso específico do C#, nós ainda poderíamos utilizar p == p2, o que causaria também a chamada de Object.Equals(). Por isso, no C#, temos a possibilidade de fazermos a sobrescrita também de operadores. Nesta situação, precisaríamos sobrescrever o operador == para a classe Pessoa para que tenhamos tudo “nos trilhos” e não tenhamos duas implementações distintas de igualdade. Da mesma maneira, acaba sendo prudente sobrescrever também o operador !=.
Nosso código poderia ficar como está abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public static bool operator ==(Pessoa a, Pessoa b)
        {
            // Fazemos o cast para Object
            // para evitar StackOverFlowException:
            // o código cairia em loop infinito
            // chamando de maneira encadeada
            // o método Equals() sobrescrito
            // e o operador == sobrescrito! 😉
            if ((((object)a) == null) || (((object)b) == null))
            {
                return false;
            }
            return object.Equals(a, b) || a.Nome == b.Nome;
        }

        public static bool operator !=(Pessoa a, Pessoa b)
        {
            return !(a == b);
        }

        public override bool Equals(Object obj)
        {
            // Verificando se o segundo participante está nulo
            if (obj == null)
            {
                return false;
            }
            Pessoa p2 = obj as Pessoa;
            // Verificando se o cast foi realizado com sucesso. 
            // Caso não foi, obj nem é um objeto do tipo Pessoa 
            // e automaticamente o método tem que retornar false
            if (p2 == null)
            {
                return false;
            }
            // Vamos agora verificar se ambos apontam 
            // para a mesma posição de memória utilizando Object.Equals()
            if (base.Equals(obj))
            {
                return true;
            }
            // Agora comparamos o estado interno dos objetos!
            return this.Nome == p2.Nome;
        }
    }
}
O método GetHashCode() ou hashCode()

Por fim, temos o famigerado método GetHashCode() ou hashCode(). Este é um dos métodos que causam mais confusão nos desenvolvedores.

O hash code é um número inteiro que é gerado de maneira única para cada objeto que esteja alocado em memória. É como se ele fosse um ID único para cada objeto que esteja sob domínio da CLR ou da JVM.

E onde este código único é utilizado? Aí é a grande sacada! Ele é utilizado principalmente dentro de coleções com a finalidade de melhoria da performance. A JVM e a CLR utilizam o hash code internamente para localizar objetos em coleções de maneira mais rápida. E daí também vem sua relação direta com o método Equals().

Uma das maneiras que os ambientes de execução do .NET e do Java utilizam para ver se um determinado objeto existe dentro de uma coleção é comparando os hash codes dos objetos pertencentes à coleção com o hash code do objeto a ser localizado. E, se um objeto é localizado dentro de uma coleção, é porque ele é igual ao elemento dentro de alguma posição da coleção em questão. Percebe a relação entre GetHashCode() e Equals()?

Por isso, vale a máxima abaixo para o método GetHashCode():

Se x.Equals(y) e x e y são objetos da mesma classe, x.GetHashCode() == y.GetHashCode() tem que obrigatoriamente retornar true.

A sobrescrita do Equals() impacta diretamente na implementação do GetHashCode() e vice-versa por causa dessa relação entre os dois. E é por isso que as IDEs emitem warnings quando você, por exemplo, sobrescreve o método Equals() e não sobrescreve o método GetHashCode() e vice-versa.

Agora, um ponto interessante: nem a CLR e nem a JVM garantem a situação inversa! Isso quer dizer que dois objetos, deste que de tipos (ou classes) diferentes, podem por coincidência retornar o mesmo hash code!

Se x é do tipo A e y é do tipo B (ou seja: x e y são objetos de classes diferentes), x.Equals(y) irá retornar por definição false; porém, pode ser que x.GetHashCode() == y.GetHashCode() retorne true.

Essa situação, apesar de ser incomum, pode acontecer. Ela é chamada de colisão. A colisão, quando ocorre, geralmente acontece por causa de sobrescrita equivocada do método GetHashCode().

O método padrão Object.GetHashCode() tem uma implementação um pouco complexa. Se estivermos falando de .NET, a chamada a Object.GetHashCode() irá ser convertida para uma chamada de baixo nível para ObjectNative::GetHashCode. Se estivermos falando de Java, a chamada de Object.hashCode() irá converter a posição de memória onde o objeto está alocado para uma representação numérica, adotando esta representação como sendo o hash code. Inclusive, lembra-se da implementação padrão do método toString() no Java? Pois então… Aquele número estranho que vem depois do @ é o hash code do objeto! =)

Existe um outro ponto muito importante com relação ao método GetHashCode():

Se GetHashCode() tem relação direta com Equals(), um mesmo objeto sempre deverá retornar o mesmo hash code, da mesma maneira que seu método Equals() sempre retornará o mesmo resultado quando haver uma comparação entre dois objetos.

Aí entra um problema grave: a implementação padrão vinda de Object.GetHashCode() não retorna o mesmo hash code para o mesmo objeto. Na verdade, a cada chamada ao método padrão Object.GetHashCode(), um novo hash code será invocado. Isso faz cair por terra o ganho de performance que a utilização de GetHashCode() poderia trazer… Daí vem a necessidade de sobrescrevermos corretamente este método em nossas classes.

Uma técnica geralmente utilizada para conseguirmos sobrescrever corretamente o método GetHashCode() é somar os hash codes de todos os atributos da classe e multiplicar por um número primo. Isso reduz bastante as chances de haver algum tipo de colisão.

Sendo assim, poderíamos sobrescrever o método GetHashCode() da classe Pessoa da seguinte maneira:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override int GetHashCode()
        {
            // 17 é um número primo! 😉
            return this.Nome.GetHashCode() * 17;
        }
    }
}

O método GetHashCode() auxilia na performance dentro de coleções porque é muito mais simples para os compiladores comparar dois números inteiros do que chamar o método Equals() para cada elemento que faça parte da coleção, sendo que a implementação do Equals() pode ser um pouco complexa e lenta.

Sendo assim, quando o compilador precisa localizar um objeto dentro de uma coleção, ele faz uma iteração em cada elemento que faça parte e faz a comparação com o objeto a ser localizado da seguinte maneira:

  • A primeira comparação é feita através do hash code dos objetos envolvidos. Se eles forem diferentes, o compilador para o trabalho por aqui, já que se dois objetos são iguais (ou seja, o método Equals() com os dois objetos deveria retornar true), o hash code de ambos também deveria ser igual;
  • Caso os hash codes sejam iguais, o compilador levanta a hipótese de estar havendo uma colisão. Então, ele chama o método Equals() para confirmar se os objetos são iguais ou não. Se eles forem iguais, o compilador considera que encontrou o objeto dentro da coleção. Caso não, o compilador avança para o próximo item da coleção e reinicia o processo de comparação.

Consegue perceber como o hash code pode acelerar o processo de manipulação de coleções? Ele ajuda o compilador a evitar a chamada ao método Equals(), que pode ser lento, de maneira desnecessária! o/

O método GetHashCode() é importantíssmo para coleções. Desde coleções mais básicas, como ArrayList; até coleções mais complexas, como Dictionary<TKey, TValue> ou Map<K, U>, se utilizam deste método. O Dictionary<TKey, TValue>, inclusive, utiliza este método para localizar se existem chaves duplicadas ou não.

Os métodos ToString(), Equals() e GetHashCode() são importantíssimos!

Esperamos que você tenha percebido melhor a importância desses métodos para os desenvolvedores. Muitos costumam não dar a devida importância para a sobrescrita correta destes métodos, o que pode ocasionar problemas bem críticos no código em determinadas situações (principalmente quando falamos de serialização de objetos e ambientes de alta concorrência). Você, inclusive, pode acompanhar um problema decorrente da sobrescrita e utilização incorretas destes métodos em um ambiente real neste post do StackOverflow.

Tem alguma dúvida? Quer discutir sobre algum determinado ponto? Quer expor uma situação pela qual você já passou no seu dia-a-dia como desenvolvedor que envolvia a utilização destes métodos? Compartilha com a gente nos comentários! Vamos discutir sobre este assunto! o/

Até o próximo post! =)

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES