C#

Visualização de idioma do C# 6.0 A

Mark Michaelis

Quando você ler este artigo, a Build, conferência para desenvolvedores da Microsoft, terá terminado e os desenvolvedores estarão pensando em como reagir a tudo que foi apresentado: adotar imediatamente, assistir com um pouco de receio ou ignorar por enquanto. Para os desenvolvedores do .NET/C#, o anúncio mais importante foi, sem dúvida, o lançamento da nova versão do compilador C# ("Roslyn") como código-fonte aberto. Em relação às melhorias de linguagem. Mesmo que você não tenha planos de adotar imediatamente o C# vNext, o qual vou me referir daqui para frente de forma não oficial como C# 6.0, no mínimo, você deveria estar ciente dos seus recursos e tomar nota daqueles que poderiam valer a pena passar para os mesmos.

Neste artigo, vou me aprofundar nos detalhes do que está disponível agora no C# 6.0 no momento desta escrita (março de 2014) ou nos bits de código-fonte aberto para download a partir do endereço roslyn.codeplex.com. Vou me referir a isso como um lançamento único que chamarei de a Preview de Março. Os recursos específicos desta Preview de Março estão implantados inteiramente no compilador, sem qualquer dependência de um Microsoft .NET Framework atualizado ou tempo de execução. Isto significa que você pode adotar o C# 6.0 no seu desenvolvimento sem ter de atualizar o .NET Framework tanto para desenvolvimento ou implantação. De fato, instalar o compilador C# 6.0 a partir deste lançamento envolve pouco mais do que instalar uma extensão do Visual Studio 2013, o qual, por sua vez, atualiza os arquivos de destino do MSBuild.

Enquanto introduzo cada recurso do C# 6.0, você pode considerar o seguinte:

  • Haveria um meio razoável de codificar a mesma funcionalidade no passado, de modo que o recurso seja, principalmente, uma simplificação da sintaxe, isto é, um atalho ou uma abordagem simplificada? A filtragem de exceção, por exemplo, não possui um equivalente no C# 5.0, enquanto os construtores primários sim.
  • Este recurso está disponível na Preview de Março? A maioria dos recursos que vou descrever estão disponíveis, porém, alguns (como o novo binário literal) não.
  • Você tem algum comentário para fazer à equipe em relação ao novo recurso de linguagem? A equipe ainda é relativamente jovem em seu ciclo de vida de lançamentos e está muito interessada em ouvir suas opiniões sobre o lançamento (ver msdn.com/Roslyn para as instruções de feedback).

Pensar nestas questões pode ajudá-lo a medir a significância dos novos recursos em relação aos seus próprios esforços de desenvolvimento.

Membros indexados e inicializadores de elemento

Para iniciar, considere o teste de unidade na Figura 1.

Figura 1 Atribuindo uma Coleção através de um Inicializador de Coleção (adicionado ao C# 3.0)

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
// ...
[TestMethod]
public void DictionaryIndexWithoutDotDollar()
{
  Dictionary<string, string> builtInDataTypes = 
    new Dictionary<string, string>()
  {
    {"Byte", "0 to 255"},
    // ...
    {"Boolean", "True or false."},
    {"Object", "An Object."},
    {"String", "A string of Unicode characters."},
    {"Decimal", "±1.0 × 10e-28 to ±7.9 × 10e28"}
  };
  Assert.AreEqual("True or false.", builtInDataTypes["Boolean"]);
}

Apesar de estar de alguma forma obscurecida pela sintaxe, a Figura 1 nada mais é do que uma coleção de nome-valor. Sendo assim, a sintaxe poderia ser significativamente mais limpa: <index> = <value>. O C# 6.0 torna isso possível através dos inicializadores de objeto C# e uma nova sintaxe de membro indexado. A seguir são mostrados os inicializadores de elementos baseados na Internet:

var cppHelloWorldProgram = new Dictionary<int, string>
{
  [10] = "main() {",
  [20] = "    printf(\"hello, world\")",
  [30] = "}"
};
Assert.AreEqual(3, cppHelloWorldProgram.Count);

Observe que, mesmo que este código utilize um número inteiro para o índice, o Dictionary<TKey,TValue> pode suportar qualquer tipo como um índice (contanto que suporte o IComparable<T>). O próximo exemplo apresenta uma sequência para o tipo de dados de índice e usa um inicializador de membro indexado para especificar os valores do elemento:

Dictionary<string, string> builtInDataTypes =
  new Dictionary<string, string> {
    ["Byte"] = "0 to 255",
    // ...
    // Error: mixing object initializers and
    // collection initializers is invalid
    // {" Boolean", "True or false."},
    ["Object"] = "An Object.",
    ["String"] = "A string of Unicode characters.",
    ["Decimal"] = "±1.0 × 10e?28 to ±7.9 × 10e28"
  };

Acompanhar a inicialização do novo membro de índice é um novo operador $. Esta sintaxe de membro indexado de sequência é fornecida especificamente para abordar a prevalência da indexação baseada na cadeia de caracteres. Com esta nova sintaxe, mostrada na Figura 2, é possível atribuir valores de elemento na sintaxe muito mais como na invocação de membro dinâmico (introduzida no C# 4.0) do que na notação de cadeia de caracteres usada no exemplo anterior.

Figura 2 Inicializando uma coleção com uma atribuição de membro indexado como parte do inicializador de elemento

[TestMethod]
public void DictionaryIndexWithDotDollar()
{
  Dictionary<string, string> builtInDataTypes = 
    new Dictionary<string, string> {
    $Byte = "0 to 255",   // Using indexed members in element initializers
    // ...
    $Boolean = "True or false.",
    $Object = "An Object.",
    $String = "A string of Unicode characters.",
    $Decimal = "±1.0 × 10e?28 to ±7.9 × 10e28"
  };
  Assert.AreEqual("True or false.", builtInDataTypes.$Boolean);
}

Para entender o operador $, dê uma olhada na chamada de função AreEqual. Observe a invocação de membro do Dictionary do "$Boolean" na variável builtInDataTypes, mesmo se não houver nenhum membro "Boolean" no Dictionary. Tal membro explícito não é requerido porque o operador $ invoca o membro indexado no dicionário, o equivalente a chamar buildInDataTypes["Boolean"].

Como com qualquer operador baseado na cadeia de caracteres, não há verificação de tempo de compilação de que o elemento de índice (por exemplo, "Boolean") existe no dicionário. Como resultado, qualquer nome de membro C# (com diferenciação entre maiúsculas e minúsculas) pode aparecer após o operador $.

Para apreciar plenamente a sintaxe dos membros indexados, considere a predominância de indexadores de cadeias de caracteres em formatos de dados de tipos flexíveis, como XML, JSON, CSV e até mesmo pesquisas de banco de dados (assumindo que não há mágica de geração de códigos no Entity Framework). A Figura 3, por exemplo, demonstra a conveniência do membro indexado da cadeia de caracteres usando a estrutura Newtonsoft.Json.

Figura 3 Aproveitando o método indexado com os dados do JSON

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
// ...
[TestMethod]
public void JsonWithDollarOperatorStringIndexers()
{
  // Additional data types eliminated for elucidation
  string jsonText = @"
    {
      'Byte':  {
        'Keyword':  'byte',
        'DotNetClassName':  'Byte',
        'Description':  'Unsigned integer',
        'Width':  '8',
        'Range':  '0 to 255'
                },
      'Boolean':  {
        'Keyword':  'bool',
        'DotNetClassName':  'Boolean',
        'Description':  'Logical Boolean type',
        'Width':  '8',
        'Range':  'True or false.'
                  },
    }";
  JObject jObject = JObject.Parse(jsonText);
  Assert.AreEqual("bool", jObject.$Boolean.$Keyword);
}

Um último ponto a observar, caso ainda não seja óbvio, é que a sintaxe do operador $ trabalha apenas com índices do tipo de cadeias de caracteres (como Dictionary<string, …>).

Propriedades automáticas com os inicializadores

Inicializar uma classe hoje pode ser complicado. Considere, por exemplo, o caso trivial de um tipo de coleção personalizada (como o Queue <T>) que mantém internamente uma propriedade privada de System.Collections.Generic.List<T> para uma quantidade de itens. Ao instanciar a coleção, você deve inicializar a fila com a lista de itens que devem ser contidos. No entanto, as opções razoáveis para fazer isso com uma propriedade requerem um campo existente com um inicializador ou um construtor, tal combinação virtualmente dobra a quantidade de código necessário.

Com o C# 6.0, há um atalho para sintaxe: inicializadores de propriedade automática. Agora, você pode atribuir diretamente às propriedades automáticas, como mostrado aqui:

class Queue<T>
{
  private List<T> InternalCollection { get; } = 
    new List<T>; 
  // Queue Implementation
  // ...
}

Observe que neste caso, a propriedade é de apenas leitura (nenhum setter é definido). No entanto, a propriedade ainda é atribuível durante o tempo de declaração. Propriedades de leitura/gravação com um setter também são suportadas.

Construtores primários

Junto às mesmas linhas que os inicializadores de propriedade, o C# 6.0 fornece atalhos sintáticos para a definição de um construtor. Considere a prevalência da validação de propriedade e construtor do C# mostrada na Figura 4.

Figura 4 Um padrão comum de construtor

[Serializable]
public class Patent
{
  public Patent(string title , string yearOfPublication)
  {
    Title = title;
    YearOfPublication = yearOfPublication;
  }
  public Patent(string title, string yearOfPublication,
    IEnumerable<string> inventors)
    : this(title, yearOfPublication)
  {
    Inventors = new List<string>();
    Inventors.AddRange(inventors);
  }
  [NonSerialized] // For example
  private string _Title;
  public string Title
  {
    get
    {
      return _Title;
    }
    set
    {
      if (value == null)
      {
        throw new ArgumentNullException("Title");
      }
      _Title = value;
    }
  }
  public string YearOfPublication { get; set; }
  public List<string> Inventors { get; private set; }
  public string GetFullName()
  {
    return string.Format("{0} ({1})", Title, YearOfPublication);
  }
}

Há vários pontos a serem observados a partir deste padrão comum de construtor:

  1. O fato de que uma propriedade necessite de validação força o campo subjacente da propriedade a ser declarado.
  2. A sintaxe do construtor é, de certa forma, detalhada de forma bastante comum com public class Patent{  public Patent(... repetitividade.
  3. O "Título", em várias versões com diferenciação entre maiúsculas e minúsculas, aparece sete vezes para um cenário bastante trivial - não incluindo a validação.
  4. A inicialização de uma propriedade requer referência explícita à propriedade a partir do construtor.

Para remover parte da cerimônia em torno deste padrão, sem perder o sabor da linguagem, o C# 6.0 introduz inicializadores de propriedade e construtores primários, como mostrado na Figura 5.

Figura 5 Usando um construtor primário

[Serializable]
public class Patent(string title, string yearOfPublication)
{
  public Patent(string title, string yearOfPublication,
    IEnumerable<string> inventors)
    :this(title, yearOfPublication)
  {
    Inventors.AddRange(inventors);
  }
  private string _Title = title;
  public string Title
  {
    get
    {
      return _Title;
    }
    set
    {
      if (value == null)
      {
        throw new ArgumentNullException("Title");
      }
      _Title = value;
    }
  }
  public string YearOfPublication { get; set; } = yearOfPublication;
  public List<string> Inventors { get; } = new List<string>();
  public string GetFullName()
  {
    return string.Format("{0} ({1})", Title, YearOfPublication);
  }
}

Em combinação com os inicializadores de propriedade, a sintaxe do construtor primário simplifica a sintaxe do construtor do C#:

  • As propriedades automáticas, quer sejam de apenas leitura (ver a propriedade dos inventores com apenas um getter) ou de leitura/gravação, (ver a propriedade YearOfPublication com ambos setter e getter), suportam a inicialização de propriedade de modo que o valor inicial da propriedade pode ser atribuído como parte da declaração de propriedade. A sintaxe corresponde ao que é usado ao atribuir aos campos um valor padrão no tempo de declaração (declaração atribuída _Título, por exemplo).
  • Por padrão, os parâmetros do construtor primário não são acessíveis fora de um inicializador. Por exemplo, não há um campo de yearOfPublication declarado na classe.
  • Ao aproveitar os inicializadores de propriedade em propriedades de apenas leitura (apenas getter), não há maneira de fornecer a validação. (Isto se deve ao fato de que na implementação IL subjacente, o parâmetro de construtor primário está atribuído ao campo existente. Também digno de nota é o fato de que o campo existente será definido como apenas leitura no IL se a propriedade automática tiver apenas um getter.)
  • Se especificado, o construtor primário deverá sempre ser executado por último na cadeia do construtor (portanto, não é possível ter um inicializador this(...)).

Em outro exemplo, considere a declaração de uma estrutura, cujas orientações indicam que deve ser imutável. A seguir é mostrado uma implementação com base na propriedade (em relação à abordagem de campo público atípica):

struct Pair(string first, string second, string name)
{
  public Pair(string first, string second) : 
    this(first, second, first+"-"+second)
  {
  }
  public string First { get; } = second;
  public string Second { get; } = first;
  public string Name { get; } = name;
  // Possible equality implementation
  // ...
}

Observe que na implementação do Par há um segundo construtor que invoca o construtor primário. Em geral, todos os construtores de estrutura devem, quer seja de forma direta ou indireta, invocar o construtor primário através de uma chamada ao inicializador this(...). Em outras palavras, não é necessário que todos os construtores chamem o construtor primário diretamente, mas, sim, que ao final da cadeia do construtor, o construtor primário seja chamado. Isto é necessário porque é o construtor primário que chama o inicializador do construtor da base e, ao fazer isso, fornece uma pequena proteção contra alguns erros de inicialização comuns. (Observe que, da mesma forma que no C# 1.0, continua sendo possível iniciar uma estrutura sem invocar um construtor. Isto, por exemplo, é o que acontece quando uma matriz da estrutura é instanciada.

Quer o construtor primário esteja em uma estrutura personalizada ou de dados de classe, a chamada ao construtor de base é implícita (portanto, invocando o construtor padrão da classe de base) ou explícita, ao chamar um construtor de classe de base específico. Em último caso, para uma exceção personalizada invocar um construtor System.Exception, o construtor de destino é especificado após o construtor primário:

class UsbConnectionException : 
  Exception(string message, Exception innerException,
  HidDeviceInfo hidDeviceInfo) :base(message, innerException)
{
  public HidDeviceInfo HidDeviceInfo { get;  } = hidDeviceInfo;
}

Um detalhe para o qual você deve estar ciente em relação a construtores primários é evitar construtores primários duplicados, potencialmente incompatíveis em classes parciais: Devido às múltiplas partes de uma classe parcial, apenas uma declaração de classe pode definir o construtor primário e, de forma similar, apenas este construtor primário pode especificar a invocação do construtor de base.

Existe uma limitação para levar em conta com relação aos construtores primários a ser implementados nesta Preview de Março: Não há maneira de fornecer uma validação para nenhum dos parâmetros do construtor primário. Além disso, devido ao fato dos inicializadores de propriedade estarem disponíveis apenas para propriedades automáticas, também não há maneira de implementar a validação na implementação da propriedade, o que potencialmente expõe os setters de propriedade pública à atribuição da pós-instalação de dados inválidos. A única alternativa, por enquanto, é não usar o recurso do construtor primário quando a validação for importante.

Apesar de ser, de certa forma, provisório no momento, há um recurso relacionado chamado parâmetro de campo a ser levado em consideração. A inclusão de um modificador de acesso no parâmetro do construtor primário (como o título da cadeia de caracteres privados) faz com que o parâmetro seja capturado no escopo da classe como um campo com o nome do título - correspondendo ao nome e ao formato do parâmetro). Desta forma, o título está disponível a partir da propriedade do Título ou de qualquer outra classe de membro de instância. Além disso, o acesso ao modificador permite que a sintaxe completa do campo seja especificada, incluindo modificadores adicionais como somente leitura, ou até mesmo atributos como estes: 

public class Person(
  [field: NonSerialized] private string firstName, string lastName)

Observe que sem o modificador de acesso, outros modificadores (incluindo atributos) não são permitidos. É o modificador de acesso que indica que a declaração de campo deve ocorrer embutida no construtor primário.

(Os bits disponíveis para mim no momento da redação deste artigo não incluem a implementação do parâmetro de campo, porém, a equipe de linguagem me garantiu de que eles serão incluídos na versão do Microsoft Build, de modo que você será capaz de testar os parâmetros de campo no momento em que estiver lendo isso. Devido à relativa "frescura" deste recurso, não hesite em fornecer seu feedback em msdn.com/Roslyn, de modo que o mesmo seja considerado antes que o processo não possa mais ser alterado).

Instruções de uso estático

Outro recurso simplificado do C# 6.0 é a introdução de uso estático. Com este recurso, é possível eliminar uma referência explícita do tipo ao invocar um método estático. Além disso, o uso estático lhe permite introduzir apenas os métodos de extensão em uma classe específica, em vez de todos os métodos de extensão dentro de um namespace. A Figura 6 fornece um exemplo de "Hello World" de uso estático no System.Console.

Figura 6 Simplificando a desordem de código com o uso estático

using System;
using System.Console;
public class Program
{
  private static void Main()
  {
    ConsoleColor textColor = ForegroundColor;
    try
    {
      ForegroundColor = ConsoleColor.Red;
      WriteLine("Hello, my name is Inigo Montoya... Who are you?: ");
      ForegroundColor = ConsoleColor.Green;
      string name = ReadLine(); // Respond: No one of consequence
      ForegroundColor = ConsoleColor.Red;
      WriteLine("I must know.");
      ForegroundColor = ConsoleColor.Green;
      WriteLine("Get used to disappointment");
    }
    finally
    {
      ForegroundColor = textColor;
    }
  }
}

Neste exemplo, o qualificador do console foi removido um total de nove vezes. Reconhecidamente, o exemplo é simulado, mas mesmo assim, o ponto está claro. Frequentemente, um prefixo de tipo no membro estático (incluindo propriedades) não adiciona valores significativos e eliminar seus resultados no código faz com que seja mais fácil a leitura e a gravação.

Apesar de não estar disponível na Preview de Março, um segundo recurso (planejado) do uso estático está sendo discutido. Este recurso é um suporte para os métodos da extensão de apenas importação de um tipo específico. Considere, por exemplo, um namespace de utilidade que inclua numerosos tipos estáticos com métodos de extensão. Sem o uso estático, todos (ou não) os métodos de extensão no namespace são importados. Com o uso estático, entretanto, é possível determinar os métodos de extensão disponíveis para um tipo específico - não para o namespace mais geral. Como resultado, você poderia chamar um operador de consulta padrão do LINQ apenas especificando o uso de System.Linq.Enumerable, em vez do namespace completo do System.Linq.

Infelizmente, este benefício não está sempre disponível (pelo menos na Preview de Maio) porque apenas tipos estáticos suportam o uso estático, razão pela qual, por exemplo, não há instrução de uso de System.ConsoleColor na Figura 6. Devido à natureza preview do C# 6.0, mesmo a restrição permanecerá sob revisão. O que vocês acham?

Expressões de declaração

Não é incomum que, no meio da escrita de uma declaração, você descubra que precisa declarar uma variável especificamente para esta declaração. Considere dois exemplos:

  • Ao codificar uma declaração de int.TryParse, você percebe que precisa de uma variável declarada para o argumento de fora onde resultados analisados serão armazenados.
  • Ao escrever para uma declaração, você percebe a necessidade de armazenar uma coleção (como o resultado da consulta do LINQ) para evitar novamente a consulta múltiplas vezes. De forma a alcançar isso, você interrompe o processo pensado de escrita da declaração para declarar uma variável.

Para abordar estas chateações semelhantes, o C# 6.0 introduz as expressões de declaração. Isto significa que você não tem que de limitar declarações variáveis apenas para declarações, mas pode usá-las dentro de expressões. A Figura 7 oferece dois exemplos.

Figura 7 Exemplos de expressão de declaração

public string FormatMessage(string attributeName)
{
  string result;
  if(! Enum.TryParse<FileAttributes>(attributeName, 
    out var attributeValue) )
  {
    result = string.Format(
      "'{0}' is not one of the possible {2} option combinations ({1})",
      attributeName, string.Join(",", string[] fileAtrributeNames =
      Enum.GetNames(typeof (FileAttributes))),
      fileAtrributeNames.Length);
  }
  else
  {
    result = string.Format("'{0}' has a corresponding value of {1}",
      attributeName, attributeValue);
  }
  return result;
}

No primeiro destaque da Figura 7, a variável attributeValue está declarada em linha com a chamada do Enum.TryParse, em vez de estar em uma declaração separada antecipadamente. De forma semelhante, a declaração de file­AttributeNames aparece imediatamente na chamada para string.Join. Isto permite o acesso ao Tamanho mais tarde na mesma declaração. (Observe que fileAttributeNames.Length é um parâmetro de substituição {2} na chamada string.Format, mesmo que tenha aparecido antes na cadeia de formato, portanto, fileAttributeNames deve ser declarado antes de acessado.)

O escopo da expressão de declaração é definido vagamente como o escopo da declaração em que a expressão aparece. Na Figura 7, o escopo de attributeValue é o mesmo da declaração if-else, tornando-o acessível para ambos os blocos de verdadeiro e falso da condicional. De forma similar, fileAttributeNames está disponível apenas na primeira metade da declaração if, a porção que corresponde ao escopo da invocação da declaração string.Format.

Sempre que possível, o compilador permitirá o uso de variáveis ​​digitadas implicitamente (VAR) para a declaração, inferindo o tipo de dado a partir do inicializador (atribuição de declaração). No entanto, no caso de argumentos de fora, a assinatura do destino da chamada pode ser usada para suportar as variáveis ​​digitadas implicitamente, mesmo se não houver nenhum inicializador. Ainda, a inferência não é sempre possível e, além disso, pode não ser a melhor escolha de uma perspectiva de legibilidade. No caso do TryParse na Figura 7, por exemplo, o var funciona apenas porque o argumento do tipo (FileAttributes) é especificado. Sem ele, a declaração var não pode ser compilada e, em vez disso, o tipo de dados explícitos seria necessário:

Enum.TryParse(attributeName, out FileAttributes attributeValue)

No segundo exemplo de expressão de declaração na Figura 7, uma declaração explícita de string[] aparece para identificar o tipo de dados como uma matriz (em vez de um List<string>, por exemplo). As orientações são padronizadas para o uso geral do var: Considere evitar variáveis ​​digitadas implicitamente quando o tipo de dados resultante não for óbvio.

Os exemplos de expressões de declaração na Figura 7 poderiam ser codificados ao simplesmente declarar as variáveis ​​em uma declaração antes da sua atribuição.

Melhorias da manipulação de exceções

Há dois novos recursos de manipulação de exceção no C# 6.0. O primeiro é uma melhoria na sintaxe do async e await e o segundo é um suporte para a filtragem de exceção.

Quando o C# 5.0 introduziu as teclas de async e await (contextual), os desenvolvedores ganharam uma maneira relativamente fácil de codificar o padrão assíncrono baseado em tarefas (TAP) onde o compilador realiza o laborioso e complexo trabalho de transformar o código C# em uma série subjacente de continuações de tarefa. Infelizmente, a equipe não foi capaz de incluir um suporte para o uso do await nos blocos finally e catch neste lançamento. Como foi verificado, a necessidade para tal invocação era mais comum do que o esperado inicialmente. Assim, os codificadores do C# 5.0 precisavam aplicar soluções significativas (como o aproveitamento do padrão awaiter). O C# 6.0 acaba de vez com essa deficiência e, agora, permite chamadas await nos blocos catch e finally (elas já eram suportadas nos blocos try), como mostrado na Figura 8.

Figura 8 Chamadas await a partir do bloco catch

try
{
  WebRequest webRequest =
    WebRequest.Create("http://IntelliTect.com");
  WebResponse response =
    await webRequest.GetResponseAsync();
  // ...
}
catch (WebException exception)
{
  await WriteErrorToLog(exception);
}

A outra melhoria de exceção no C# 6.0, suporte para filtros de exceção, traz a linguagem atualizada com outras linguagens .NET, ou seja, Visual Basic .NET e F#. A Figura 9 mostra detalhes deste recurso.

Figura 9 Aproveitando os filtros de exceção para determinar que exceção capturar

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.ComponentModel;
using System.Runtime.InteropServices;
// ...
[TestMethod][ExpectedException(typeof(Win32Exception))]
public void ExceptionFilter_DontCatchAsNativeErrorCodeIsNot42()
{
  try
  {
    throw new Win32Exception(Marshal.GetLastWin32Error());
  }
  catch (Win32Exception exception) 
    if (exception.NativeErrorCode == 0x00042)
  {
    // Only provided for elucidation (not required).
    Assert.Fail("No catch expected.");
  }
}

Observe que a expressão if adicional segue a expressão catch. Agora, o bloco catch verifica não apenas a exceção de tipo Win32Exception (ou derivações dela), mas também verifica as condições adicionais, ou seja, o valor particular do código de erro neste exemplo. No teste de unidade na Figura 9, a expectativa é que o bloco catch não vai capturar a exceção, mesmo que o tipo de exceção corresponda, em vez disso, a exceção escapará e será tratada pelo atributo ExpectedException no método de teste.

Observe que, diferentemente dos outros recursos do C# 6.0 discutidos anteriormente (como o construtor primário), não havia meios alternativos equivalentes de codificar os filtros de exceção antes do C# 6.0. Até agora, o único método era capturar todas as exceções de um tipo particular, verificar explicitamente o contexto da exceção e, em seguida, relançar a exceção, caso o estado atual não fosse um cenário de captura de exceção válido. Em outras palavras, a filtragem de exceção do C# 6.0 fornece uma funcionalidade que até agora não era possível no C#.

Formatos adicionais de literais numéricos

Apesar de ainda não estar implementado na Preview de Março, o C# 6.0 introduzirá um separador de dígitos, o sublinhado (_), como meio de separar os dígitos em um literal numérico (decimal, hexadecimal ou binário). Os dígitos podem ser divididos em qualquer agrupamento que faça sentido para o seu cenário. Por exemplo, o valor máximo de um número inteiro poderia ser agrupado em milhares:

número inteiro = 2_147_483_647;

O resultado faz com que seja mais evidente ver a magnitude de um número, quer seja decimal, hexadecimal ou binário.

É provável que o superador de dígitos seja especialmente útil para o novo literal binário numérico do C# 6.0. Apesar de não ser necessário em todos os programas, a disponibilidade de um literal binário poderia melhorar a capacidade de manutenção ao trabalhar com enumerações baseadas em sinalizador ou lógica binária. Considere, por exemplo, a enumeração de FileAttribute mostrada na Figura 10.

Figura 10 Atribuindo literais binários para os valores de enumeração

[Serializable][Flags]
[System.Runtime.InteropServices.ComVisible(true)]
public enum FileAttributes
{
  ReadOnly =          0b00_00_00_00_00_00_01, // 0x0001
  Hidden =            0b00_00_00_00_00_00_10, // 0x0002
  System =            0b00_00_00_00_00_01_00, // 0x0004
  Directory =         0b00_00_00_00_00_10_00, // 0x0010
  Archive =           0b00_00_00_00_01_00_00, // 0x0020
  Device =            0b00_00_00_00_10_00_00, // 0x0040
  Normal =            0b00_00_00_01_00_00_00, // 0x0080
  Temporary =         0b00_00_00_10_00_00_00, // 0x0100
  SparseFile =        0b00_00_01_00_00_00_00, // 0x0200
  ReparsePoint =      0b00_00_10_00_00_00_00, // 0x0400
  Compressed =        0b00_01_00_00_00_00_00, // 0x0800
  Offline =           0b00_10_00_00_00_00_00, // 0x1000
  NotContentIndexed = 0b01_00_00_00_00_00_00, // 0x2000
  Encrypted =         0b10_00_00_00_00_00_00  // 0x4000
}

Agora, com os literais numéricos binários, você pode mostrar mais claramente quais sinalizadores estão definidos e quais não. Isto substitui a notação hexadecimal mostrada nos comentários ou a abordagem de mudança do tempo de compilação:

Encrypted = 1<<14.

(Os desenvolvedores ansiosos para experimentar este recurso imediatamente podem fazê-lo no Visual Basic .NET com o lançamento da Preview de Março).

Conclusão

Ao considerar apenas estas alterações de linguagem, você vai perceber que não há nada particularmente revolucionário ou surpreendente no C# 6.0. Se você compará-lo com outros lançamentos importantes, como os genéricos no C# 2.0, LINQ no C# 3.0 ou TAP no C# 5.0, o C# 6.0 se assemelha mais a um lançamento "pequeno" do que a um lançamento principal. (A grande novidade é que o compilador agora é lançado como código-fonte aberto). Mas só porque ele não revoluciona a sua codificação do C#, não significa que ele não tenha feito um progresso real na eliminação de alguns aborrecimentos e ineficiências de codificação e que, uma vez introduzido ao seu uso diário, será rapidamente aderido. Os recursos que estão entre os meus favoritos particulares são o operador $ (membros de índice da cadeia de caracteres), os construtores primários (sem parâmetros de campo), o uso estático e as expressões de declaração. Espero que cada um deles rapidamente se tornem o padrão na minha codificação, e, provavelmente, ainda sejam acrescentados aos padrões de codificação em alguns casos.

Mark Michaelis é o fundador da IntelliTect e atua como arquiteto técnico principal e treinador. Desde 1996, ele tem sido um MVP da Microsoft para C#, Visual Studio Team System (VSTS), e o SDK do Windows, além disso, ele foi reconhecido como diretor regional da Microsoft em 2007. Ele também atua em várias equipes de revisão de design de softwares da Microsoft, incluindo o C#, da Divisão de Sistemas Conectados e VSTS. Michaelis é orador em conferências de desenvolvedores e tem escrito numerosos artigos e livros. Atualmente, ele está trabalhando na próxima edição do "Essencial C#" (Addison-Wesley Professional). Michaelis possui bacharelado na área de humanas em filosofia pela Universidade de Illinois e mestrado em ciência da computação pelo Instituto de Tecnologia de Illinois. Quando não está preso ao computador, ele está ocupado com a sua família ou treinando para ser o Homem de Ferro.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Mads Torgersen