#if DEBUG vs. Condicional ("DEBUG")

432

Qual é melhor usar e por que, em um projeto grande:

#if DEBUG
    public void SetPrivateValue(int value)
    { ... }
#endif

ou

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }
Lucas B
fonte
18
Veja blogs.msdn.com/b/ericlippert/archive/2009/09/10/… para algumas reflexões sobre esta questão.
Eric Lippert
2
você pode usar isso também: if (Debugger.IsAttached) {...}
sofsntp
Nota para desenvolvedores do Unity: DEBUG significa no editor ou nas construções de desenvolvimento. forum.unity.com/threads/…
KevinVictor

Respostas:

578

Realmente depende do que você procura:

  • #if DEBUG: O código aqui nem chegará à IL no lançamento.
  • [Conditional("DEBUG")]: Este código alcançará o IL, no entanto, as chamadas para o método serão omitidas, a menos que DEBUG seja definido quando o chamador for compilado.

Pessoalmente, uso os dois, dependendo da situação:

Exemplo condicional ("DEBUG"): Eu uso isso para não precisar voltar e editar meu código posteriormente durante o lançamento, mas durante a depuração, quero ter certeza de que não fiz nenhum erro de digitação. Essa função verifica se eu digito um nome de propriedade corretamente ao tentar usá-lo no meu material INotifyPropertyChanged.

[Conditional("DEBUG")]
[DebuggerStepThrough]
protected void VerifyPropertyName(String propertyName)
{
    if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        Debug.Fail(String.Format("Invalid property name. Type: {0}, Name: {1}",
            GetType(), propertyName));
}

Você realmente não deseja criar uma função usando, a #if DEBUGmenos que esteja disposto a encerrar todas as chamadas dessa função com a mesma #if DEBUG:

#if DEBUG
    public void DoSomething() { }
#endif

    public void Foo()
    {
#if DEBUG
        DoSomething(); //This works, but looks FUGLY
#endif
    }

versus:

[Conditional("DEBUG")]
public void DoSomething() { }

public void Foo()
{
    DoSomething(); //Code compiles and is cleaner, DoSomething always
                   //exists, however this is only called during DEBUG.
}

#if Exemplo de DEBUG: eu uso isso ao tentar configurar ligações diferentes para a comunicação WCF.

#if DEBUG
        public const String ENDPOINT = "Localhost";
#else
        public const String ENDPOINT = "BasicHttpBinding";
#endif

No primeiro exemplo, todo o código existe, mas é apenas ignorado, a menos que DEBUG esteja ativado. No segundo exemplo, o const ENDPOINT é definido como "Localhost" ou "BasicHttpBinding", dependendo se DEBUG estiver definido ou não.


Atualização: Estou atualizando esta resposta para esclarecer um ponto importante e complicado. Se você optar por usar o ConditionalAttribute, lembre-se de que as chamadas são omitidas durante a compilação e não o tempo de execução . Isso é:

MyLibrary.dll

[Conditional("DEBUG")]
public void A()
{
    Console.WriteLine("A");
    B();
}

[Conditional("DEBUG")]
public void B()
{
    Console.WriteLine("B");
}

Quando a biblioteca é compilada contra o modo de versão (ou seja, nenhum símbolo de depuração), ele sempre terá a chamada para B()dentro A()omitido, mesmo se uma chamada para A()está incluído porque DEBUG é definido na chamar montagem.

meu
fonte
13
O #if Debug for DoSomething não precisa ter todas as instruções de chamada cercadas por #if DEBUG. você pode 1: apenas #se DEBUG dentro do DoSomething, ou faça um #else com uma definição em branco do DoSomething. Ainda assim, seu comentário ajudou a entender a diferença, mas #if DEBUG não precisa ser tão feio quanto você demonstrou.
Apeiron
3
Se você apenas #parar depurar o conteúdo, o JIT ainda poderá incluir uma chamada para a função quando seu código for executado em uma compilação sem depuração. Usar o atributo Condicional significa que o JIT sabe nem mesmo emitir o site de chamada quando estiver em uma construção que não seja DEBUG.
Jeff Yates
2
@ JeffYates: Não vejo como o que você está escrevendo é diferente do que eu expliquei.
meu
1
@ Apeiron, se você tiver apenas o conteúdo da função na depuração #if, a chamada de função ainda será adicionada à pilha de chamadas, embora isso geralmente não seja muito importante, adicionar a declaração e a chamada de função ao #if significa que o compilador se comporta como se a função não existir, o método my será a maneira mais "correta" de usar #if. embora ambos os métodos produzem os resultados que são indistinguíveis uns dos outros, em utilização normal
miket
5
se alguém está se perguntando, IL = Intermediate Language - en.wikipedia.org/wiki/Common_Intermediate_Language
jbyrd
64

Bem, vale a pena notar que eles não significam a mesma coisa.

Se o símbolo DEBUG não estiver definido, no primeiro caso, o SetPrivateValuepróprio não será chamado ... enquanto no segundo caso existir, mas todos os chamadores que são compilados sem o símbolo DEBUG terão essas chamadas omitidas.

Se o código e todos os seus chamadores estiverem no mesmo assembly, essa diferença será menos importante - mas isso significa que, no primeiro caso, você também precisará ter também#if DEBUG o código de chamada .

Pessoalmente, eu recomendaria a segunda abordagem - mas você precisa manter clara a diferença entre elas.

Jon Skeet
fonte
5
O +1 para o código de chamada também precisará ter as instruções #if. Que significa que haverá uma proliferação de #if declarações ...
Lucas B
Embora a segunda opção (atributo Condicional) seja mais agradável e mais limpa em alguns casos, pode ser necessário comunicar o fato de que uma chamada de método seria retirada do assembly durante a compilação (por uma convenção de nomenclatura, por exemplo).
ácido lisérgico
45

Tenho certeza de que muitos discordarão de mim, mas, tendo passado algum tempo como profissional de construção ouvindo constantemente "Mas funciona na minha máquina!", Entendo que você também nunca deve usar isso. Se você realmente precisa de algo para teste e depuração, descubra uma maneira de tornar essa testabilidade separada do código de produção real.

Abstraia os cenários com zombaria nos testes de unidade, faça versões únicas dos itens para os cenários que você deseja testar, mas não coloque testes de depuração no código dos binários que você testa e escreve para o release de produção. Esses testes de depuração apenas ocultam possíveis erros dos desenvolvedores, para que não sejam encontrados até mais tarde no processo.

Jimmy Hoffa
fonte
4
Eu concordo totalmente com você Jimmy. Se você estiver usando DI e zombaria para seus testes, por que você precisaria #if debugou qualquer construção semelhante em seu código?
Richard Ev
@RichardEv Pode haver uma maneira melhor de lidar com isso, mas atualmente estou usando para me permitir desempenhar o papel de diferentes usuários por meio de uma string de consulta. Não quero isso na produção, mas desejo que seja para depuração, para que eu possa controlar o fluxo de trabalho percorrido sem precisar criar vários usuários e fazer login nas duas contas para percorrer o fluxo. Embora esta seja a primeira vez que eu realmente o usei.
23413 Tony
4
Em vez de apenas testar, geralmente fazemos coisas como definir um email de destinatário padrão para nós mesmos, em compilações de depuração, usando-o #if DEBUGpara não enviar spam acidentalmente a outros enquanto testamos um sistema que deve transmitir emails como parte do processo. Às vezes, essas são as ferramentas certas para o trabalho :)
Ido Codificação
6
Em geral, eu concordo com você, mas se você estiver em uma situação em que o desempenho é primordial, não desejará desorganizar o código com registros estranhos e saída do usuário, mas concordo 100% de que nunca devem ser usados ​​para alterar o comportamento fundamental
MikeT
5
-1 Não há nada de errado em usar um desses. Reivindicar testes de unidade e DI de alguma forma substitui uma compilação habilitada para depuração de um produto é ingênua.
Ted Bigham
15

Este também pode ser útil:

if (Debugger.IsAttached)
{
...
}
sofsntp
fonte
1
Pessoalmente, não vejo como isso pode ser útil em comparação com as outras duas alternativas. Isso garante que todo o bloco seja compilado e Debugger.IsAttacheddeve ser chamado em tempo de execução, mesmo nas versões do release.
Jai
9

Com o primeiro exemplo, SetPrivateValuenão existirá na compilação se DEBUGnão estiver definido, com o segundo exemplo, chamadas para SetPrivateValuenão existirão na compilação se DEBUGnão estiver definido.

Com o primeiro exemplo, você vai ter que envolver todas as chamadas para SetPrivateValuecom #if DEBUGtão bem.

Com o segundo exemplo, as chamadas para SetPrivateValueserão omitidas, mas saiba que SetPrivateValueelas ainda serão compiladas. Isso é útil se você estiver criando uma biblioteca, para que um aplicativo que faça referência à sua biblioteca ainda possa usar sua função (se a condição for atendida).

Se você deseja omitir as chamadas e economizar o espaço do chamado, você pode usar uma combinação das duas técnicas:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}
P Papai
fonte
@P Daddy: #if DEBUGContornar Conditional("DEBUG")não remove as chamadas para essa função, apenas remove a função da IL completamente, então você ainda está tendo chamadas para a função que não existe (erros de compilação).
meu
1
Se alguém não quiser que o código exista na versão, deve envolver o corpo do método em "#if DEBUG", possivelmente com um stub "#else" (com um valor de retorno throw ou fictício) e use o atributo para sugerir que os chamadores não se incomodam com a ligação? Isso pareceria o melhor dos dois mundos.
supercat
@myermian, @supercat: Sim, vocês dois estão certos. Meu erro. Vou editar de acordo com a sugestão do supercat.
P papai
5

Vamos supor que seu código também tenha uma #elseinstrução que definiu uma função de stub nulo, abordando um dos pontos de Jon Skeet. Há uma segunda distinção importante entre os dois.

Suponha que a função #if DEBUGou Conditionalexista em uma DLL referenciada pelo executável do projeto principal. Usando o #if, a avaliação do condicional será realizada com relação às configurações de compilação da biblioteca. Usando o Conditionalatributo, a avaliação do condicional será realizada com relação às configurações de compilação do invocador.

Kennet Belenky
fonte
2

Eu tenho uma extensão SOAP WebService para registrar o tráfego de rede usando um costume [TraceExtension]. Eu uso isso apenas para compilações de depuração e omitir das compilações de versão . Use o #if DEBUGpara quebrar o [TraceExtension]atributo, removendo-o das compilações do Release .

#if DEBUG
[TraceExtension]
#endif
[System.Web.Service.Protocols.SoapDocumentMethodAttribute( ... )]
[ more attributes ...]
public DatabaseResponse[] GetDatabaseResponse( ...) 
{
    object[] results = this.Invoke("GetDatabaseResponse",new object[] {
          ... parmeters}};
}

#if DEBUG
[TraceExtension]
#endif
public System.IAsyncResult BeginGetDatabaseResponse(...)

#if DEBUG
[TraceExtension]
#endif
public DatabaseResponse[] EndGetDatabaseResponse(...)
Steven J. Hathaway
fonte
0

Normalmente, você precisaria dele em Program.cs, onde deseja decidir executar o código Debug on Non-Debug e principalmente nos Serviços do Windows. Então, criei um campo somente leitura IsDebugMode e defina seu valor no construtor estático, como mostrado abaixo.

static class Program
{

    #region Private variable
    static readonly bool IsDebugMode = false;
    #endregion Private variable

    #region Constrcutors
    static Program()
    {
 #if DEBUG
        IsDebugMode = true;
 #endif
    }
    #endregion

    #region Main

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main(string[] args)
    {

        if (IsDebugMode)
        {
            MyService myService = new MyService(args);
            myService.OnDebug();             
        }
        else
        {
            ServiceBase[] services = new ServiceBase[] { new MyService (args) };
            services.Run(args);
        }
    }

    #endregion Main        
}
Yashwant Shukla
fonte