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:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
Super DVD C# - Recursos de aprendizagens e vídeo aulas para C#
Curso Fundamentos da Programação Orientada a
Objetos com VB .NET
C# - Compreendendo as palavras chaves Constantes ... - Macoratti.net
NET Core - Padrão Repositório - Macoratti
Conceitos - .NET Framework versus .NET Core - Macoratti
NET Core ou .NET Framework - Macoratti
NET Core 3.0 e Visual Studio 2019 - Macoratti