C# - Compilação Condicional (revisitado)


 Neste artigo vou rever os conceitos relacionados com a compilação condicional na linguagem C#.

O que é a Compilação condicional ?

A compilação condicional é o processo de definição de diretivas do compilador que compõem partes diferentes do código a serem compiladas e outras partes a serem ignoradas.

Esta técnica pode ser usada em um cenário de desenvolvimento entre plataformas para especificar partes do código que são compiladas especificamente para uma plataforma específica. Como exemplo,  fazemos isso quando criamos uma aplicação Xamarin Forms usando o tipo de projeto Shared.

Assim, podemos compilar condicionalmente qualquer seção de código em C# usando diretivas de pré-processamento. Estas diretivas são instruções especiais para o compilador que iniciam com o simbolo # que executam antes da compilação principal (embora, na prática, o compilador as processe durante a fase de análise léxica).

As diretrizes do pré-processador para a compilação condicional são:
 #if, #else, #endif e #elif.

A diretiva #if instrui o compilador a ignorar uma seção de código, a menos que um símbolo especificado seja definido. Você pode definir um símbolo usando ou a diretiva #define ou uma opção de compilação.

Dessa forma, a base das diretivas de pré-processamento na linguagem C# são as diretivas #define e #undef. Elas permitem que você crie e destrua os símbolos utilizados pela etapa de pré-processamento. Você pode user este recurso para construir os scripts que produzem diferentes versões do seu código. Por exemplo, uma versão "completa" e uma versão "demo" limitada.

As diretivas #if, #elif, #else e #endif são usadas no pré-processamento condicional verificando se um símbolo é verdadeiro ou não.

A diretiva #define se aplica a um arquivo específico; enquanto que a compilação condicional se aplica a todo o assembly. Vejamos o exemplo abaixo:

#define MODO_TESTE
using System;
namespace CShp_CompilacaoCondicional
{
    class Program
    {
        static void Main(string[] args)
        {
             #if MODO_TESTE
                Console.WriteLine("Estou em modo de teste");
                Console.ReadLine();
             #endif
        }
    }
}

Usamos a diretiva #define MODO_TESTE para compilar a classe Program condicionalmente, e, a compilação é dependente da presença do símbolo MODO_TESTE.

Se você excluir esta linha ou comentá-la. as instruções definidas dentro do bloco #if MODO_TESTE não serão compiladas.

A instrução #else é análoga à instrução else do C# e a inswtrução  #elif é  equivalente a instrução  #else seguido de #if. (Os operadores  ||, && e ! podem ser usados para executar as operações ou, e, e não.)

A regra para usar uma diretiva é que ela deve ser a única instrução em uma linha e deve começar com o símbolo “#” (espaços em branco são permitidos antes e depois do simbolo “#”.)

Exemplo:

#define MODO_TESTE
using System;
namespace CShp_CompilacaoCondicional
{
    class Program
    {
        static void Main(string[] args)
        {
             #if MODO_TESTE && !MODULO_INICIO
                Console.WriteLine("Estou em modo de teste");
                Console.ReadLine();
             #endif
        }
    }
}

Tenha em mente, no entanto, que você não está construindo uma expressão C# comum, e os símbolos sobre os quais você opera não têm absolutamente nenhuma conexão com as variáveis sejam elas estáticas ou não.

Para definir um símbolo abrangente em todo o assembly, você tem que especificar a opção /define quando for compilar o código, seguida dos símbolos que vai usar:

Exemplo :   csc Program.cs /define:TESTMODE,PLAYMODE

Nota: O Visual Studio fornece uma opção para inserir símbolos de compilação condicional em Project Properties.

Se você definiu um símbolo no nível de assembly e então deseja "desfazer a definição" do símbolo para um arquivo específico, você pode fazer isso usando a diretiva
#undef.

A compilação condicional versus Variáveis Estáticas sinalizadoras

O código do exemplo anterior pode ser implementado usando um campo estático :

using System;
namespace CShp_CompilacaoCondicional
{
    class Program
    {
        static internal bool ModoTeste = true;
        static void Main(string[] args)
        {
            if (ModoTeste) Console.WriteLine("Estou em modo teste");
            Console.ReadLine();
        }
    }
}

Essa abordagem tem a vantagem de permitir a configuração em tempo de execução.

Então, por que escolher a compilação condicional ?

Ora bolas, o motivo é que a compilação condicional pode levá-lo a lugares que as variáveis sinalizadoras não podem, como:

1- Incluir um atributo de forma condicional;
2- Alterar o tipo de variável declarada;
3- Alternar entre diferentes namespaces ou tipos de alias em uma diretiva using;

Exemplos:       

 using TipoTeste =
    #if Desenvolvimento
            Macoratti.Projeto.Teste;
    #else
            Macoratti.Projeto.Producao;
    #endif

#if CONDICAO
   using Condicional.Namespace;
#else
   using Outro.Namespace;
#endif

 

  
Você pode até mesmo realizar uma refatoração sob uma diretiva de compilação condicional , para que você possa alternar instantaneamente entre versões antigas e novas e escrever bibliotecas que podem ser compiladas contra múltiplas versões do Framework, alavancando os recursos mais do Framework, quando disponíveis.

Outra vantagem da compilação condicional é que o código de depuração pode se referir a tipos em assemblies que não estão incluídos na implantação.

O atributo Conditional

O atributo Conditional instrui o compilador a ignorar as chamadas para um classe ou método específico, se o símbolo especificado não tiver sido definido.

Para ver como isso é útil, suponha que você escreva um método para registrar o status das informações da seguinte forma:

...
       static void LogStatus(string mensagem)
       {
           string logCaminhoArquivo = "....";
           System.IO.File.AppendAllText(logCaminhoArquivo, mensagem + "\r\n");
       }
...

Pois bem, agora você quer que esse código execute somente se o símbolo MODO_LOGGING estiver definido.

A primeira solução é envolver todas as chamadas para LogStatus em torno de uma diretiva  #if, assim:

#if MODO_LOGGING
     LogStatus ("Headers: " + GetMsgHeaders());
#endif

Vai funcionar , mas imagina o trabalho que fazer isso em todo o código vai dar ???

A segunda solução então é colocar a diretiva #if no método LogStatus(), mas isso faz com que toda a chamada para LogStatus() seja executada.

E agora ?  Quem poderá nos salvar deste dilema ???

Simples...

Podemos combinar a funcionalidade da primeira solução com a conveniência da segunda usando o atributo Conditional, do namespace System.Diagnostics, no método LogStatus. Ficaria assim:

Ao usar o atributo Conditional do namespace System.Diagnostics, o compilador será instruido a tratar as chamadas ao método LogStatus como se ele estivesse envolvido por uma diretiva #if MODO_LOGGING.

Se o símbolo não estiver definido, qualquer chamada a LogStatus será eliminada da compilação, incluindo o argumento de avaliação de expressões. (Portanto, quaisquer expressões de efeitos colaterais serão ignoradas).

Obs: Isso funciona mesmo se o método LogStatus e o chamador do método estiverem em assemblies diferentes.

Outro benefício do atributo Conditional é que a verificação de condicionalidade é realizada quando o chamador é compilado, em vez de quando o método chamado for compilado.

E porque isso é bom ???

Porque permite que você escreva uma biblioteca contendo métodos como LogStatus e crie apenas uma versão dessa biblioteca.

Mas nem tudo são flores com o atributo Conditional.

Ele será inútil se você precisar ativar ou desativar a funcionalidade em tempo de execução. Neste cenário você pode usar uma abordagem baseada em variável, usando variáveis sinalizadoras.

E assim recordamos os conceitos básicos da compilação condicional.

Até o próximo artigo...

"Havendo Deus antigamente falado muitas vezes, e de muitas maneiras, aos pais, pelos profetas, a nós falou-nos nestes últimos dias no Filho (Jesus)"
Hebreus 1:1

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti