.NET

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! 😀

Curso de
CONHEÇA O CURSO
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.

Curso de
CONHEÇA O CURSO
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! =)

Curso de
CONHEÇA O CURSO

Processo de execução de um código no .NET Framework

Quando estamos desenvolvendo, o único momento em que pensamos na compilação do código é quando algum erro ocorre. Fora isso, essa é só mais uma etapa do processo de execução (que costuma passar despercebida).

Só que um bom programador precisa entender como este processo funciona, para criar códigos melhores, entender mais a linguagem que se está utilizando e compreender melhor os erros gerados.

No geral as linguagens possuem um processo de compilação parecido, mas aqui veremos o processo de compilação de uma aplicação desenvolvida para o .NET Framework.

Nele, separamos o processo de compilação em quatro etapas:

  • Escolhendo um compilador;
  • Compilando o código para a linguagem intermediária;
  • Compilando o código da linguagem intermediário para a nativa;
  • Executando o código.

Essas etapas são ilustradas na imagem abaixo:

Curso de
CONHEÇA O CURSO

Escolhendo um compilador

O .NET Framework possui um importante recurso que permite a interoperabilidade das linguagens suportadas por ele, que é o Common Language Runtime
(CLR).

Seguindo as especificações definidas no padrão ECMA-335 (Obs: Esse link leva a um arquivo PDF) – que define as especificações do CLR – qualquer linguagem pode definir um compilador suportado pelo .NET. Com isso, é possível desenvolver aplicações para o .NET utilizando C#, F#, Perl, Cobol, Ruby etc.

Assim, na compilação, a primeira coisa que o .NET faz é selecionar o compilador definido para a linguagem do código que será compilado.

É função do compilador verificar o código que está sendo analisado. Ele verificará se algum token está sendo utilizado de forma errônea e se todas as regras da linguagem estão sendo obedecidas. Basicamente ele verificará se há algum erro de digitação no código.

Compilando o código para a linguagem intermediária

Agora que o .NET começa a fazer a sua “mágica”. Ao final do processo de compilação, o compilador converte o código-fonte para uma linguagem intermediária, que no .NET, é chamada de Microsoft Intermediate Language (MSIL), ou apenas Intermediate Language (IL).

O IL é um conjunto de instruções independente da CPU, que pode ser convertido de forma mais eficiente para código nativo.

Nele temos instruções para carregar, armazenar, iniciar e chamar métodos em objetos, bem como, instruções para operações lógicas e matemáticas, controle de fluxo, acesso direto a memória, manipulação de erros, entre outros recursos.

Antes deste código ser gerado, ele precisa ser convertido em tempo de execução para um código de máquina que é entendido pela CPU do computador. Normalmente este processo é realizado por um compilador Just-in-Time (JIT). Como o CLR fornece um ou mais compiladores de acordo com a arquitetura do computador, o mesmo conjunto de instruções IL pode ser compilado e executado em qualquer arquitetura suportada pelo .NET

Quando o código intermediário é produzido, também são gerados metadados. Esses metadados descrevem os tipos utilizados no código incluindo as suas definições, as assinaturas dos seus membros, as referências a outros códigos e qualquer coisa que será utilizada em tempo de execução.

O IL e os metadados são contidos em um arquivo executável portátil (PE – portable executable), que é baseado e que estende o Microsoft PE e Common Object File Format (COFF), historicamente utilizado para conteúdo executável. Este formato de arquivo, que possui o IL ou código nativo, bem como metadados, permite que o sistema operacional reconheça características do CLR. A presença dos metadados com o código intermediário permite que o código se descreva, o que significa que não há necessidade de incluir bibliotecas de tipos ou interfaces de definição da linguagem (IDL).

Durante a execução, o sistema localiza e extrai dos metadados as informações que necessitar.

Compilando o código da linguagem intermediária para a nativa

Antes de executar o código IL, é necessário compilá-lo para a linguagem nativa, suportada pela arquitetura da máquina alvo. Para fazer este processo, o .NET fornece duas formas de conversão:

  • Um compilador Just-in-Time (JIT);
  • O Ngen.exe (Native Image Generator).

Compilação com um compilador JIT

A compilação JIT converte o código intermediário em código nativo em tempo de execução. Conforme um código IL for requisitado, o JIT o converte para código nativo.

Como o CLR fornece um compilador JIT para cada arquitetura suportada pelo .NET, um código IL pode ser compilado pelo JIT e executado em diferentes arquiteturas. No entanto, se o código chamar APIs nativas de uma plataforma, ou algum recurso específico, o código IL só poderá ser executado nesta plataforma.

A compilação JIT leva em conta a possibilidade de algum código nunca ser chamado durante a execução. Assim, em vez de usar tempo e memória para converter todo o código, em um arquivo PE nativo, ele converte apenas o que for necessário para a execução, e armazena este código nativo gerado, na memória, para ele ficar acessível para as chamadas subsequentes.

Por exemplo, na primeira vez que um método é invocado durante a execução, o JIT irá convertê-lo para código nativo e armazená-lo na memória. Quando este código for chamado novamente, o JIT irá diretamente na memória obter o código nativo, em vez de tentar convertê-lo novamente.

Compilação com o Ngen.exe

Como o compilador JIT gera o código nativo em tempo de execução, isso pode impactar um pouco na performance da aplicação. Na maioria dos casos, esta perda é aceitável ou irrisória. Além disso, o código gerado pelo compilador JIT fica vinculado ao processo que desencadeou a compilação, assim ele não pode ser compartilhado entre vários processos.

Para permitir que o código gerado possa ser compartilhado entre múltiplas chamadas ou entre vários processos que compartilham um conjunto de códigos nativos, o .NET possui o compilador Ngen.exe, também chamado de compilador Ahead-of-Time (AOT).

Este compilador gera o código nativo de uma forma muito parecida que o JIT, mas eles se diferenciam em três pontos:

  • O código IL é convertido antes do arquivo ser executado, no lugar de convertê-lo durante a execução.
  • Todo o código é convertido de uma única vez.
  • O código convertido é salvo no disco, e não na memória.

Verificação de código

Se não for desabilitado, durante a compilação para o código nativo, tanto com JIT ou com o AOT, o código IL passa por uma verificação que analisa se o código é “type safe”, o que significa que o código só acessa locais da memória que ele está autorizado a acessar.

Isso ajuda a isolar um objeto do outro e ajuda a protegê-lo contra corrupção indevida ou mal-intencionada. Ele também garante que as restrições de segurança podem ser aplicadas de forma confiável.

Para verificar se o código é “type safe”, a verificação se baseia em três afirmações (que precisam ser verdadeiras):

  • Uma referência a um tipo é estritamente compatível com o tipo a ser referenciado;
  • Somente operações apropriadamente definidas são invocadas em um objeto;
  • As identidades são de quem afirmam ser.

Se o requisito “type safe” estiver ativo e o código não passar na verificação acima, é gerando um erro de execução.

Executando o código

O processo final de execução, é a execução do código propriamente dito. Como já dito nos tópicos anteriores, durante esta execução o CLR e os metadados irão fornecer as informações que forem necessárias para a execução da aplicação.

Lendo assim, é possível notar que o processo não é simples, mas não é tão complexo, por isso que é importante o seu entendimento.

Curso de
CONHEÇA O CURSO

Google na .NET Foundation? SQL Server para Linux? Visual Studio para MacOS? Saiba como foi o Connect()

A Microsoft realizou no dia 16 de novembro sua tradicional conferência anual, a Connect(). Trata-se de um evento muito importante não somente para os profissionais envolvidos com tecnologias Microsoft, mas sim de um evento que vem ganhando cada vez mais relevância para a comunidade com um todo desde a mudança do comportamento da Microsoft com relação às plataformas Open Source.

A Microsoft utiliza este evento também para apresentar quais são as grandes novidades para desenvolvedores de software e profissionais de infraestrutura. E podemos afirmar que as novidades apresentadas neste ano são simplesmente de deixar o queixo caído!

Se você não está por dentro das novidades, dê uma olhadinha neste post que fizemos para que você fique por dentro de tudo! 😉

Curso de
CONHEÇA O CURSO

Google entra oficialmente para a .NET Foundation

Aí está algo que era inimaginável há pouco tempo… A Google entrou de forma oficial dentro da .NET Foundation.

Caso você não conheça, a .NET Foundation é o braço open source da Microsoft, responsável por gerenciar e suportar o .NET Framework dentro da comunidade. Ela foi criada desde o momento em que a Microsoft decidiu tornar todo o ecossistema .NET completamente gratuito e open source. Ela seria, fazendo um paralelo com outras plataformas, o equivalente à Apache Foundation ou à Eclipse Foundation.

Já que o Google decidiu se juntar à .NET Foundation, isso quer dizer que ele irá contribuir de maneira oficial e permanente com o desenvolvimento e evolução das tecnologias .NET. Já já teremos então códigos dos engenheiros da Google dentro dos repositórios .NET!

A cooperação Google e Microsoft já não é tão recente. O Angular2, por exemplo, foi escrito em TypeScript, que é um superset do JavaScript criado pela… Microsoft! Houve uma intensa interação entre os times da Google e da Microsoft no processo de criação do Angular2, interação essa que foi muito proveitosa segundo ambos os lados. Mas, a entrada da Google como membro oficial da .NET Foundation expande os horizontes para essa parceria.

Segundo a Google, a intenção de fazer parte da .NET Foundation é melhorar o suporte à aplicações .NET dentro de sua plataforma de computação em nuvem, a Google Cloud. Mas, será que mais coisas estão para surgir dessa parceria inusitada?

Microsoft entra para a Linux Foundation

Se a parceria anterior era inusitada, esta aqui era no mínimo completamente inimaginável anteriormente. Mas, aconteceu: a Microsoft entrou como membro oficial da Linux Foundation! Isso quer dizer que a Microsoft agora se compromete de maneira oficial a contribuir com o desenvolvimento e evolução dos sistemas baseados em Linux. E aí entram todas as distribuições que nós conhecemos ou não, como Ubuntu, Debian e por aí vai.

A Microsoft já adotava o tom de cooperação com sistemas baseados em Linux há algum tempo, cooperação esta que foi se intensificando com o lançamento de softwares para ambientes Linux. Porém, a entrada da Microsoft como membro oficial da Linux Foundation leva tudo a um patamar muito superior. E o melhor: a Linux Foundation já se manifestou de maneira completamente favorável e entusiasmada à entrada da Microsoft.

É cedo para afirmar o que pode acontecer com relação a este acontecimento. Mas, certamente o suporte ao .NET Framework dentro de ambientes não-Windows (suporte que já é muito bom na verdade) tende a melhorar de maneira exponencial. Há também uma grande tendência de começar a surgir soluções interoperáveis entre servidores baseados em Linux e servidores Windows, interoperabilidade essa que existe hoje em dia mas pode ser perfeitamente aperfeiçoada. Vamos aguardar…

Samsung passa a dar suporte oficial para o .NET Framework no Tizen

Outro ponto importantíssimo para o .NET Framework. A Microsoft anunciou uma parceria com a Samsung, o que irá permitir o desenvolvimento de aplicações .NET nativas para dispositivos baseados no Tizen!

O Tizen é um sistema operacional desenvolvido pela Samsung para smartphones, wearable devices e smart TVs. Se você, por exemplo, tem uma smart TV Samsung, a chance de ela estar rodando o Tizen internamente é bem grande.

Você pode estar pensando que o Tizen não é um sistema operacional lá muito famoso e que o impacto disso não é tão grande. Mas, nos permita discordar disso, rs. As smart TVs, por exemplo, estão se tornando cada vez mais populares. A Samsung é uma das líderes de mercado neste segmento. Isso é uma possibilidade e tanto para desenvolvedores .NET começarem a expandir os horizontes de suas aplicações com o mínimo de esforço de reescrita de código. Há também o fato de que é uma oportunidade muito legal para que melhorias no suporte do .NET Framework a dispositivos “menores” possa ser melhorado com base na experiência da implantação de aplicações dentro do Tizen. E ainda há outro ponto: se a Samsung conseguiu dar este suporte, por que outras plataformas mobile futuramente não podem dar suporte ao desenvolvimento .NET nativo? 😉

Se quiser saber mais sobre o anúncio oficial do time do Tizen com relação ao suporte ao .NET, é só clicar aqui

SQL Server para Linux

Pois é, agora podemos rodar o SQL Server no Linux!

Isso não é bem uma novidade… Faz algum tempo que a Microsoft já tinha comentado sobre essa possibilidade, tendo inclusive disponibilizado uma versão do SQL Server para Linux em um beta muito restrito. Porém, agora a versão do SQL Server para Linux (chamado por hora de SQL Server vNext) é pública e qualquer um pode experimentá-la. Ela está aqui.

Para que você fique mais empolgado, o SQL Server agora também é oficialmente suportado pelo Docker! Inclusive, já há imagens oficiais disponíveis no Docker Hub. É só baixar as imagens e começar a se divertir! o/

Curso de
CONHEÇA O CURSO

Visual Studio for Mac

Outra novidade que era inimaginável há pouquíssimo tempo atrás… Agora a Microsoft disponibiliza uma versão do Visual Studio para… Mac! E sim, estamos falando de Visual Studio mesmo, aquele igualzinho ao do Windows, e não do Visual Studio Code. Agora será possível ter a experiência do Visual Studio for Windows dentro do MacOS de maneira nativa. Você pode obter o Visual Studio for Mac aqui.

Nós instalamos o Visual Studio for Mac e também testamos alguns de seus recursos. Confira nas imagens abaixo:

Assim que você baixa e monta o arquivo DMG, você cai nesta tela. Basta dar um duplo clique na seta gigante que o processo de instalação será iniciado.

Você receberá o tradicional aviso do MacOS sobre a origem do instalador. Não se preocupe, pode confiar e clicar no “Abrir” ou “Open”.

Como não poderia faltar, os tradicionais termos de licença, haha. Marque o checkbox para afirmar que você concorda com os termos de licença para continuar com o processo de instalação.

Nesta página, você poderá escolher os componentes adicionais a serem instalados junto com o Visual Studio. Perceba que, por estes componentes adicionais, estamos na verdade falando de um único framework: Xamarin! Isso mostra que a Microsoft está cada vez mais absorvendo e integrando o Xamarin (que foi recentemente adquirido pela Microsoft) como um integrante nativo do .NET Framework.

Aqui você pode definir o local de instalação padrão do Visual Studio. Se quiser alterá-lo, basta clicar no botão “+” (sim, o ícone deste botão não ficou muito legal, rs).

Logo depois é apresentado um pequeno review dos componentes a serem instalados.

E agora é esperar pacientemente a instalação. Se você marcou os componentes do Xamarin anteriormente, a instalação levará um tempinho bom. Isso ocorre porque o instalador irá baixar todos os componentes necessários para o desenvolvimento de aplicações Android e iOS, o que inclui todos os SDKs necessários.

Quando a instalação for concluída, você verá a tela abaixo:

Agora é hora de abrir o Visual Studio!

Muito igual ao do Windows, não? Haha

O Visual Studio for Mac na verdade traz um misto da interface para o Windows com a interface de ferramentas de desenvolvimento tradicionais para o MacOS, como o XCode. A intenção da Microsoft foi suavizar a curva de aprendizado da IDE tanto para desenvolvedores acostumados a desenvolver aplicações dentro da plataforma MacOS com o XCode como para desenvolvedores acostumados com o Visual Studio para Windows.

E já dá até para criar uma série de projetos!

Agora, não se esqueça de que esta é a primeira versão, além de ser um preview ainda. O Visual Studio for Mac ainda tem vários bugs, além de não suportar completamente todas as plataformas da maneira como você pode estar esperando (um exemplo é o desenvolvimento de aplicações ASP.NET Core: nesta versão, você ainda não vai ter a experiência igual a que você tem no Visual Studio para Windows, muito pelo contrário, haha). Mas só a possibilidade de podermos desenvolver aplicações multiplataforma nativas com o .NET Core utilizando IDEs também interoperáveis é fantástico! Ah, provavelmente já já deve ser lançada uma versão para Linux também! 😉

Outro ponto importante: não pense que agora o Visual Studio Code será inutilizado dentro do Mac. Eles são ferramentas diferentes. O Visual Studio Code é um editor de código, enquanto o Visual Studio for Mac é uma IDE propriamente dita. Ambos possuem suas respectivas utilidades. Há ainda o fato de o Visual Studio for Mac ainda não suportar legal o desenvolvimento de aplicações ASP.NET Core, sendo melhor desenvolvê-las no Visual Studio Code junto com o grupo DotNET CLI + Yeoman + Gulp + Bower ainda. Isso também quer dizer que, se você fez nosso curso de Introdução ao ASP.NET Core, você não perdeu o que foi aprendido, muito pelo contrário! o/

E agora!?

Agora só nos resta, como desenvolvedores, aproveitar todos os benefícios que estas novidades nos trará. E não são poucos os benefícios, heim? Rs

A Microsoft já suportava o desenvolvimento multiplataforma com o .NET Framework, mas, a tendência é que o framework agora seja cada vez mais refinado com estas novas parcerias. Há também a forte tendência de que o framework evolua cada vez mais para o campo dos dispositivos mobile, smart devices e wearable devices com a parceria com a Samsung e com a Google, o que certamente ampliará mais ainda as possibilidades para desenvolvedores .NET. A entrada na Linux Foundation certamente ajudará a Microsoft a construir um .NET Framework cada vez mais multiplataforma, além de fomentar o surgimento de aplicações e soluções Windows que se encaixam perfeitamente com soluções Linux, e vice-versa.

O futuro para os desenvolvedores .NET parece excelente, não? =)

(Ah, se você estiver curioso para ver como já é possível desenvolver aplicações .NET de maneira nativa em ambientes Linux e MacOS, você pode dar uma olhadinha no nosso curso de Introdução ao ASP.NET Core. E fique ligado: já já estaremos lançando o nosso curso de Docker! o/).

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Operações CRUD no ASP.NET MVC 5 com o ADO.NET

Conversando com um amigo ele contou como funciona a estrutura da aplicação ASP.NET MVC da empresa onde ele recentemente começou a trabalhar.

A minha surpresa foi saber que durante a migração da aplicação desktop para ASP.NET MVC eles optaram por continuar utilizando a mesma estrutura de acesso à base de dados com o clássico e bom ADO.NET.

Não pretendo discorrer nesse artigo se essa foi uma boa ou má escolha ou se seria melhor modificar todo o acesso à base de dados durante a migração e optar por outro framework ORM.

O que essa situação me mostrou é que mesmo com
várias opções, ainda há quem prefira utilizar o ADO.NET e não há muitos artigos abordando o uso deste framework com o ASP.NET MVC.

Sendo assim, mostrarei aqui como isso pode ser feito. Então, mãos à massa!

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Criando uma aplicação ASP.NET MVC

Para esse artigo estou utilizando o Visual Studio Community 2015. Nele, siga os procedimentos abaixo para criar o projeto.

  1. Com o Visual Studio aberto, em “File”, clique em “New > Project”, selecione o template “ASP.NET Web Application”, por fim, clique em OK.

A tela abaixo será apresentada:

  1. Como mostra a imagem, selecione a opção de template Empty e adicione a referência ao MVC. Depois clique em OK para criar o projeto:

Pronto! O nosso projeto está criado.

Criando classes do domínio

Aqui não vou me ater muito a padrões de projetos, mas para ficar próximo a uma estrutura comum quando se faz uso de um framework ORM, vamos criar no projeto classes de domínio e um repositório.

A classe de domínio será idêntica com uma classe de domínio de um framework ORM:

public class Pessoa
{
    public int Id { get; set; }

    [Required(ErrorMessage = "O campo nome é obrigatório.")]
    public string Nome { get; set; }

    public string Email { get; set; }

    public string Cidade { get; set; }

    public string Endereco { get; set; }
}

A anotação Required definida acima será utilizada para a validação na View.

Como a aplicação está utilizando o ADO.NET ela não poderá fazer uso das migrations do Entity (ou qualquer outro recurso equivalente em outro framework ORM), então, as classes de domínio devem representar exatamente a estrutura das tabelas do banco.

Ou seja, quando se utiliza o ADO.NET, deve ser empregado o princípio Database-First.

Criando o repositório

A criação do repositório é a parte parte mais crítica de uma aplicação que faz uso do ADO.NET, pois é nesta fase que as configurações de acesso são definidas.

Recomenda-se definir uma uma interface, mas neste exemplo, utilizarei uma classe abstrata:

public abstract class AbstractRepository<TEntity, TKey>
    where TEntity : class
{
    protected string StringConnection { get; } = WebConfigurationManager.ConnectionStrings["DatabaseCrud"].ConnectionString;

    public abstract List<TEntity> GetAll();
    public abstract TEntity GetById(TKey id);
    public abstract void Save(TEntity entity);
    public abstract void Update(TEntity entity);
    public abstract void Delete(TEntity entity);
    public abstract void DeleteById(TKey id);
}

Nesta classe, além dos métodos do repositório, é definido um atributo readonly (somente leitura), que obterá do arquivo web.config a string de conexão com o banco.

Nele é necessário definir essa string:

<connectionStrings>
  <add name="DatabaseCrud" connectionString="Data Source=(localdb)MSSQLLocalDB; Initial Catalog=DatabaseCrud-20161026144350; Integrated Security=True; MultipleActiveResultSets=True;"
    providerName="System.Data.SqlClient" />
</connectionStrings>

Agora implementaremos o repositório da nossa entidade:

public class PessoaRepository : AbstractRepository<Pessoa, int>
{
    ///<summary>Exclui uma pessoa pela entidade
    ///<param name="entity">Referência de Pessoa que será excluída.</param>
    ///</summary>
    public override void Delete(Pessoa entity)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "DELETE Pessoa Where Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", entity.Id);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    ///<summary>Exclui uma pessoa pelo ID
    ///<param name="id">Id do registro que será excluído.</param>
    ///</summary>
    public override void DeleteById(int id)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "DELETE Pessoa Where Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", id);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    ///<summary>Obtém todas as pessoas
    ///<returns>Retorna as pessoas cadastradas.</returns>
    ///</summary>
    public override List<Pessoa> GetAll()
    {
        string sql = "Select Id, Nome, Email, Cidade, Endereco FROM Pessoa ORDER BY Nome";
        using (var conn = new SqlConnection(StringConnection))
        {
            var cmd = new SqlCommand(sql, conn);
            List<Pessoa> list = new List<Pessoa>();
            Pessoa p = null;
            try
            {
                conn.Open();
                using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    while (reader.Read())
                    {
                        p = new Pessoa();
                        p.Id = (int)reader["Id"];
                        p.Nome = reader["Nome"].ToString();
                        p.Email = reader["Email"].ToString();
                        p.Cidade = reader["Cidade"].ToString();
                        p.Endereco = reader["Endereco"].ToString();
                        list.Add(p);
                    }
                }
            }
            catch(Exception e)
            {
                throw e;
            }
            return list;
        }
    }

    ///<summary>Obtém uma pessoa pelo ID
    ///<param name="id">Id do registro que obtido.</param>
    ///<returns>Retorna uma referência de Pessoa do registro encontrado ou null se ele não for encontrado.</returns>
    ///</summary>
    public override Pessoa GetById(int id)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "Select Id, Nome, Email, Cidade, Endereco FROM Pessoa WHERE Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", id);
            Pessoa p = null;
            try
            {
                conn.Open();
                using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    if (reader.HasRows)
                    {
                        if (reader.Read())
                        {
                            p = new Pessoa();
                            p.Id = (int)reader["Id"];
                            p.Nome = reader["Nome"].ToString();
                            p.Email = reader["Email"].ToString();
                            p.Cidade = reader["Cidade"].ToString();
                            p.Endereco = reader["Endereco"].ToString();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                throw e;
            }
            return p;
        }
    }

    ///<summary>Salva a pessoa no banco
    ///<param name="entity">Referência de Pessoa que será salva.</param>
    ///</summary>
    public override void Save(Pessoa entity)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "INSERT INTO Pessoa (Nome, Email, Cidade, Endereco) VALUES (@Nome, @Email, @Cidade, @Endereco)";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Nome", entity.Nome);
            cmd.Parameters.AddWithValue("@Email", entity.Email);
            cmd.Parameters.AddWithValue("@Cidade", entity.Cidade);
            cmd.Parameters.AddWithValue("@Endereco", entity.Endereco);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    ///<summary>Atualiza a pessoa no banco
    ///<param name="entity">Referência de Pessoa que será atualizada.</param>
    ///</summary>
    public override void Update(Pessoa entity)
    {
        using (var conn = new SqlConnection(StringConnection))
        {
            string sql = "UPDATE Pessoa SET Nome=@Nome, Email=@Email, Cidade=@Cidade, Endereco=@Endereco Where Id=@Id";
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.Parameters.AddWithValue("@Id", entity.Id);
            cmd.Parameters.AddWithValue("@Nome", entity.Nome);
            cmd.Parameters.AddWithValue("@Email", entity.Email);
            cmd.Parameters.AddWithValue("@Cidade", entity.Cidade);
            cmd.Parameters.AddWithValue("@Endereco", entity.Endereco);
            try
            {
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }
}

Note que em todos os métodos a conexão é aberta manualmente. Ela será fechada graças ao uso do using. E todos os métodos “lançam” as exceções geradas para o respectivo método do repositório que fora invocado.

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Criando o Controller

Caso estivéssemos utilizando o Entity, o controller poderia ser criado já com as views, mas no nosso ambiente, teremos que criá-los separadamente. O controller será criado com as actions:

Com o uso do repositório terá o código abaixo:

public class PessoaController : Controller
{
    private PessoaRepository respository = new PessoaRepository();
    // GET: Pessoa
    public ActionResult Index()
    {
        return View(respository.GetAll());
    }

    // GET: Pessoa/Create
    public ActionResult Create()
    {
        return View();
    }

    // POST: Pessoa/Create
    [HttpPost]
    public ActionResult Create(Pessoa pessoa)
    {
        if (ModelState.IsValid)
        {
            respository.Save(pessoa);
            return RedirectToAction("Index");
        }
        else { 
            return View(pessoa);
        }
    }

    // GET: Pessoa/Edit/5
    public ActionResult Edit(int id)
    {
        var pessoa = respository.GetById(id);

        if (pessoa == null)
        {
            return HttpNotFound();
        }

        return View(pessoa);
    }

    // POST: Pessoa/Edit/5
    [HttpPost]
    public ActionResult Edit(Pessoa pessoa)
    {
        if (ModelState.IsValid)
        {
            respository.Update(pessoa);
            return RedirectToAction("Index");
        }
        else
        {
            return View(pessoa);
        }
    }

    // POST: Pessoa/Delete/5
    [HttpPost]
    public ActionResult Delete(int id)
    {
        respository.DeleteById(id);
        return Json(respository.GetAll());      
    }
}

Pronto, agora só é necessário definir as Views.

Criando as views

O código das views é bem simples, pois podemos utilizar o scaffolding do Visual Studio.

No controller clique com o botão direito sobre a
action Index, e selecione Add View. A tela abaixo será apresentada:

Configure os dados conforme a imagem acima. Agora, modifique o código gerado para este:

@model IEnumerable<CRUDUsingMVCwithAdoNet.Models.Pessoa>
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>

<p>
    @Html.ActionLink("Adicionar Pessoa", "Create")
</p>
<table class="table" id="tblPessoas">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Nome)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Email)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Cidade)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Nome)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Email)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Cidade)
                </td>
                <td>
                    @Html.ActionLink("Editar", "Edit", new { id = item.Id }) |
                    <button type="button" class="btn btn-link" data-item="@item.Id">Deletar</button>
                </td>
            </tr>
        }
    </tbody>
</table>
@section Scripts {
<script type="text/javascript">
        $(document).ready(function () {
            $(".btn-link").click(function () {
                var id = $(this).attr('data-item');
                if (confirm("Você tem certeza que gostaria de excluir este registro?")) {
                    $.ajax({
                        method: "POST",
                        url: "/Pessoa/Delete/" + id,
                        success: function (data) {
                            $("#tblPessoas tbody > tr").remove();
                            $.each(data, function (i, pessoa) {
                                $("#tblPessoas tbody").append(
                                    "<tr>" +
                                    "   <td>" + pessoa.Nome + "</td>" +
                                    "   <td>" + pessoa.Email + "</td>" +
                                    "   <td>" + pessoa.Cidade + "</td>" +
                                    "   <td>" +
                                    "       <a href='/Pessoa/Edit/" + pessoa.Id + "'>Editar</a> |" +
                                    "       <button type="button" class="btn btn-link" data-item="" + pessoa.Id + "">Deletar</button>" +
                                    "   </td>" +
                                    "</tr>"
                                );
                            });
                        },
                        error: function (data) {
                            alert("Houve um erro na pesquisa.");
                        }
                    });
                }
            });
        });
</script>
}

Como mostra o código acima, para não ser necessário criar uma view de exclusão, esta funcionalidade já é definida na listagem.

Repita o mesmo procedimento para termos a view Create:

@model CRUDUsingMVCwithAdoNet.Models.Pessoa
@{
    ViewBag.Title = "Cadastrar";
}

<h2>Cadastrar nova Pessoa</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Pessoa</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Nome, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Nome, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Nome, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Cidade, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Cidade, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Cidade, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Endereco, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Endereco, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Endereco, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Cadastrar" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>
<script src ="~/Scripts/jquery.validate.min.js" />
<script src ="~/Scripts/jquery.validate.unobtrusive.min.js" />

E agora a view Edit:

@model CRUDUsingMVCwithAdoNet.Models.Pessoa
@{
    ViewBag.Title = "Editar";
}

<h2>Editar</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Pessoa</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.Id)

        <div class="form-group">
            @Html.LabelFor(model => model.Nome, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Nome, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Nome, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Cidade, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Cidade, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Cidade, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Endereco, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Endereco, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Endereco, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Salvar" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Voltar a listagem", "Index")
</div>
<script src="~/Scripts/jquery.validate.min.js" />
<script src="~/Scripts/jquery.validate.unobtrusive.min.js" />

Como criamos o projeto a partir de um template vazio, não se esqueça de adicionar os scripts do jQuery Validation (https://jqueryvalidation.org/) e do jQuery Validation Unobtrusive (https://github.com/aspnet/jquery-validation-unobtrusive).

Pronto! A aplicação está pronta. Agora é só testar.

Executando a aplicação

Ao executar a aplicação a tela abaixo será mostrada:

Clique em Adicionar Pessoa para inserir um novo registro.

Se no cadastro nada for informado:

A validação funcionará. Se dados forem informados, o registro será salvo no banco e listado:

Caso clique em Editar, a edição estará funcionando perfeitamente:

E no caso da exclusão é exibida uma caixa de diálogo:

Que ao ser confirmada o registro será excluído:

Conclusão

Os frameworks ORM trazem produtividade para o desenvolvimento, mas o seu uso não pode nos cegar em relação ao legado. Caso seja necessário, é possível fazer uso do ADO.NET no ASP.NET MVC, sem estresse. Esta substituição só será sentida na produtividade devido a não possibilidade de uso de alguns recursos do Visual Studio.

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Gerenciamento de memória no C#: stack, heap, value-types e reference-types

Opa pessoal, tudo certinho?

Neste post, eu vou abordar uma dúvida que aparece com uma certa frequência em nosso suporte: o que são, afinal de contas, as benditas memórias stack e heap? O que são afinal de contas os value-types e os reference-types?

Para entendermos melhor estes conceitos, precisamos também verificar um pouco sobre a criação de objetos e de variáveis primitivas, assim como os conceitos de value-types e reference-types… Então, vamos lá! o/

O compilador, de maneira geral, divide a memória em duas grandes áreas: a stack (uma área bem menor) e a heap (uma área bem maior). Seria algo simiar à ilustração abaixo:

Áreas de memória - compilador .NET

Na configuração padrão do .NET Framework, para que você tenha uma idéia melhor de como a stack é muito menor que a heap, o tamanho padrão para a memória stack é de apenas 1MB!

Ambas trabalham como pilhas, porém, a maneira como cada uma provê acesso a seu conteúdo é diferente. A stack é bem mais eficiente para localizar as coisas em seu interior com relação a heap, mesmo porque ela é bem menor.

As variáveis de alguns tipos de dados leves (tipos primitivos – int, double, bool etc. – e structs) são armazenadas diretamente na stack, a área menor e mais eficiente para localização dos conteúdos. Elas ficam diretamente nessa área justamente por serem tipos de dados que não ocupam tanto espaço na memória. O mais interessante é que o valor que elas contêm também fica junto com elas na stack. Ou seja, quando você faz a declaração abaixo:

int numero = 3;

O compilador armazena essa variável diretamente na memória stack, como na ilustração abaixo:

Alocação de memória - Value-Type

C# (C Sharp) Básico
Curso de C# (C Sharp) Básico
CONHEÇA O CURSO

Perceba que o valor da variável fica junto com a própria variável. Variáveis onde isso acontece são chamadas de Value-Types, justamente porque o valor delas fica junto com a própria variável na memória stack. Assim, quando você tem o seguinte código:

if (numero >= 3)
{
    //...
}

O compilador tem acesso direto ao conteúdo, pois ele está juntinho com a própria variável na memória stack:

Acesso à memória: value-type

Agora, outros tipos de dados ocupam muito mais espaço de memória do que estes tipos leves que são value-types. Por isso, eles não podem ser armazenados diretamente na stack (caso fossem, rapidamente a memória stack seria “estourada”, causando o famoso erro StackOverflowException). Sendo assim, estes dados são armazenados na memória heap.

Vamos imaginar que você tenha o seguinte código:

class Pessoa
{
    public int Id {get; set;}
    public string Nome {get; set;}
}

Quando você cria um objeto dessa classe, este objeto será armazenado na memória heap:

Pessoa minhaPessoa = new Pessoa();

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

Porém, o compilador não acessa a heap diretamente. Por que ele não acessa? Justamente porque ela é muito grande… Se ele fosse procurar o objeto minhaPessoa dentro da heap, ele iria demorar um tantinho bom de tempo. O compilador precisaria ter um jeito de acessar pela stack (que é rápida pra encontrar as coisas até mesmo por ser bem menor) o que está alocado na heap (que é bem maior). Como o compilador contorna isso? Criando uma referência dentro da stack para o objeto minhaPessoa, apontando onde na memória heap que este objeto está de fato guardado!

Processo de referência stack-heap

Essa porção de memória que é alocada na stack para apontar para uma posição de memória da heap é chamada de ponteiro. Por isso ele tem esse asterisco (*) na frente do seu nome.

Repare então que é criada uma referência da stack para uma determinada posição de memória da heap, referência essa guardada por um ponteiro na stack. Esse tipo de variável (como no caso da variável minhaPessoa, do tipo Pessoa) é chamada de Reference-Type, já que é necessário uma referência da stack para a heap para que esta variável seja acessível. Variáveis reference-type geralmente precisam que seja chamado o respectivo construtor através da palavra-chave new, pois ele é que define que uma porção de memória da heap deverá ser utilizada para guardar aquele objeto.

Dessa maneira, quando temos o código abaixo:

if (minhaPessoa.Id > 2)
{
    //...
}

O compilador faz o acesso ao objeto minhaPessoa através da stack, ou seja, através do ponteiro. Esse ponteiro encaminha o compilador para a posição de memória da heap que contém de fato o objeto minhaPessoa.

Acesso à memória: reference-types

Resumindo:

  • Value-Type: são tipos leves (como os tipos primitivos e structs) que ficam armazenados diretamente na memória stack. Os valores das variáveis ficam armazenados juntamente com as próprias variáveis, sendo o acesso ao seu conteúdo feito de maneira direta;
  • Reference-Type: tipos pesados (objetos criados a partir de classes, etc.) que ficam armazenados na heap. Para não sacrificar a performance, é criada uma referência (ponteiro) na stack que aponta para qual posição de memória o objeto está armazenado na heap. O acesso é feito via essa referência na stack. Sendo assim, o acesso ao conteúdo é indireto, dependendo dessa referência;
  • Stack: porção de memória pequena onde os value-types e os ponteiros ficam;
  • Heap: porção maior de memória onde os reference-types ficam de fato alocados… Para se fazer o acesso a eles, precisamos de um ponteiro na stack que indique a posição de memória na heap onde o objeto está de fato alocado.

O detalhe interessante é que a imensa maioria das linguagens gerenciadas (como o Java, Swift e o próprio C#) seguem exatamente esta arquitetura de gerenciamento de memória… Isso quer dizer que o que vimos aqui para o C#, pode ser aplicado para outras linguagens com pequenas variações. o/

É isso aí pessoal. Obrigado por terem lido este post! =)

Nos próximos posts, continuaremos as discussões sobre os value-types e os reference-types e veremos um tipo de dado que foge um pouco da regra: as strings.

C# (C Sharp) - TDD
Curso de C# (C Sharp) - TDD
CONHEÇA O CURSO