C

Utilizando AutoMapper no C#

Ao criar uma aplicação, a recomendação mais ouvida (e que deveria ser a mais seguida) é a separação de responsabilidade entre as camadas do sistema. Existem vários padrões de projetos que auxiliam na implementação deste processo, sendo MVC o mais popular. Hoje há uma infinidade de frameworks baseados neste padrão.

Entretanto em algumas situações apenas ele não garante isso, pode ser necessário ter um desacoplamento maior entre as camadas. Para facilitar este processo, temos o AutoMapper.

AutoMapper

O AutoMapper é uma biblioteca open-source criada por Jimmy Bogard para resolver um problema enganosamente complexo, que é o mapeamento de um objeto para outro. Este tipo de tarefa geralmente é chata e tediosa, então esta pequena biblioteca foi criada para resolver esta situação de forma simples.

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

Mapeando uma Model para uma ViewModel

O AutoMapper é mais utilizado em aplicações ASP.NET MVC, principalmente quando esta aplicação faz uso do padrão MVVM (Model-View-ViewModel), mas como pode ser utilizado em qualquer tipo de aplicação e para facilitar a explicação, aqui veremos os exemplos do uso desta biblioteca em uma aplicação console simples.

Entretanto, antes de vermos o AutoMapper na prática, vamos compreender o problema que resolve.

Ao implementar padrão MVVM, haverão duas classes com características equivalentes: uma que refletirá o domínio, e.g, uma entidade do Entity Framework; e outra que refletirá a View, a interface da aplicação. Para ilustrar isso, imagine uma entidade Cliente:

public class Cliente
{
    public long Id { get; set; }
    public string Nome { get; set; }
    public string Sobrenome { get; set; }
    public DateTime DataNascimento { get; set; }
    public double Renda { get; set; }

    public override string ToString()
        => $"{Id} - {Nome} - {Sobrenome} - {DataNascimento} - {Renda}";
}

Na interface de visualização dos clientes, não deve ser exibido a renda. Desta forma, cria-se uma ViewModel com a seguinte estrutura:

public class ClienteListViewModel
{
    public long Id { get; set; }
    public string Nome { get; set; }
    public string Sobrenome { get; set; }
    public DateTime DataNascimento { get; set; }

    public override string ToString()
        => $"{Id} - {Nome} - {Sobrenome} - {DataNascimento}";
}

Para mapear os dados de Cliente para ClienteListViewModel, podemos fazê-lo manualmente da seguinte forma:

static void Main(string[] args)
{
    var cliente = new Cliente()
    {
        Id = 1,
        Nome = "Carlos",
        Sobrenome = "Silva",
        DataNascimento = new DateTime(1980, 03, 12),
        Renda = 4012.04
    };
    var clienteViewModel = new ClienteListViewModel()
    {
        Id = cliente.Id,
        Nome = cliente.Nome,
        Sobrenome = cliente.Sobrenome,
        DataNascimento = cliente.DataNascimento
    };

    Console.WriteLine(clienteViewModel);
    Console.ReadLine();
}

Algo simples. Agora imagine ter que trabalhar com uma entidade que possui vinte propriedades, caso haja muitos registros, ou mesmo, com várias entidades implementando o padrão MVVM? É possível ver que este tipo de tarefa simples pode se tornar complicado com adição de poucos detalhes.

É para facilitar este tipo de ação que o AutoMapper foi criado.

AutoMapper vem ao resgate

Para utilizar esta biblioteca em um projeto é necessário adicioná-la via NuGet:

dotnet add package AutoMapper

Na sua utilização básica, é necessário criar uma configuração que indica como as classes são mapeadas:

var configuration = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Cliente, ClienteListViewModel>();
});

Em CreateMap deve ser informado a classe de origem dos objetos (Cliente) e a classe de destino (ClienteListViewModel). Caso também seja realizado o mapeamento inverso, isso deve ser informado nesta configuração:

var configuration = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Cliente, ClienteListViewModel>();
    cfg.CreateMap<ClienteListViewModel, Cliente>();
});

A partir da configuração cria-se um objeto Mapper:

var mapper = configuration.CreateMapper();

Que irá aplicar o mapeamento:

var clienteViewModel = mapper.Map<ClienteListViewModel>(cliente);

Perfis de entidade

Como as ViewModels refletem as interfaces da aplicação, em alguns projetos uma entidade pode gerar várias ViewModels. Para facilitar a organização dos mapeamentos dessas ViewModels, o AutoMapper permite a criação de perfis (Profile) para uma entidade:

public class ClienteProfile: Profile
{
    public ClienteProfile()
    {
        CreateMap<Cliente, ClienteListViewModel>();
        CreateMap<ClienteListViewModel, Cliente>();
    }
}

Com isso, na hora de criar a configuração, indica-se o perfil da entidade:

var configuration = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<ClienteProfile>();
});

Outra vantagem de se utilizar perfis é que pode ser informado o assembly da aplicação:

var configuration = new MapperConfiguration(cfg =>
{
    cfg.AddMaps(typeof(Program).Assembly);
});

E o AutoMapper irá analisa-lo para carregar os perfis definidos no código. Este tipo de recurso é muito útil quando a aplicação define vários perfis de entidade.

Mapeamento customizado

Caso ambos os objetos possuam propriedades com os mesmos nomes, como no nosso exemplo até o momento, o mapeamento do AutoMapper é automático, mas pode haver situações onde isso não ocorre.

Por exemplo, vamos modificar a nossa classe ClienteListViewModel para o código abaixo:

public class ClienteListViewModel
{
    public long Id { get; set; }
    public string NomeCompleto { get; set; }
    public DateTime DataNascimento { get; set; }

    public override string ToString()
        => $"{Id} - {NomeCompleto} - {DataNascimento}";
}

Isso não irá gerar erro no nosso projeto, mas a biblioteca não saberá mapear o valor de NomeCompleto, assim esta propriedade não receberá nenhum dado.

Então é necessário informar como essa propriedade deve ser mapeada:

CreateMap<Cliente, ClienteListViewModel>()
    .ForMember(dst => dst.NomeCompleto,
                    map => map.MapFrom(src => $"{src.Nome} {src.Sobrenome}"));

Acima estamos dizendo que a propriedade NomeCompleto em ClienteListViewModeldeve ser mapeada para os valores de Nome e Sobrenome de Cliente.

GraphQL - Criando APIs modernas com Graphcool e Apollo
Curso de GraphQL - Criando APIs modernas com Graphcool e Apollo
CONHEÇA O CURSO

Conclusão

O uso básico da biblioteca AutoMapper é muito simples, o que facilita a adoção do padrão MVVM. Além disso, mesmo sendo pequena, possui muitos recursos, que não foram abordados por completo aqui.

Existem outras bibliotecas que realizam este tipo de procedimento, mas caso necessite fazer este tipo de mapeamento no seu projeto, ou esteja pensando em desacoplar as camadas da sua aplicação, o AutoMapper é uma ótima pedida.

Por hoje é só! Até a próxima 🙂

Generalizando tipos de retornos assíncronos no C# 7.0

Até a versão 6.0 do C#, os métodos assíncronos poderiam retornar os tipos Task, Task<T> e void.

Por se tratar de um objeto, em algumas situações os retornos de Task<T>, podem apresentar problemas de performance. Alocado na memória heap e coletado pelo garbage coletor, ao fazer uso intenso de métodos assíncronos, a performance de uma aplicação pode ser impactada pela volumosa alocação dos objetos Task<T>.

Para resolver este problema, na versão 7.0 do C#, foi adicionado o recurso que permite criar tipos de retorno para os métodos assíncronos.

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

Regras para a criação de um tipo Task<T>

Ao ler a documentação sobre os tipos de retorno assíncrono, é dito que qualquer tipo que contenha um método GetAwaiter público, que retorne um objeto que implemente a interface System.Runtime.CompilerServices.ICriticalNotifyCompletion, pode ser utilizado. Mas não é apenas isso.

Além deste detalhe, os tipos retornados por um método assíncrono, devem seguir um padrão “task type”. Pode ser uma classe ou estrutura, mas precisam estar associados a um builder type, identificado com o atributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Caso retornem valores, deve ser definido como um tipo genérico.

O “tipo task” em si, só necessita implementar o método GetAwaiter público, mas o objeto retornado por este método, deve implementar a interface ICriticalNotifyCompletion, o método GetResult e possuir uma propriedade pública chamada IsCompleted.

Já o builder type a ser criado, deve corresponder a classe ou estrutura definida. Não se pode utilizar um existente e deve possuir a estrutura abaixo:

class MyTaskMethodBuilder<T>
{
    public static MyTaskMethodBuilder<T> Create();

    public void Start<TStateMachine>(ref TStateMachine stateMachine)
        where TStateMachine : IAsyncStateMachine;

    public void SetStateMachine(IAsyncStateMachine stateMachine);
    public void SetException(Exception exception);
    public void SetResult(T result);

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine;

    public MyTask<T> Task { get; }
}

Para tipos não genéricos, o método SetResult não possui parâmetro.

A classe acima será utilizada pelo compilador para transformar tipo assíncrono como um método assíncrono na compilação para o MSIL.

Você pode ver mais detalhes do funcionamento desta classe, aqui.

Parece complicado? Vamos a um exemplo para entender estes pontos.

Criando um tipo assíncrono/Task

Inicialmente criarei uma classe que implementará a interface ICriticalNotifyCompletion, que chamarei de MyTaskAwaiter:

public class MyTaskAwaiter<TResult> : ICriticalNotifyCompletion
{
    //Retorna se a operação foi concluída
    public bool IsCompleted => _value.IsCompleted;
    private readonly MyTask<TResult> _value;

    //Inicializa a classe com a classe Task criada
    public MyTaskAwaiter(MyTask<TResult> value)
    {
        this._value = value;
    }

    //Retorna o resultado
    public TResult GetResult() => _value.GetResult();

    //Define uma continuação.
    public void OnCompleted(Action continuation)
    {
        _value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().OnCompleted(continuation);
    }

    //Define uma continuação.
    public void UnsafeOnCompleted(Action continuation)
    {
        _value.AsTask().ConfigureAwait(continueOnCapturedContext: true).GetAwaiter().UnsafeOnCompleted(continuation);
    }
}

Os métodos mais importantes desta classe são, o GetResult e o OnCompleted, que, respectivamente, retorna o resultado da “Task”, e informa quando ela foi concluída.

Note que a “Task” é uma classe customizada, que será definida com o seguinte código:

[AsyncMethodBuilder(typeof(MyTaskBuilder<>))]
public class MyTask<TResult>
{
    //Define uma propriedade Task para indicar quando uma operação assincrona foi finalizada
    private Task<TResult> _task;

    //O resultado retornado pela classe
    private TResult _result;

    //Indica que a operação foi finalizada ou não
    public bool IsCompleted => _task == null || _task.IsCompleted;

    //Inicializando a classe com o resultado da operação bem sucedida
    public MyTask(TResult result){
        this._task = null;
        this._result = result;
    }

    //Obtém o "Awaiter" da classe
    public MyTaskAwaiter<TResult> GetAwaiter() => new MyTaskAwaiter<TResult>(this);

    //Retorna uma task, ou cria uma para o resultado
    public Task<TResult> AsTask() => _task ?? Task.FromResult(_result);

    //Retorna o resultado
    public TResult GetResult() =>
        _task == null ? 
            _result : 
            _task.GetAwaiter().GetResult();
}

Mesmo necessitando apenas de um método GetAwaiter, note que a classe definiu outros métodos e propriedades para facilitar o seu uso.

Por fim, é necessário definir o builder type para a classe acima, que deve ter o código abaixo:

public class MyTaskBuilder<TResult>
{
    private AsyncTaskMethodBuilder<TResult> _methodBuilder;
    private TResult _result;
    private bool _haveResult;
    private bool _useBuilder;

    //Cria o Buider Type
    public static MyTaskBuilder<TResult> Create() => new MyTaskBuilder<TResult>() { _methodBuilder = AsyncTaskMethodBuilder<TResult>.Create() };

    //Inicia o Buider Type
    public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { _methodBuilder.Start(ref stateMachine); }

    //Define o State Machine para o Buider Type
    public void SetStateMachine(IAsyncStateMachine stateMachine) { _methodBuilder.SetStateMachine(stateMachine); }

    //Define o resultado
    public void SetResult(TResult result)
    {
        if (_useBuilder)
        {
            _methodBuilder.SetResult(result);
        }
        else
        {
            _result = result;
            _haveResult = true;
        }
    }

    //Gera uma exceção
    public void SetException(Exception exception) => _methodBuilder.SetException(exception);

    //Retorna a task vinculado ao builder type
    public MyTask<TResult> Task {
        get {
            if (_haveResult)
            {
                return new MyTask<TResult>(_result);
            }
            else
            {
                _useBuilder = true;
                return new MyTask<TResult>(_methodBuilder.Task.Result);
            }
        }
    }

    //Define o comportamento quando for necessário aguardar a conclusão da operação assincronona
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine 
    {
        _useBuilder = true;
        _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
    }

    //Define o comportamento quando for necessário aguardar a conclusão da operação assincronona
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { 
        _useBuilder = true;
        _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
    }
}

Com a definição dessas três classes, podemos retornar o tipo MyTask em métodos assíncronos.

Utilizando o tipo assíncrono

O uso desta classe não difere do uso das classes Task ou Task em um método assíncrono:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Retorno: " + MyMethod().GetResult());
    }

    public static async MyTask<int> MyMethod(){
        var result = await MyMethodAsync();

        return result;
    }

    public static MyTask<int> MyMethodAsync()
    {
        return new MyTask<int>(5);
    }
}

Mesmo permitindo a criação de tipos assíncronos, criar um não é algo recomendado. Tudo que vimos acima só deve ser utilizado em casos específicos. Se o objetivo for apenas melhorar a performance da aplicação, basta substituir o tipo Task<T>, pelo ValueTask<T>.

ValueTask<T>

O tipo System.Threading.Tasks.ValueTask<T> foi adicionado na versão 7.0 do C#, como uma forma de superar os gargalos da classe Task<T>. Definido como uma estrutura, os objetos de ValueTask<T> não são salvos na memória heap, o que já contorna o problema com alocação de memória da classe Task<T>.

O seu uso é bem simples:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Retorno: " + MyMethod().Result);
    }

    public static async ValueTask<int> MyMethod(){
        var result = await MyMethodAsync();

        return result;
    }

    public static ValueTask<int> MyMethodAsync()
    {
        return new ValueTask<int>(5);
    }
}

E por não ser removida da memória pelo garbage collector, ela permite até a criação de “cache”:

class Program
{
    static void Main(string[] args)
    {
var task1 = CachedMethod();
        Console.WriteLine("Retorno da primeira chamada: " + task1.Result);

        var task2 = CachedMethod();
        Console.WriteLine("Retorno da segunda chamada: " + task2.Result);

        var task3 = CachedMethod();
        Console.WriteLine("Retorno da segunda chamada: " + task3.Result);
    }

    public static ValueTask<int> CachedMethod()
    {
        return (cache) ? new ValueTask<int>(cacheResult) : new ValueTask<int>(LoadCache());
    }
    private static bool cache = false;
    private static int cacheResult;
    private static async Task<int> LoadCache()
    {
        // simulando um processamento assincrono:
        await Task.Delay(100);
        cacheResult = new Random().Next(5000, 10000);
        cache = true;
        return cacheResult;
    }
}

A execução do código acima será:

Resultado da execução do código

Conclusão

Como recomendado na documentação, no C# 7.0, procure sempre utilizar a classe ValueTask<T>, pois o seu uso já se provou ser mais performático que a classe Task<T>.

C# (C Sharp) Básico
Curso de C# (C Sharp) Básico
CONHEÇA O CURSO
C# (C Sharp) Intermediário
Curso de C# (C Sharp) Intermediário
CONHEÇA O CURSO
C# (C Sharp) Avançado
Curso de C# (C Sharp) Avançado
CONHEÇA O CURSO

Analisando a cobertura de testes em aplicações .NET com o OpenCover

Quem trabalha com .NET geralmente utiliza o Visual Studio e, hoje, uma das deficiências de algumas versões desta ferramenta é a funcionalidade de avaliar a cobertura dos testes. Recurso este que encontra-se disponível apenas na versão Enterprise, e ele acaba não sendo utilizado na hora da implementação dos testes unitários.

Dependendo do tamanho da aplicação, não ter uma boa ferramenta de cobertura de teste pode dificultar e muito a análise das partes que ainda precisam ser testadas.

Para facilitar este processo para quem não possui a versão Enterprise do Visual Studio, temos a opção de utilizar o OpenCover, que é uma ferramenta open-source.

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

OpenCover

Como dito acima, o OpenCover é uma ferramenta open-source, que gera a cobertura de testes em projetos .NET. Infelizmente no momento ele só funciona no Windows, e não suporta projetos .NET Core.

Mesmo sendo uma ferramenta de linha de comando, ela pode ser baixada pelo NutGet ou como um arquivo MSI ou zip na página do projeto Github.

Para testá-la, criei um projeto simples, que possui apenas classe:

public class Exemplo
{
    public static void Metodo()
    {
        try
        {
            Console.WriteLine("Ok!");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

E um projeto de teste também com apenas uma classe:

using NUnit.Framework;

[TestFixture]
public class ExemploTest
{
    [Test]
    public void MetodoTest()
    {
        Exemplo.Metodo();
    }
}

Executando os testes com o OpenCover

O OpenCover não executa diretamente os testes, na verdade, ele precisa de outra aplicação que executa o teste, que no nosso caso será o NUnit.

Como o OpenCover irá executar o NUnit pelo console, também adcione na aplicação o NUnit Console.

Com isso, poderemos executar o comando abaixo:

C:ProjetosTreinawebTreinaweb.CalculadorapackagesOpenCover.4.6.519toolsOpenCover.Console.exe -target:"C:ProjetosTreinawebTreinaweb.CalculadorapackagesNUnit.ConsoleRunner.3.7.0toolsnunit3-console.exe" -targetargs:"C:ProjetosTreinawebTreinaweb.CalculadoraTreinaweb.Calculadora.TestsbinDebugTreinaweb.Calculadora.Tests.dll" -output:"C:ProjetosTestResultscoverage.xml" -register:user

Os argumentos utilizados acima são:

  • target: – Indica o processo que OpenCover irá executar.
  • targetargs: – Informa argumentos para o processo que o OpenCover irá executar. No caso, é passado a dll do projeto que contém os testes;
  • output: – O local onde o arquivo xml gerado será salvo;
  • register:user – Utilizado para registrar objetos COM que o OpenCover utiliza. A opções user deve ser utilizada por usuários sem privilégios administrativos.

Você pode ver outros comandos, na documentação do OpenCover aqui.

Ao executar o código acima, o OpenCover irá gerar um arquivo XML como resultado. Para visualizar este resultado em um ambiente mais amigável, pode ser utilizado o ReportGenerator.

ReportGenerator

Assim como o OpenCover, o ReportGenerator também é uma ferramenta de linha de comando open source, que pode ser baixada pelo NuGet. Ao baixá-la, pode ser executado o comando abaixo para gerar o relatório:

C:ProjetosTreinawebTreinaweb.CalculadorapackagesReportGenerator.3.0.2toolsReportGenerator.exe -reports:"C:ProjetosTestResultscoverage.xml" -targetdir:"C:ProjetosTestResultsReport"

Neste relatório podemos visualizar quais trechos de código não são cobertos:

Relatório do Report Generator

Conclusão

Agora o projeto tem code coverage e histórico, tudo com ferramentas open source. Com isso, é possível enxergar os pontos mais críticos que precisam de testes, entender a complexidade do código escrito com as métricas disponíveis e acompanhar a evolução do projeto.

Essas ferramentas facilitam muito a vida de quem desenvolve software, e é ótimo ver que a comunidade .NET está criando ferramentas open-source para resolver esses problemas.

Utilizar o Cake facilita ainda mais o uso e a integração delas, pra que você não precise perder muito tempo com isso e possa focar no que importa.

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

Automatizando tarefas de um projeto C# com o Cake

O desenvolvimento de uma aplicação pode ser um processo repetitivo: compilação, teste e execução/publicação. Vendo apenas esses pontos, o processo pode ser algo simples, mas ao longo do desenvolvimento, toda esta repetição pode ter consumido horas.

Quando nos deparamos com uma situação assim, chegou o momento de procuramos formas de automatizar este processo e economizarmos um esforço não útil para a aplicação.

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

Hoje em dia existem várias soluções de automação para essas tarefas repetitivas de um projeto. Neste artigo conheceremos o Cake.

Cake

O Cake (C# Make) é um sistema de automação, multiplataforma e open source, que permite definir das tarefas em C#, se tornando assim o sistema de automação ideal para o desenvolvedor desta linguagem.

Aliases e Extensibilidade

Como é voltado para aplicações da plataforma .NET, o Cake já define uma série de métodos que representam ações em aplicações desta plataforma. Por exemplo, para aplicar o restore em um projeto .NET Core, pode ser definido a tarefa abaixo:

Task("Restore")
   .Does(() =>
    {
        DotNetCoreRestore("./ExemploCake.csproj");
    });

O método DotNetCoreRestore usado acima é chamado de “alias”, e faz parte dos alises de DotNetCore. Existem aliases para uma infinidade de tarefas, e você pode ver todos na documentação do Cake.

Caso não exista um alias nativo para a ação que queira executar, a plataforma também suporta os chamados Addins, que são métodos de extensão criados pela comunidade.

Se mesmo assim não encontrar um método para a sua tarefa, você pode criar seu próprio alias, que nada mais é que um método de extensão do C#. E se fizer isso, contribua com a comunidade disponibilizando ele no NuGet.

Agora que conhecemos o Cake, vamos ver um exemplo rápido do seu uso.

Colocando a mão na massa

Para este artigo vou testar o Cake uma aplicação console simples:

dotnet new console -n ExemploCake
cd ExemploCake

Nela o Cake pode ser adicionado com o comando do PowerShell abaixo, caso esteja no Windows:

Invoke-WebRequest https://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1

Ou o comando de terminal abaixo, caso esteja no Linux:

curl -Lsfo build.sh https://cakebuild.net/download/bootstrapper/linux

Ou caso esteja no Mac OS X:

curl -Lsfo build.sh https://cakebuild.net/download/bootstrapper/osx

Com isso, será adicionado um arquivo build.ps1 no Windows e build.sh nos ambientes Unix. Será este arquivo que executaremos quando as tarefas estiverem automatizadas.

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

Criando um script Cake

Com o Cake adicionado na aplicação, crie nela um arquivo chamado build.cake. Será neste arquivo que as tarefas devem ser adicionadas. Por exemplo:

var target = Argument("target", "Default");

Task("Default")
  .Does(() =>
{
  Information("Hello World!");
});

RunTarget(target);

A tarefa acima é simples, como é possível supor, ela apenas exibe o famoso "Hello World" no console/terminal quando o Cake for executado:

Windows

.uild.ps1

Linux / Mac OS X

# Agusta a permissão
chmod +x build.sh

# Executa o arquivo.
./build.sh

No código, a primeira linha:

var target = Argument("target", "Default");

Define a tarefa padrão, caso alguma não seja definida durante a execução, que pode ser feito da seguinte forma:

./build.sh -target Default

Por ser código C#, podemos definir elementos que são válidos nesta linguagem com uma variável:

var target = Argument("target", "Default");

var projectFile = "./ExemploCake.csproj";

Task("Default")
  .IsDependentOn("Restore-Application")
  .Does(() =>
{
  Information("Hello World!");
});

Task("Restore-Application")
  .Does(() =>
{
  DotNetCoreRestore(projectFile);
});

RunTarget(target);

Além de definirmos uma variável:

var projectFile = "./ExemploCake.csproj";

Também foi definido que antes de executar a tarefa “Default“:

Task("Default")
  .IsDependentOn("Restore-Application")
  .Does(() =>
{
  Information("Hello World!");
});

Deve ser executa a tarefa “Restore-Application“, que aplica o restore na aplicação:

Task("Restore-Application")
  .Does(() =>
{
  DotNetCoreRestore(projectFile);
});

Pode-se cascatear uma tarefa após a outra, quantas verses for necessário:

var target = Argument("target", "Default");

var projectFile = "./ExemploCake.csproj";

Task("Default")
  .IsDependentOn("Run-Application")
  .Does(() =>
{
  Information("Hello World!");
});

Task("Restore-Application")
  .Does(() =>
{
  DotNetCoreRestore(projectFile);
});


Task("Run-Application")
  .IsDependentOn("Restore-Application")
  .Does(() =>
{
  DotNetCoreRun(projectFile);
});

RunTarget(target);

Conclusão

Agora que a configuração do Cake está definida e que já temos uma estrutura básica, ele pode ser expandido com adição de novas tarefas, como aplicação de teste e ou commit dos códigos alterados.

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

Caso tenha gostado da ferramenta, você pode ver mais funcionalidades dela na sua ótima documentação.

Utilizando o Micro ORM Dapper em uma aplicação ASP.NET Core

Persistir informações da aplicação é um requisito básico de quase todos os sistemas desenvolvidos atualmente. E o meio mais utilizado para isso são os bancos de dados relacionais.

Porém, o processo de persistência de dados em um banco de dados relacional não é simples, são necessárias uma gama de configurações, além de certas adaptações para que os objetos sejam persistidos corretamente no banco.

Para facilitar este processo, foram criados os frameworks ORM (Object-Relational Mapping – mapeamento objeto relacional). Eles se encarregam do trabalho pesado, como o processo de conversão dos objetos da aplicação em informações compreendidas pelo banco de dados (códigos SQL), e abstraem o máximo do acesso ao banco para facilitar a persistência de dados. Com isso, o desenvolvedor pode focar no que é mais importante para a aplicação.

Só que, com a facilidade, esses frameworks ORM acabaram comprometendo um pouco da performance das aplicações. Para resolver este problema, criou-se os chamados micro frameworks ORM. Este tipo abstrai apenas o necessário e otimiza ao máximo. Um dos frameworks que se destacam nesta categoria é o Dapper.NET.

Criado pela equipe por atrás do Stack Overflow, este micro framework ORM foi criado visando rapidez, fácil integração e liberdade de configuração. Com poucas linhas de código é possível adicioná-lo em uma aplicação e já fazer uso de todo o seu poder.

Para se ter ideia da sua rapidez e das características que o caracterizam, podemos ver a comparação que a sua equipe realiza com outros frameworks ORM disponíveis para .NET:

DaVinci Resolve - OFX Filtros e Efeitos
Curso de DaVinci Resolve - OFX Filtros e Efeitos
CONHEÇA O CURSO

Performance de uma pesquisa com 500 iterações – POCO serialization

MethodDurationRemarks
Hand coded (using a SqlDataReader)47ms
Dapper ExecuteMapperQuery49ms
ServiceStack.OrmLite (QueryById)50ms
PetaPoco52msCan be faster
BLToolkit80ms
SubSonic CodingHorror107ms
NHibernate SQL104ms
Linq 2 SQL ExecuteQuery181ms
Entity framework ExecuteStoreQuery631ms

Performance de uma pesquisa com 500 iterações – dynamic serialization

MethodDurationRemarks
Dapper ExecuteMapperQuery (dynamic)48ms
Massive52ms
Simple.Data95ms

Performance de uma pesquisa com 500 iterações – typical usage

MethodDurationRemarks
Linq 2 SQL CompiledQuery81msNot super typical involves complex code
NHibernate HQL118ms
Linq 2 SQL559ms
Entity framework859ms
SubSonic ActiveRecord.SingleOrDefault3619ms

Para exemplificar o seu uso, vamos ver um exemplo dele em uma aplicação ASP.NET Core.

Criando a aplicação

No terminal digite o código abaixo para criar uma aplicação chamada AspNetCoreDapper:

dotnet new mvc -n AspNetCoreDapper

Agora, adicione o pacote do Dappper:

dotnet add package Dapper

Não se esqueça de aplicar o restore no projeto:

dotnet restore

Com isso já podemos começar a nossa configuração Dapper.

Criando o model / entidade

Para este exemplo será utilizada a entidade abaixo:

using System;
using System.ComponentModel.DataAnnotations;

namespace AspNetCoreDapper.Models
{
    public class Product
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }
        public int Quantity { get; set; }

        [Required]
        public double Price { get; set; }
    }
}

Configurando o acesso ao banco com Dapper

Para este exemplo vou utilizar como banco de dados o SQLite, assim, antes de configurar o Dapper é necessário adicionar o pacote deste banco de dados na aplicação:

dotnet add package Microsoft.Data.Sqlite

Aplique o restore para iniciarmos o processo de configuração do banco.

Diferente de outros frameworks ORM, o Dapper não define uma classe de configuração. Assim, configuraremos o acesso dele na classe base dos repositórios:

namespace AspNetCoreDapper.Repositories
{
    public abstract class AbstractRepository<T>
    {
        private string _connectionString;
        protected string ConnectionString => _connectionString;
        public AbstractRepository(IConfiguration configuration){
            _connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");

        }
        public abstract void Add(T item);
        public abstract void Remove(int id);
        public abstract void Update(T item);
        public abstract T FindByID(int id);
        public abstract IEnumerable<T> FindAll();
    }
} 

O Dapper também não cria o banco de dados de acordo com as entidades. Nele, caso o banco ainda não esteja criado, ele deve ser criado “na mão”. Que é o que iremos fazer na classe abaixo:

namespace AspNetCoreDapper.Db
{
    public class Seed
    {
        private static IDbConnection _dbConnection;

        private static void CreateDb(IConfiguration configuration)
        {
            var connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");
            var dbFilePath = configuration.GetValue<string>("DBInfo:ConnectionString");
            if (!File.Exists(dbFilePath))
            {
                _dbConnection = new SqliteConnection(connectionString);
                _dbConnection.Open();

                // Create a Product table
                _dbConnection.Execute(@"
                    CREATE TABLE IF NOT EXISTS [Product] (
                        [Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
                        [Name] NVARCHAR(128) NOT NULL,
                        [Quantity] INTEGER NULL,
                        [Price] NUMERIC NOT NULL
                    )");

                _dbConnection.Close();
            }

        }
    }
}

E ele deve ser chamado na classe AbstractRepository:

public AbstractRepository(IConfiguration configuration){
    _connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");

    AspNetCoreDapper.Db.Seed.CreateDb(configuration);
}

Para finalizar a configuração de acesso Dapper, iremos definir o repositório abaixo:

namespace AspNetCoreDapper.Repositories
{
    public class ProductRepository: AbstractRepository<Product>
    {
        public ProductRepository(IConfiguration configuration): base(configuration) { }

        public override void Add(Product item)
        {
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "INSERT INTO Product (Name, Quantity, Price)"
                                + " VALUES(@Name, @Quantity, @Price)";
                dbConnection.Open();
                dbConnection.Execute(sQuery, item);
            }
        }
        public override void Remove(int id)
        {
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "DELETE FROM Product" 
                            + " WHERE Id = @Id";
                dbConnection.Open();
                dbConnection.Execute(sQuery, new { Id = id });
            }
        }
        public override void Update(Product item)
        {
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "UPDATE Product SET Name = @Name,"
                            + " Quantity = @Quantity, Price= @Price" 
                            + " WHERE Id = @Id";
                dbConnection.Open();
                dbConnection.Query(sQuery, item);
            }
        }
        public override Product FindByID(int id)
        { 
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                string sQuery = "SELECT * FROM Product" 
                            + " WHERE Id = @Id";
                dbConnection.Open();
                return dbConnection.Query<Product>(sQuery, new { Id = id }).FirstOrDefault();
            }
        }
        public override IEnumerable<Product> FindAll()
        { 
            using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
            {
                dbConnection.Open();
                return dbConnection.Query<Product>("SELECT * FROM Product");
            }
        }
    }
}

Note que o Dapper realiza o processo de impedância, a transformação dos dados do banco para entidades da aplicação de forma quase transparente:

dbConnection.Query<Product>("SELECT * FROM Product");

Na busca basta informar a classe que mapeia os dados do banco que o framework se encarregará de transformar os resultados obtidos em objetos desta classe. Aqui é importante que os nomes das colunas da tabela sejam iguais aos nomes das propriedades da classe, pois o Dapper irá realizar a transformação por Reflection.

Este mesmo processo é aplicado quando um dado é enviado para o banco:

using (IDbConnection dbConnection = new SqliteConnection(ConnectionString))
{
    string sQuery = "INSERT INTO Product (Name, Quantity, Price)"
                    + " VALUES(@Name, @Quantity, @Price)";
    dbConnection.Open();
    dbConnection.Execute(sQuery, item);
}

Também repare que é necessário informar o código SQL, pois este é um recurso dos frameworks ORM tradicionais que o Dapper não implementa. Assim, é possível definir um código mais eficiente, obtendo ainda mais performance.

Testando o Dapper

Para testar o Dapper, crie um controller:

namespace AspNetCoreDapper.Repositories
{
    public class ProductController : Controller
    {
        private readonly ProductRepository productRepository;

        public ProductController(IConfiguration configuration){
            productRepository = new ProductRepository(configuration);
        }

        // GET: Products
        public ActionResult Index()
        {
            return View(productRepository.FindAll().ToList());
        }

        // GET: Products/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

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

        // POST: Products/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Add(product);
                return RedirectToAction("Index");
            }

            return View(product);
        }

        // GET: Products/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Update(product);
                return RedirectToAction("Index");
            }
            return View(product);
        }

        // GET: Products/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            productRepository.Remove(id);
            return RedirectToAction("Index");
        }
    }
}

Também crie as views as actions acima e então podemos ver o sistema funcionando:

Você pode fazer download dos sources dessa aplicação clicando aqui.

Até a próxima!

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

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.

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

Binary Literals, Digit Separators e Throw Expressions no C# 7.0

Dando continuidade à série de artigos sobre novidades do C# 7, hoje conheceremos três novos recursos que melhoram a legibilidade e reduzem o número de linhas de código:

  • Binary Literals;
  • Digit Separators;
  • Throw Expressions.
C# (C Sharp) Básico
Curso de C# (C Sharp) Básico
CONHEÇA O CURSO

Binary Literals

Este recurso seria adicionado no C# 6.0 mas, no final, foi decidido que só entraria na versão 7. Como o nome indica, ele permite declarar valores binários como literais.

Para fazer isso, basta utilizar o prefixo 0b:

byte var1 = 0b00011011;
byte var2 = 0b10101010;
ushort var3 = 0b111001011010000;
Console.WriteLine($"{var1:X}");
Console.WriteLine($"{var2:X}");
Console.WriteLine($"{var3:X}");

Ele torna mais simples trabalhar com valores binários no código. Mesmo não sendo algo muito comum, agora não é mais necessário trabalhar com as versões em decimais dos valores binários. O que melhora a legibilidade do código.

Digit Separators

Ainda no quesito de melhorar a legibilidade, agora na versão 7, o C# permite que se utilize o underline (_) como digito separador de um valor numérico literal.

No lugar de definir um valor assim:

int var1 = 1234567891011;

Ele pode ser definido da seguinte forma:

int var1 = 1_234_567_891_011;

Isso também pode ser aplicado a valores binários:

ushort var1 = 0b0001_1011_1100_0001;

Ou hexadecimais:

ulong l2 = 0xfedc_ba98_7654_3210;

E o digito separador tem importância apenas visual, portanto, ele pode ser definido de qualquer forma no valor que será interpretado da mesma forma pelo C#:

int var1 = 1_2345_6789_1011;
ushort var1 = 0b000_110_111_100_000_1;
ulong l2 = 0xfe_dc_ba_98_76_54_32_10;

Throw Expressions

Por fim, temos um recurso chamado throw expressions, que permite reduzir a quantidade de linhas do código.

Até a versão 6, quando era necessário gerar uma exceção a partir do valor de um retorno, era necessário realizar o procedimento abaixo:

public class Pessoa
{
    private int _idade;
 
    public int Idade
    {
        get
        {
            return _idade;
        }
        set
        {
            if (value > 0)
                _idade = value;
            else
                throw new Exception("Idade inválida!");
        }
    }
}

Como a cláusula throw não podia ser utilizada em expressões que retornam valores, o seu uso em situações acima, estava atrelado a uma condicional. Mas, na versão 7.0, com o recurso throw expression, é possível lançar exceções a partir do código que define uma expressão. Assim, o mesmo código acima pode ser refeito para:

public class Pessoa
{
    private int _idade;
 
    public int Idade
    {
        get
        {
            return _idade;
        }
        set
        {
            _idade = value > 0 ? value : throw new Exception("Idade inválida!");
        }
    }
}

Teremos o mesmo resultado do código anterior.

Este recurso pode ser combinado com o recurso Expression-bodied Members, para gerar um resultado ainda mais enxuto:

public class Pessoa
{
    private int _idade;
 
    public int Idade
    {
        get => _idade;
        set => _idade = value > 0 ? value : throw new Exception("Idade inválida!");
    }
}

Conclusão

Apesar de serem recursos que modificam pequenos detalhes da linguagem, seus usos são recomendados pois melhoram a qualidade do código. Então, sempre que possível procure utilizá-los.

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

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.