C# 6 PROGRAMAÇÃO COM PRODUTIVIDADE
Luís Abreu Paulo Morgado
FCA – Editora de Informática, Lda www.fca.pt
EDIÇÃO FCA – Editora de Informática, Lda. www.fca.pt Copyright © 2016, FCA – Editora de Informática, Lda. ISBN eBook: 978-972-722-835-5 1.ª edição eBook: abril 2016 Capa: José Manuel Ferrão – Look-Ahead Todos os nossos livros passam por um rigoroso controlo de qualidade, no entanto aconselhamos a consulta periódica do nosso site (www.fca.pt) para fazer o download de eventuais correções. Não nos responsabilizamos por desatualizações das hiperligações presentes nesta obra, que foram verificadas à data de publicação da mesma. Os nomes comerciais referenciados neste livro têm patente registada.
Reservados todos os direitos. Esta publicação não pode ser reproduzida, nem transmitida, no todo ou em parte, por qualquer processo eletrónico, mecânico, fotocópia, digitalização, gravação, sistema de armazenamento e disponibilização de informação, sítio Web, blogue ou outros, sem prévia autorização escrita da Editora.
ÍNDICE GERAL OS AUTORES AGRADECIMENTOS 0. INTRODUÇÃO 0.1. O que posso encontrar neste livro 0.2. Requisitos 0.3. A quem se dirige este livro 0.4. Convenções 0.5. Organização do livro 0.5.1. Capítulo 1: Simplificação 0.5.2. Capítulo 2: Inicialização de Propriedades e Índices 0.5.3. Capítulo 3: Strings 0.5.4. Capítulo 4: Exceções 0.5.5. Capítulo 5: .NET Compiler Platform (“Roslyn”) 0.6. Suporte 1. SIMPLIFICAÇÃO 1.1. Introdução 1.2. Membros com corpo de expressão (expression bodied members) 1.2.1. Onde podemos utilizá-los? 1.3. Importação de métodos estáticos (using static) 1.3.1. Métodos de extensão 1.3.2. Enumerações 1.4. Operadores null-conditional 1.4.1. Delegates e eventos 1.4.2. IDisposable e afins 1.4.3. Encadeamento em expressões 1.4.4. Uso com índices 1.4.5. Value types
1.4.6. Considerações finais Conclusão Bibliografia 2. INICIALIZAÇÃO DE PROPRIEDADES E ÍNDICES 2.1. Introdução 2.2. Inicialização de propriedades implementadas automaticamente 2.2.1. Propriedades apenas de leitura implementadas automaticamente 2.2.2. Structs 2.3. Inicialização de índices 2.3.1. Métodos de extensão na inicialização de coleções Conclusão Bibliografia 3. STRINGS 3.1. Operador nameof 3.1.1. Nomes qualificados 3.1.2. Data binding 3.1.3. Observações finais 3.2. Strings interpoladas 3.2.1. Localização das formatações 3.2.2. Cenários avançados 3.2.3. Strings interpoladas com versões anteriores Conclusão Bibliografia 4. EXCEÇÕES 4.1. Instruções await em blocos catch e finally 4.2. Filtros de exceções (exception filters) 4.2.1. Stack unwinding Conclusão Bibliografia
5. “ROSLYN” 5.1. Introdução 5.2. Áreas funcionais 5.3. Camadas da “Roslyn” 5.3.1. Sintaxe 5.3.1.1. Nós de sintaxe 5.3.1.2. Tokens 5.3.1.3. Trivia 5.3.1.4. Caraterísticas comuns 5.3.2. Semântica 5.3.2.1. Compilation 5.3.2.2. Symbol 5.3.2.3. Modelo semântico 5.4. Syntax visualizer 5.5. “Roslyn” na prática 5.5.1. Navegação através de propriedades 5.5.2. Métodos de pesquisa 5.5.3. Manipulação da árvore 5.5.4. Compilação 5.6. Observações finais Conclusão Bibliografia ÍNDICE REMISSIVO COMPLEMENTOS NA WEB
OS AUTORES Luís Abreu – Licenciado em Engenharia de Sistemas e Computadores pela Universidade da Madeira. Utilizador regular da plataforma .NET desde 2002. Ao longo dos últimos anos, participou em vários projetos Web e contribuiu com vários artigos para o site do grupo português PontoNetPT. Atualmente, trabalha na SRA, onde é um dos responsáveis pela arquitetura e desenvolvimento de aplicações. É MVP ASP.NET desde outubro de 2005. A partir de 2006, passou a ser o único português a integrar o grupo internacional ASP Insiders (um grupo de elite que mantém contactos regulares com a equipa da Microsoft que desenvolve a plataforma ASP.NET). Autor dos seguintes livros editados pela FCA: ASP.NET 4.5, AJAX com ASP.NET, Silverlight 4.0 e Desenvolvimento em Windows 8, da coleção Curso Completo; HTML5 (4.a Ed. At. e Aum.), ASP.NET MVC, JavaScript (2.a Ed. At.), LINQ com C# (coautor), ASP.NET 4.5.1 e JavaScript 6, da coleção MyTI. Paulo Morgado – Bacharel em Eletrónica e Telecomunicações (Sistemas Digitais) pelo Instituto Superior de Engenharia de Lisboa e Licenciado em Engenharia Informática pela Faculdade de Ciências e Tecnologia da Universidade Nova de Lisboa. Utilizador regular da plataforma .NET desde 2003, tendo desenhado e desenvolvido as mais variadas aplicações. Tem artigos publicados em sites Web da especialidade em Portugal e no estrangeiro. Desde 2003, recebe da Microsoft o prémio “Most Valuable Professional” em competências relacionadas com a linguagem de programação C# e com a plataforma .NET. Coautor do livro LINQ com C#, da coleção MyTI, editado pelo FCA.
Este livro é dedicado à minha esposa, Marina. Luís Abreu
Este livro é dedicado à minha esposa, Eugénia, e ao meu filho, Carlos. Paulo Morgado
AGRADECIMENTOS Em primeiro lugar, quero agradecer à minha esposa, Marina, pelo apoio fornecido durante todo o processo de escrita do livro. Quero ainda aproveitar esta oportunidade para agradecer ao meu amigo Paulo Morgado por ter aceitado o desafio de participar na criação deste livro. Não posso deixar de referir a participação do João Paulo Carreiro e da Sara Silva. As suas revisões, contribuições técnicas e opiniões sinceras tiveram uma influência positiva no resultado final. Finalmente, queria também agradecer à Ana Correia e à Laura Faia, da FCA, pela disponibilidade demonstrada ao longo do projeto. Luís Abreu Em primeiro lugar, quero agradecer à minha esposa, Eugénia, e ao meu filho, Carlos, por todo o apoio e incentivos. Quero ainda aproveitar esta oportunidade para agradecer ao meu amigo Luís Abreu, pelo desafio para participar na criação deste livro, e à Sara Silva, pela sua revisão e comentários. Finalmente, queria também agradecer à Ana Correia e à Laura Faia, da FCA, pela disponibilidade demonstrada ao longo do projeto. Paulo Morgado
0
INTRODUÇÃO
Com o passar dos anos, o C# assumiu-se como a linguagem da plataforma .NET. O lançamento de cada nova versão cimentou esta posição e forneceu-nos cada vez mais funcionalidades que contribuíram para simplificar e reduzir o trabalho relacionado com a escrita de código em .NET. As novidades do C# 6 podem não ser tão impressionantes como as introduzidas pelos genéricos ou pelo Language Integrated Query (LINQ), mas não deixam de ser importantes para aumentar a eficiência do programador.
0.1
O QUE POSSO ENCONTRAR NESTE LIVRO?
Neste livro, são apresentadas as principais caraterísticas e funcionalidades introduzidas pela versão 6 da linguagem C#. As novidades introduzidas pela última versão da linguagem aplicam-se a várias áreas e foram concebidas com um único objetivo: contribuir para aumentar a produtividade e reduzir código necessário à concretização de determinados cenários, sem que isso implicasse uma perda de legibilidade do código final. Para além das novidades introduzidas ao nível da sintaxe da linguagem, o livro efetua ainda uma breve apresentação à nova plataforma “Roslyn” (oficialmente designada por .NET Compiler Platform). Com esta
plataforma, o programador passa a poder aceder a um conjunto de serviços que, até hoje, apenas podiam ser utilizados pelos compiladores da plataforma .NET.
0.2
REQUISITOS
O livro foi escrito para permitir que a aprendizagem das novas funcionalidades da linguagem possa ser feita sem que o leitor tenha de estar sentado em frente de um computador (PC). Todos os exemplos apresentados no livro podem ser obtidos a partir do site da editora (http://www.fca.pt). Com exceção dos exemplos apresentados no Capítulo 5, cada um dos programas apresentado nos restantes capítulos está contido num único ficheiro, de forma a simplificar a sua compilação e execução. Na prática, isto significa que o leitor terá apenas de saber o caminho até ao compilador de C# (csc.exe) para poder testar cada um dos exemplos num PC – o compilador de C# faz parte do .NET SDK 4.6 (que pode ser obtido gratuitamente em http://www.microsoft.com/en-us/download/details.aspx?id=48130). Supondo que o leitor adicionou o caminho até ao compilador nas variáveis de sistema do computador (http://www.computerhope.com/issues/ch000549.htm), então a compilação de cada um dos exemplos poderá ser efetuada através de uma instrução semelhante à seguinte: csc ficheiro.cs Alternativamente, e caso o leitor decida instalar uma versão do Visual Studio 2015 (o que, como veremos, é recomendado para simplificar a execução dos exemplos apresentados no Capítulo 5), então também pode utilizar a janela interativa do C# (designada originalmente por C# Interactive Window), que é automaticamente instalada pelo Service Pack 1. Na prática, esta janela fornece-nos um REPL (read-eval-printloop) enriquecido com algumas funcionalidades avançadas de edição (por exemplo, Intellisense), que nos permitem explorar e analisar código sem que seja necessário proceder à criação de um projeto ou efetuar a respetiva
compilação. O leitor interessado pode obter mais informações sobre esta ferramenta em https://github.com/dotnet/roslyn/wiki/Interactive-Window. Para os exemplos apresentados no Capítulo 5, recomendamos o uso do Visual Studio 2015 Community Edition. Na prática, esta versão do Visual Studio disponibiliza as mesmas funcionalidades que podem ser encontradas na versão Professional, mas impõe algumas restrições no que diz respeito à sua utilização (por exemplo, não é permitido o seu uso no desenvolvimento de aplicações comerciais em organizações ou empresas que possuam mais de 250 PC ou que faturem mais de um milhão de dólares ao ano). Atualmente, esta versão do Visual Studio pode ser obtida gratuitamente a partir de http://aka.ms/vscommunity. O uso desta ferramenta simplifica os passos necessários ao desenvolvimento dos exemplos apresentados neste capítulo, pelo que recomendamos vivamente o seu uso.
0.3
A QUEM SE DIRIGE ESTE LIVRO?
Este livro é dirigido a todos aqueles que pretendem colocar-se rapidamente a par de todas as novidades introduzidas pela versão 6 da linguagem C#. O livro parte do pressuposto de que o leitor já tem alguns conhecimentos e experiência na escrita de código nesta linguagem, pelo que não são apresentadas quaisquer noções básicas ou introdução à mesma.
0.4
CONVENÇÕES
Ao longo deste livro, optou-se por seguir um conjunto de convenções que facilitam a interpretação do texto e do código apresentados. Assim, todos os excertos de código são apresentados no seguinte formato: var a = 10; Por sua vez, as notas ou observações importantes poderão ser encontradas no interior de uma secção semelhante à seguinte:
Nota importante Esta é uma nota ou observação importante.
0.5
ORGANIZAÇÃO DO LIVRO
Este livro agrupa as várias novidades introduzidas pela nova especificação em quatro capítulos (1-4), que podem ser lidos sequencialmente ou, se o leitor assim o preferir, alternadamente (isto é, sem respeitar a ordem de capítulos apresentada). A leitura não sequencial dos capítulos é possível, devido ao facto de todas as (poucas) dependências entre capítulos estarem devidamente identificadas.
0.5.1 CAPÍTULO 1: SIMPLIFICAÇÃO Este capítulo inicia a análise das novidades introduzidas pelo C# através da apresentação de três funcionalidades que, com toda a certeza, contribuirão para aumentar a eficiência do programador no dia a dia. Assim, começamos por ver como podemos simplificar o código necessário à criação de membros através do uso dos chamados membros com corpo de expressão (expression bodied members). Em seguida, mostramos como a diretiva using static pode ser usada para reduzir o código utilizado na invocação de métodos estáticos. Finalmente, o capítulo encerra com uma das grandes novidades introduzidas pelo C# 6: estamos a referir-nos aos operadores null-conditional.
0.5.2 CAPÍTULO 2: INICIALIZAÇÃO DE PROPRIEDADES E ÍNDICES No segundo capítulo, apresentamos um par de novidades que, com toda a certeza, serão úteis ao leitor durante a criação e inicialização de propriedades e dicionários. Inicialização de propriedades implementadas automaticamente, propriedades de leitura implementadas automaticamente e inicialização de índices são os tópicos que serão detalhados em pormenor nas páginas deste capítulo.
0.5.3 CAPÍTULO 3: STRINGS No que diz respeito ao uso de strings, a linguagem introduz duas novas funcionalidades. Com o operador nameof, podemos remover praticamente todas as ocorrências das chamadas strings “mágicas” do nosso código. Para além desse operador, podemos ainda contar com as novas strings interpoladas para simplificar a forma como compomos strings nos nossos programas. Ambos os tópicos são apresentados detalhadamente neste capítulo.
0.5.4 CAPÍTULO 4: EXCEÇÕES As exceções foram outra das áreas que sofreram melhorias com o lançamento da nova versão da linguagem. Depois de muito esperar, os programadores de C# já podem utilizar expressões await no interior de blocos catch e finally. Para além disso, a linguagem passa também a suportar o uso dos chamados filtros de exceções (exception filters). Com a introdução destas funcionalidades, podemos escrever código assíncrono em qualquer secção de um método, e operações como, por exemplo, o logging podem ser efetuadas sem provocarmos o stack unwinding.
0.5.5 CAPÍTULO 5: .NET COMPILER PLATFORM (“ROSLYN”) Neste capítulo, apresentamos algumas das funcionalidades disponibilizadas pela nova plataforma “Roslyn”. Oficialmente designada por .NET Compiler Platform, a “Roslyn” passa a disponibilizar publicamente funcionalidades que até ao momento eram internas ao compilador de C#. Por outras palavras, esta plataforma permite-nos aceder e manipular as árvores sintáticas e semânticas obtidas a partir da compilação de código fonte. Na prática, isto significa que os programadores que necessitam de analisar e gerar código terão as suas vidas muito facilitadas com a introdução desta plataforma.
0.6
SUPORTE
Este livro foi com base na versão final da linguagem C# 6. Se, por acaso, o leitor encontrar informação que lhe pareça incorreta, ou tiver sugestões em relação ao conteúdo de alguma secção do livro, então não hesite e envie um email com as suas questões para labreu@gmail.com. Eventuais alterações e erratas serão publicadas na página do livro no site da editora em http://www.fca.pt.
1
SIMPLIFICAÇÃO
Com o lançamento do C# 6, a Microsoft introduziu um conjunto de novas funcionalidades que contribuem para simplificar o código e para aumentar a produtividade do programador. Neste capítulo, começamos por ilustrar as principais caraterísticas dos membros com corpo de expressão (expression bodied members) para, em seguida, apresentarmos algumas das funcionalidades associadas à importação de métodos estáticos (using static). Para encerrarmos o capítulo, reservámos uma secção que apresenta os novos operadores null-conditional.
1.1
INTRODUÇÃO
As novidades introduzidas pelo C# 6 não representam uma revolução semelhante à ocorrida aquando da introdução de genéricos ou do Language Integrated Query (LINQ). E isto porque a equipa decidiu concentrar-se na introdução de algumas funcionalidades que contribuem para aumentar a nossa eficiência no dia a dia e que consistem em “abrir” o compilador, fazendo com que este passe a ser visto como um fornecedor de serviços (e não como uma “caixa negra” que efetua apenas a compilação de programas – como acontecia até agora). Assim, e como veremos ao longo deste livro, a linguagem C# introduz novos atalhos para a definição de determinadas operações e reduz o código necessário para a concretização de vários cenários comuns. No final deste livro, e se tivermos feito
um bom trabalho, é bem possível que o leitor se interrogue acerca de como conseguiu sobreviver até hoje sem estas funcionalidades. A Figura 1.1 tenta resumir as principais novidades introduzidas pela versão 6 da linguagem. Neste capítulo, e como já referimos, concentramo-nos nas funcionalidades relacionadas com a simplificação do corpo dos métodos e das propriedades, com a importação de métodos estáticos (using static) e com os novos operadores null-conditional. No Capítulo 2, focamos a nossa atenção nas novidades relacionadas com a criação de DTO (Data Transfer Objects – nomeadamente, com as novidades associadas à definição e inicialização de propriedades). Por sua vez, no Capítulo 3, encarregamo-nos de apresentar todas as novidades inerentes ao uso da interpolação de strings. No Capítulo 4, analisamos duas novas funcionalidades relacionadas com o uso e tratamento de exceções (estamos a falar do uso de instruções await no interior de blocos catch e finally e dos novos exception filters). Finalmente, deixamos para o Capítulo 5 uma análise de algumas das potencialidades associadas ao uso da nova plataforma “Roslyn”.
FIGURA 1.1 – Resumo das principais novidades introduzidas pelo C# 6
1.2
MEMBROS COM CORPO DE EXPRESSÃO (EXPRESSION BODIED MEMBERS)
Com o passar dos anos, o compilador C# tornou-se cada vez mais “inteligente”, tendo cada nova versão da linguagem contribuído para reduzir o número de linhas de código necessário à concretização de determinadas operações. Por exemplo, se nos concentrarmos na definição de propriedades, é notória a evolução ocorrida ao longo dos anos. Analisemos, por exemplo, o excerto escrito em C# 1.0 que é apresentado em seguida (cap01/ex1.cs): public class Retangulo {
private double _lado; private double _altura; public Retangulo(double lado, double altura) { _lado = lado; _altura = altura; } public double Lado { get { return _lado; } } public double Altura { get { return _lado; } } public double Area { get { return _altura * _lado; } } }
A classe Retangulo recorre a dois campos que são usados para guardar, respetivamente, os valores correspondentes à altura e ao lado de um retângulo. O valor da área do retângulo pode ser recuperado através do acesso à propriedade de leitura Area. As versões posteriores da linguagem introduziram funcionalidades que nos permitem simplificar o código usado no exemplo anterior. Neste caso, podemos, por exemplo, combinar o uso de propriedades implementadas automaticamente com a definição de diferentes graus de acesso para getters e setters. Esta combinação permite-nos omitir a declaração dos campos internos usados pela classe, conforme é possível verificar através do excerto seguinte (cap01/ex2.cs): public class Retangulo { public Retangulo(double lado, double altura) { Lado = lado; Altura = altura; } public double Lado { get; private set; }
public double Altura { get; private set; } public double Area { get { return Altura * Lado; } } }
As alterações apresentadas no excerto anterior permitiram-nos reduzir o código escrito, sem quaisquer implicações para os eventuais consumidores do tipo Retangulo (repare-se como, à semelhança do que acontecia no excerto inicial, a classe continua apenas a disponibilizar três propriedades de leitura). As propriedades implementadas automaticamente continuam a ser suportadas por campos, já que são eles os verdadeiros responsáveis pelo armazenamento dos dados. Contudo, a introdução deste tipo de propriedade permitiu-nos passar para o compilador a responsabilidade (e o trabalho repetitivo!) de criação dos campos. Este tipo de comportamento é facilmente comprovado se recorrermos a uma ferramenta como, por exemplo, o dotPeek (https://www.jetbrains.com/decompiler/) para analisar o código gerado pelo compilador: namespace LivroCSharp { using System; using System.Diagnostics; using System.Runtime.CompilerServices; public class Retangulo { [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] private double <Altura>k__BackingField; [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] private double <Lado>k__BackingField; public double Lado {
[CompilerGenerated] get { return this.<Lado>k__BackingField } [CompilerGenerated] private set { this.<Lado>k__BackingField = value; } } public double Altura { [CompilerGenerated] get { return this.<Altura>k__BackingField; } [CompilerGenerated] private set { this.<Altura>k__BackingField = value; } } public double Area { get { double num; num = this.Altura * this.Lado; return num; } } public Retangulo(double lado, double altura)
{ this.Lado = lado; this.Altura = altura; } } }
Será que com o C# 6 podemos reduzir ainda mais o código escrito neste tipo de cenário? Felizmente para nós, a resposta é sim. Se o desejarmos, podemos simplificar o código anterior, nomeadamente no que diz respeito à definição da propriedade de leitura, através do uso de uma nova funcionalidade designada por expression bodied members. O excerto seguinte mostra como podemos reduzir o código necessário à criação de uma propriedade de leitura através do uso desta nova sintaxe (cap01/ex3.cs): public class Retangulo { public Retangulo(double lado, double altura) { Lado = lado; Altura = altura; } public double Lado { get; private set; } public double Altura { get; private set; } public double Area => Altura * Lado; }
À primeira vista, este tipo de expressão assemelha-se muito às das expressões Lambda (que foram introduzidas pela versão 3.0 da linguagem). Repare-se como praticamente todo o código inerente à definição da propriedade (termo get e pares de chavetas) deixa de ser necessário quando recorremos a esta nova sintaxe para a definirmos.
Nesta altura, o leitor poderá estar a interrogar-se acerca de eventuais penalizações a nível de performance inerentes à definição de propriedades de leitura através desta nova sintaxe. Como é possível verificar através do excerto seguinte, não existem quaisquer penalizações, já que o uso deste tipo de expressão gera código C# semelhante ao que foi apresentado inicialmente: namespace LivroCSharp { using System; using System.Diagnostics; using System.Runtime.CompilerServices; public class Retangulo { [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] private double <Altura>k__BackingField; [CompilerGenerated, DebuggerBrowsable(DebuggerBrowsableState.Never)] private double <Lado>k__BackingField; public double Altura { [CompilerGenerated] get { return this.<Altura>k__BackingField; } [CompilerGenerated] private set { this.<Altura>k__BackingField = value; } } public double Area
{ get { return (this.Altura * this.Lado); } } public double Lado { [CompilerGenerated] get { return this.<Lado>k__BackingField; } [CompilerGenerated] private set { this.<Lado>k__BackingField = value; } } public Retangulo(double lado, double altura) { this.Lado = lado; this.Altura = altura; } } }
Portanto, esta sintaxe alternativa limita-se a reutilizar a sintaxe caraterística das expressões Lambda, sem resultar na criação de nenhum delegate.
1.2.1 ONDE PODEMOS UTILIZÁ-LOS? Este tipo de expressões pode ser usado na simplificação de propriedades de leitura simples como a apresentada no exemplo anterior, em que o corpo da propriedade é definido à custa de uma expressão simples. Nesta altura, não podemos recorrer a estas expressões para implementar propriedades de escrita (ou seja, os setters não podem ser implementados através de uma sintaxe semelhante a esta). Todos os métodos constituídos por expressões simples (tipicamente, estamos a falar de métodos que contêm apenas uma linha) também podem ser definidos através desta sintaxe. Para ilustrar este cenário, vamos efetuar o override do método ToString do tipo Retangulo (cap01/ ex4.cs) para permitir a leitura dos valores usados no lado e na altura do retângulo: public class Retangulo { public Retangulo(double lado, double altura) { Lado = lado; Altura = altura; } public double Lado { get; private set; } public double Altura { get; private set; } public double Area => Altura * Lado; public override string ToString() => string.Format("{0} x {1} = {2}", Altura, Lado, Area); }
Uma vez mais, convém salientar que a sintaxe apresentada no exemplo anterior não introduz qualquer tipo de penalização a nível de
performance, representando apenas uma nova forma compacta de escrever o método seguinte: public override string ToString() { return string.Format("{0} x {1} = {2}", Altura, Lado, Area); }
Outra das questões que poderá estar a incomodar o leitor experiente prende-se com o facto de este novo tipo de expressões poder levar a uma diminuição da legibilidade do código. Afinal de contas, o que impede alguém de recorrer a uma expressão deste tipo para implementar um método com várias instruções? Para ilustrar este ponto, vamos modificar a implementação do método ToString para o seguinte (cap01/ ex5.cs): public override string ToString() => { //supor que temos diversas instruções no corpo do método return string.Format("{0} x {1} = {2}", Altura, Lado, Area); }
Se tentarmos compilar o excerto anterior, obtemos um erro de compilação, que é apresentado no excerto seguinte (erro de compilação associado ao uso de expression bodied members com corpo). Nesta altura, importa ainda referir que este tipo de expressões também pode ser utilizado na definição do corpo de um operador personalizado (se bem que, neste cenário, estas expressões tendem a não ser muito usadas devido ao facto de este tipo de elementos não ser tipicamente representado por expressões simples).
> csc ex6.cs Microsoft (R) Visual C# Compiler version 1.1.0.51109 Copyright (C) Microsoft Corporation. All rights reserved. ex6.cs(17,46): error CS1525: Invalid expression term '{' ex6.cs(17,46): error CS1002: ; expected ex6.cs(17,46): error CS1519: Invalid token '{' in class, struct, or interface member declaration ex6.cs(18,27): error CS1519: Invalid token '+' in class, struct, or interface member declaration ex6.cs(18,40): error CS1519: Invalid token '+' in class, struct, or interface member declaration ex6.cs(18,52): error CS1519: Invalid token ';' in class, struct, or interface member declaration ex6.cs(30,1): error CS1022: Type or namespace definition, or end-of-file expected
Portanto, o objetivo fundamental deste novo tipo de expressões é simplificar a escrita de métodos pequenos (tipicamente, métodos definidos por uma expressão), e não introduzir uma nova sintaxe para a escrita de métodos (o que, como vimos, tenderia a contribuir para dificultar a legibilidade desses métodos). Uma vez que a melhoria da legibilidade é um dos objetivos fundamentais que norteou a introdução das várias funcionalidades do C# 6 descritas neste livro, não faria sentido introduzir uma nova funcionalidade que pudesse dificultar a leitura de código. Antes de encerrarmos esta secção, resta-nos referir que podemos recorrer a esta sintaxe para definir propriedades do tipo índice (usadas
normalmente para aceder a um item mantido numa coleção). O excerto seguinte recorre a esta sintaxe para efetuar a implementação de uma propriedade deste tipo que foi adicionada ao tipo Retangulo (cap01/ ex6.cs): public double this[string index] => string.Compare(index, "lado", StringComparison.CurrentCultureIgnoreCase) == 0 ? Lado : Altura;
Portanto, e em jeito de balanço, podemos afirmar que os membros com corpo de expressão (expression bodied members) são úteis quando estamos perante propriedades de leitura ou métodos simples, já que reduzem alguma da “cerimónia” típica associada à definição deste tipo de membros.
1.3
IMPORTAÇÃO DE MÉTODOS ESTÁTICOS (USING STATIC)
Até ao lançamento do C# 6, a diretiva using podia ser usada para atingir dois objetivos. Na sua forma mais usual, esta diretiva era utilizada para introduzir os tipos definidos num determinado namespace no âmbito atual, sem que esse tipo tivesse de ser qualificado pelo nome desse namespace. Para além disso, a diretiva também podia ser usada para criarmos um alias para um determinado tipo definido no interior de um namespace. O uso de um alias é útil quando possuímos dois tipos com o mesmo nome simplificado que foram definidos em namespaces diferentes. Sem aliases, não teríamos outro remédio que não utilizar o nome completo de cada um desses tipos para nos referirmos a eles. No excerto seguinte, ilustramos o uso da diretiva using em ambos os cenários (cap01/ex7.cs):
using System; //introduzir tipos do namespace using Consola = System.Console; //alias namespace LivroCSharp { class Exemplo { private static void Main() { Console.WriteLine("1"); //uso do tipo mantido no interior namespace Consola.WriteLine("2"); //usar alias } } }
No exemplo anterior, a primeira diretiva using é responsável pela introdução no âmbito global de todos os tipos definidos no interior do namespace System. Foi devido a ela que pudemos referir-nos diretamente ao tipo Console (Console.WriteLine) no interior do método Main sem que tivesse sido necessário utilizar o seu nome completo (System.Console.WriteLine). Por sua vez, a segunda diretiva apresentada (using Consola = System.Console;) é responsável pela criação de um alias. No exemplo anterior, isso quer dizer que o termo Consola passa a poder ser usado como sinónimo de System.Console. Com o C# 6, a diretiva passa ter um terceiro objetivo. Assim, esta diretiva passa a estar associada a uma nova funcionalidade, designada por using static, que nos permite aceder aos membros estáticos públicos de um tipo a partir do contexto atual sem que o nome desses membros tenha de ser precedido pelo nome do tipo que os define. A melhor forma de ilustrarmos o uso desta funcionalidade passa pela apresentação de um exemplo simples (cap01/ex8.cs):
using static System.Math; using static System.Console; namespace LivroCSharp { class Circulo { private readonly double _raio; public Circulo(double raio) { _raio = raio; } public double Raio { get { return _raio; } } public double CalculaArea() { return Raio * PI; } } class Exemplo { static void Main() { var circulo = new Circulo(2.0); WriteLine("Raio: {0}", circulo.CalculaArea()); //uso direto do método } } }
No excerto anterior, recorremos à diretiva using static para podermos aceder diretamente aos membros estáticos expostos pelos tipos System.Math e System.Console. A partir desta altura, e como é possível confirmar através do exemplo anterior, podemos referir-nos diretamente aos membros expostos por esses tipos sem termos de indicar
explicitamente o nome da classe onde eles são definidos (propriedade estática PI no caso do tipo System.Math e o método WriteLine no caso do tipo System.Console). No caso dos membros expostos pelo tipo System.Math, a utilização desta funcionalidade tende a não prejudicar a legibilidade do código, já que a probabilidade de existência de membros estáticos com o mesmo nome expostos por outros tipos é muito baixa (por outras palavras, neste caso é fácil identificarmos a origem dos métodos). O mesmo já não se pode dizer em relação ao uso do método WriteLine no excerto anterior. Será que estamos a referir-nos ao método exposto pelo tipo System.Console? Ou será que estamos a falar do método exposto pelo tipo StreamWriter? A resposta a estas perguntas só é possível depois de consultarmos a lista de importações existentes no código, uma vez que a legibilidade final do excerto anterior acabou por ser comprometida neste caso. Portanto, e como é possível aferir a partir do exemplo anterior, esta é uma funcionalidade que deve ser usada com algum cuidado, já que pode contribuir para dificultar a legibilidade do código. Como seria de esperar, o compilador obriga-nos a indicar o nome do tipo quando o método estático é disponibilizado por dois tipos diferentes que foram previamente importados através da diretiva using static. O excerto seguinte ilustra este tipo de cenário (cap01/ex9.cs): using System; using static System.IO.File; using static System.IO.Directory; namespace LivroCSharp { class Exemplo { static void Main()
{ var existe = Exists("file"); } } }
Neste caso, recorremos à diretiva using static para acedermos diretamente aos membros estáticos definidos pelos tipos File e Directory. Como ambos disponibilizam um método estático designado por Exists, quando tentamos compilar o programa anterior, obtemos automaticamente um erro (CS0121), que nos indica que a chamada do método Exists é ambígua (excerto seguinte): > csc ex10.cs Microsoft (R) Visual C# Compiler version 1.1.0.51109 Copyright (C) Microsoft Corporation. All rights reserved. ex10.cs(11,26): error CS0121: The call is ambiguous between the following methods or properties: 'File.Exists(string)' and 'Directory.Exists(string)'
Para solucionarmos este erro de compilação, podemos remover uma das diretivas using static ou então temos de indicar explicitamente o nome da classe que expõe o método Exists que queremos utilizar.
1.3.1 MÉTODOS DE EXTENSÃO Apesar dos métodos de extensão serem, por definição, estáticos, a verdade é que eles não são introduzidos no âmbito global quando aplicamos a diretiva using static à classe que os define. Esta restrição
justifica-se, em primeiro lugar, pelo facto de este tipo de método ter sido criado para ser usado como método de instância de um tipo. Para além disso, existe ainda outra questão que pode ser problemática na prática: muitos dos tipos que servem de suporte ao LINQ disponibilizam métodos de extensão que possuem exatamente o mesmo nome. Por exemplo, quer a classe Enumerable, quer a classe ParallelEnumerable definem métodos de extensão designados por Select. Portanto, se a linguagem permitisse a introdução deste tipo de métodos no âmbito global através da diretiva using static, acabaríamos por ter uma grande “congestão” de métodos e, muito provavelmente, um número elevado de invocações ambíguas. Apesar das restrições anteriores, a diretiva using static pode ser aplicada a tipos que definem métodos de extensão, mas com resultados diferentes dos obtidos quando aplicamos esta diretiva a um tipo que apenas disponibiliza métodos estáticos “tradicionais”. Neste caso, e para além de introduzir os métodos estáticos “tradicionais” no âmbito atual, a diretiva serve ainda para limitar os métodos que podem ser utilizados como métodos de instância do tipo estendido. O exemplo seguinte ilustra o uso desta diretiva neste cenário (cap01/ex10.cs): using System; using static LivroCSharp.Interior1.Demo1; namespace LivroCSharp { namespace Interior1 { public static class Demo1 { public static string Opcao(this string item) { return item + "Opção 1"; } }
public static class Demo2 { public static string Opcao(this string item) { return item + "Opção 2"; } } } class Exemplo { static void Main() { var str = "luis"; Console.WriteLine(str.Opcao()); } } }
No excerto anterior, o uso da diretiva using static permitiunos limitar os métodos de extensão disponíveis aos métodos definidos pela classe Demo1. Neste exemplo, a importação tradicional baseada em namespaces (using LivroCSharp.Interior) resultava num erro de compilação (método ambíguo) porque quer Demo1, quer Demo2 introduzem dois métodos com o mesmo nome e ambos são introduzidos no âmbito atual. Neste caso, o uso da diretiva using static é obrigatória e permite-nos dizer que queremos utilizar apenas os métodos de extensão definidos pelo tipo Demo1. Como é óbvio, teríamos o mesmo problema da ambiguidade se tivéssemos aplicado a diretiva simultaneamente aos tipos Demo1 e Demo2.