.NET

O funcionamento do garbage collector no .NET

A CLR – o ambiente de execução do .NET Framework – é um ambiente gerenciado e, por isso, não precisamos nos preocupar diretamente com questões relacionadas a liberação de memória. Isso é possível por causa de um componente importantíssimo da CLR: trata-se do garbage collector. O garbage collector tem exatamente a responsabilidade de lidar com a liberação da memória associada a uma variável e/ou objeto quando este não é mais utilizado em nenhum ponto do nosso código.

Entender como o garbage collector funciona no .NET é essencial para que possamos entender como um todo como a CLR funciona. Questões relacionadas ao garbage collector também são muito úteis quando precisamos escrever código de alta performance para aplicações que exigem respostas próximas ao real-time “de verdade”. Por isso, vamos entender como o garbage collector funciona na CLR.

Como funciona o gerenciamento de memória no .NET?

No artigo C# Gerenciamento de memória no C#: stack, heap, value-types e reference-types que escrevi aqui para o blog da TreinaWeb, apresentei as duas principais divisões da área de memória durante a execução de uma aplicação .NET: a stack, para os value-types; e a heap, para os reference-types. Dependendo do tipo de “dado” que você aloca em uma variável, o dado pode ser deslocado ou para a stack – se estivermos falando de structs – ou para a heap – se estivermos falando de tipos ditos “complexos”, como objetos.

Desenvolvedor C# Sênior
Formação: Desenvolvedor C# Sênior
A formação Desenvolvedor C# nível Sênior da TreinaWeb tem como objetivo apresentar conceitos avançados do C#, como covariância, contravariância e invariância de interfaces; utilização extensiva de delegates, expressões lambda e métodos de extensão. Por fim, o LINQ e o Entity Framework, ferramentas importantíssimas e essenciais no .NET, são abordadas e estudadas.
CONHEÇA A FORMAÇÃO

Quando falamos sobre o garbage collector, é importante frisar que este atua organizando a memória na heap, embora ele utilize as referências guardadas na stack para determinar o que ainda está em uso ou não. Os objetos que são alocados na heap acabam sendo divididos em três grupos, também chamados de gerações:

  • geração 0: é a primeira geração onde um objeto é alocado. Assim que algo é alocado na heap,o objeto alocado é colocado imediatamente como sendo parte da geração 0;
  • geração 1: trata-se de uma geração de transição, onde objetos que são utilizados de maneira “média” são alocados;
  • geração 2: é uma geração que contém objetos que são utilizados por mais tempo e que, por isso, precisam existir na memória por um tempo maior.

O que o garbage collector faz basicamente é migrar os objetos entre estas três áreas distintas e eliminar as áreas de memória associadas a objetos que não são mais utilizados.

O ciclo de trabalho do garbage collector na heap

O garbage collector trabalha em ciclos de análise sobre as gerações que existem na heap. O que diferencia o funcionamento do garbage collector sobre as gerações é a peridiocidade da inspeção: a geração 0, por ser menor na maioria das vezes, sofre análises do garbage collector mais frequentes do que as gerações 1 e 2 por exemplo. Nestas análises, o que o garbage collector faz é verificar se os integrantes das gerações estão ainda sendo utilizados e, caso algum integrante não seja mais necessário, este é removido, fazendo com que a área de memória correspondente seja liberada e fique disponível para novas alocações.

É importante notar também que os ciclos de análise ocorrem na geração que é alvo do ciclo e nas gerações anteriores. Por exemplo: se o garbage collector precisa analisar a geração 0, somente ela é analisada. Se o garbage collector precisa analisar a geração 1, as gerações 1 e 0 são analisadas. Se o garbage collector precisa analisar a geração 2, as gerações 2, 1 e 0 são analisadas. Em decorrência desse funcionamento, o ciclo de análise na geração 2 ganha o nome de coleta completa, pois todas as gerações acabam sendo analisadas.

Essa análise do garbage collector pode ocorrer em situações pré-definidas:

  • Quando o sistema operacional informa que possui pouca memória física disponível;
  • O tamanho das gerações é estourado;
  • O método GC.Collect() é invocado, o que caracteriza uma chamada explícita para o processo de análise do garbage collector dentro da aplicação.

Promoções de geração em objetos

Os objetos são inicialmente alocados na geração 0 – portanto, na geração que sofre coletas mais rápidas – porque a CLR supõe que estes objetos não serão mais necessários muito rapidamente, o que faria com que estes objetos fossem removidos rapidamente da memória. E isso é verdade para a maioria dos cenários. Porém, alguns objetos podem precisar sobreviver por mais de um ciclo de análise do garbage collector… Quando o garbage collector detecta um objeto que precisa sobreviver por mais tempo na memória do que o esperado para a geração onde ele se encontra, o objeto sofre o que é chamado de promoção: o objeto é deslocado para a geração superior, sofrendo ciclos mais espaçados de análise do garbage collector. Por exemplo: se um objeto que está na geração 0 sobrevive a um ciclo de análise, o mesmo é deslocado para a geração 1. Se um objeto sobrevive a um ciclo de análise sob a geração 1, o mesmo é deslocado para a geração 2, sofrendo a análise do garbage collector de maneira mais espaçada ainda.

Esse processo de promoção pode tornar a gerência de memória um processo muito lento, já que esse deslocamento de gerações pode exigir uma quantidade de processamento computacional considerável, dependendo da quantidade de objetos alocados… Por isso, o CLR sempre está “supervisionando” o trabalho do garbage collector. Se o garbage collector passa a detectar que a taxa de sobrevivência de objetos em uma determinada geração é muito alta, a CLR aumenta o tamanho da geração em questão, evitando que as gerações tenham seu tamanho estourado frequentemente. A CLR sempre tenta equilibrar os ciclos de análise do garbage collector e o tamanho das gerações – as gerações também não podem ser sempre expandidas, pois isso deixaria o sistema operacional sem memória em um curto espaço de tempo.

Áreas efêmeras

O CLR e o garbage collector supõe que os objetos que fazem parte das gerações 0 e 1 terão um ciclo de vida muito curto, sendo eliminados rapidamente da memória. Por isso, estas gerações também são chamadas de gerações efêmeras.

As gerações também costumam ser agrupadas em segmentos de memória, segmentos estes que são gerenciados pelo garbage collector. Segmentos que possuem as gerações 0 e 1 também são chamados de segmentos efêmeros.

O garbage collector também realiza a manipulação destes segmentos. Os segmentos recém-criados também são definidos como segmentos efêmeros. Quando o garbage collector cria mais um novo segmento, o segmento que antes era efêmero deixa de ter essa característica, passando a ser considerado um segmento de geração 2 (a geração com objetos que sobrevivem por mais tempo). O novo segmento passa a ser considerado neste momento como o segmento efêmero.

No próximo artigo, iremos analisar em detalhes como o garbage collector realiza o trabalho de remoção e promoção de objetos entre os segmentos gerenciados pelo garbage collector.

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.

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

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.

C# (C Sharp) Intermediário
Curso de C# (C Sharp) Intermediário
CONHEÇA O CURSO

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 &gt;, 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 =&gt; num &lt; 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.

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

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.

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

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:

Lógica de Programação Completo
Curso de Lógica de Programação Completo
CONHEÇA O CURSO
Lógica Orientada a Objetos Completo
Curso de Lógica Orientada a Objetos Completo
CONHEÇA O CURSO

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

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

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.

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

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.

C# (C Sharp) Avançado
Curso de C# (C Sharp) Avançado
CONHEÇA O CURSO

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.)
Desenvolvedor C# Pleno
Formação: Desenvolvedor C# Pleno
A formação Desenvolvedor C# nível Pleno da TreinaWeb tem um enfoque sobre a conectividade entre o .NET Framework e os bancos de dados relacionais através do ADO.NET. Também serão abordados os recursos para desenvolvedores que o Oracle e o MySQL oferecem, como functions, stored procedures e triggers.
CONHEÇA A FORMAÇÃO

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
Desenvolvedor ASP.NET Full-Stack
Formação: Desenvolvedor ASP.NET Full-Stack
A formação Desenvolvedor ASP.NET Full Stack da TreinaWeb tem como objetivo abordar as duas principais plataformas dentro do ASP.NET: o ASP.NET MVC, para criação de aplicações web seguindo o padrão MVC/MVW; e o ASP.NET WebAPI, para criação de APIs RESTful que sigam os padrões mais atuais da indústria.
CONHEÇA A FORMAÇÃO

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.

F# (F Sharp) - Fundamentos
Curso de F# (F Sharp) - Fundamentos
CONHEÇA O CURSO

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.

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

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.

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

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;
C# (C Sharp) Intermediário
Curso de C# (C Sharp) Intermediário
CONHEÇA O CURSO

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;
}
C# (C Sharp) Avançado
Curso de C# (C Sharp) Avançado
CONHEÇA O CURSO

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.

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

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.

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

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.

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

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.

C# (C Sharp) Intermediário
Curso de C# (C Sharp) Intermediário
CONHEÇA O CURSO

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.

C# (C Sharp) Avançado
Curso de C# (C Sharp) Avançado
CONHEÇA O CURSO

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).

C# (C Sharp) - APIs REST com ASP.NET Web API
Curso de C# (C Sharp) - APIs REST com ASP.NET Web API
CONHEÇA O CURSO

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.

Adobe After Effects CC - Completo
Curso de Adobe After Effects CC - Completo
CONHEÇA O CURSO