Fabulous Adventures In Coding

Eric Lippert's Blog

What's the difference between conditional compilation and the conditional attribute?

User: Why does this program not compile correctly in the release build?

class Program
{
#if DEBUG
    static int testCounter = 0;
#endif 
    static void Main(string[] args)
    {
        SomeTestMethod(testCounter++);
    } 
    [Conditional("DEBUG")]
    static void SomeTestMethod(int t) { }
}

Eric: This fails to compile in the retail build because testCounter cannot be found in the call to SomeTestMethod.

User: But that call site is going to be omitted anyway, so why does it matter? Clearly there's some difference here between removing code with the conditional compilation directive versus using the conditional attribute, but what's the difference?

Eric: You already know the answer to your question, you just don't know it yet. Let's get Socratic; let me turn this around and ask you how this works. How does the compiler know to remove the method call site? 

User: Because the method called has the conditional attribute on it.

Eric: You know that. But how does the compiler know that the method called has the conditional attribute on it?

User: Because overload resolution chose that method. If this were a method from an assembly, the metadata associated with that method has the attribute. If it is a method in source code, the compiler knows that the attribute is there because the compiler can analyze the source code and figure out the meaning of the attribute.

Eric: I see. So fundamentally, overload resolution does the heavy lifting. How does overload resolution know to choose that method? Suppose hypothetically there were another method of the same name with different parameters.

User: Overload resolution works by examining the arguments to the call and comparing them to the parameter types of each candidate method and then choosing the unique best match of all the candidates.

Eric: And there you go. Therefore the arguments must be well-defined at the point of the call, even if the call is going to be removed. In fact, the call cannot be removed unless the arguments are extant! But in the release build, the type of the argument cannot be determined because its declaration has been removed.

So now you see that the real difference between these two techniques for removing unwanted code is what the compiler is doing when the removal happens. At a high level, the compiler processes a text file like this. First it "lexes" the file. That is, it breaks the string down into "tokens" -- sequences of letters, numbers and symbols that are meaningful to the compiler. Then those tokens are "parsed" to make sure that the program conforms to the grammar of C#. Then the parsed state is analyzed to determine semantic information about it; what all the types are of all the expressions and so on. And finally, the compiler spits out code that implements those semantics.

The effect of a conditional compilation directive happens at lex time; anything that is inside a removed #if block is treated by the lexer as a comment. It's like you simply deleted the whole contents of the block and replaced it with whitespace. But removal of call sites depending on conditional attributes happens at semantic analysis time; everything necessary to perform that semantic analysis must be present. 

User: Fascinating. Which parts of the C# specification define this behavior?

Eric: The specification begins with a handy “table of contents”, which is very useful for answering such questions. The table of contents states that section 2.5.1 describes "Conditional compilation symbols" and section 17.4.2 describes "The Conditional attribute".

User: Awesome.

Published Thursday, September 10, 2009 7:12 AM by Eric Lippert

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Banjobeni said:

Great post.

There's probably a typo in your first answer. Should be release build instead of retail build.

September 10, 2009 6:55 PM
 

Cata said:

I always though it would have been nicer if the methods marked with [Conditional] attribute would have been removed by the JIT instead of the compiler, that way you could switch a retail assembly to debug mode and have all the extra debug information available like the debug output, without having a second debug version of the same assembly.

What I would also like to ask is, are there any planned posts regarding the Reactive Framework and the new IObservable<T> and IObserver<T> interfaces, or those are just planned at library level so far?

Like if IObservable<T> and IObserver<T> are the counterparts of IEnumerable<T> and IEnumerator<T> what would the counterpart of yield be?

September 10, 2009 8:23 PM
 

Jonathan Pryor said:

@Cata: I think you're thinking too low level with 'yield'. The high level construct would be query comprehension expressions (select, where, etc.), which should be supported "normally" with the Reactive framework. Thus, no new keyword is necessary, as in-language LINQ support will be sufficient.

September 10, 2009 8:33 PM
 

Jonathan Pryor said:

...though it should be noted that low level support such as a 'push return' keyword would make *implementing* the Reactive framework itself easier, just as 'yield' greatly simplifies implementing LINQ extension methods...

That said, if C# had LINQ before 'yield,' I'm not sure that 'yield' would have been added to the language.  See VB.NET...

September 10, 2009 9:06 PM
 

TheCPUWizard said:

What can get "real interesting" is code like the following (illustrative, not actual code...)

[Conditional]

void Foo(DervivedClass v)  {}

void Fool(BaseClass v) {}

class BaseClass {}

class DerivedClass : BaseClass {}

void Bar()

{

  DerivedClass d;

  Foo(d);    / Who gets called......

}

September 10, 2009 9:39 PM
 

Cata said:

@TheCPUWizard, no one get's called, it clearly stated that calls to methods with conditional attribute are removed, which means the call is removed.

However, I've also tested this, and the call is removed as expected opposed to being re-evaluated, which the spec doesn't say it should happen.

September 10, 2009 10:24 PM
 

TheCPUWizard said:

@Cata - You are exactly correct. But many people get this wrong. I know of at least one case where this mistake lead directly to a very subtle bug [the conditional method was added for "debugging/isolation" of one specific derived class and internally called the "normal" inplementation. Before this was done all classes in the derivation tree would get processed by the base method. In release builds after this change, the one specific derived class did not get processed AT ALL!]

I haven't had the chance to test the following, but I wonder how the 4.0 behaviour would be if "d" was declared as dynamic...............Seems like something I will try over the weekend.

September 11, 2009 5:39 AM
 

Szindbad said:

TheCPUWizard: Jesus, these typesystem-feature interferences increases the necessary knowledege of programming in C# exponentially. This makes me sad. If you need pilot licence for developing in a language, that language missed the point.

September 14, 2009 3:50 AM
 

Dave Lacerte said:

I bet on something: that user does not exist in real life.

First, his narrow answer to your first question serves too well the rest of the question/answer path.  Why did he choose "overload resolution" before being asked a more specific question?

Second, his second answer, again right on target ("Overload resolution works by examining the arguments") proves he's not a beginner and thus, it should already have rang the bells in this head.  It's clear that "examining the arguments" is the key to "why does it look at the argument"...

Am I wrong? :-P

The user exists but of course the dialogue is mostly made up. The didactic dialogue has a long history; Plato's dialogues were certainly not word-for-word transcriptions of Socrates' conversations either. The purpose of the dialogue structure is to provide a framework upon which to hang the instructive text, not to be an accurate account of a real conversation. Try recording a word-for-word transcription of a technical conversation over lunch sometime and you'll see why; it's completely incoherent on the page. -- Eric

 

September 17, 2009 11:09 AM
 

Dave Lacerte said:

Haha!!  You're so right!!  :-)  I didn't mean no disrespect though, I just felt like playing Sherlock a bit today.  In all cases, I love your blog and the way you bring that up.

September 17, 2009 4:56 PM

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required
Submit

About Eric Lippert

Eric Lippert is a senior developer on the Microsoft C# compiler team. Before that he worked on the framework of Visual Studio Tools For Office. Before that, he worked on the compilers, runtimes and tools for VBScript, JScript, Windows Script Host and other Microsoft Scripting technologies. He lives in Seattle and spends his free time editing books about programming languages, playing the piano, and trying to keep his tiny sailboat upright in Puget Sound.

This Blog

Syndication


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker