Search code examples
c#conditional-attribute

Why are the arguments to a Conditional method always type-checked?


In the event that a Conditional method is compiled away, the arguments to each invocation are still type-checked at compile time. What is the motivation for this? Example:

using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 2;
            string st = "";
            // this invocation compiles fine
            ConditionalMethod(x, st);
            // this invocation won't compile
            ConditionalMethod(st, x);
        }

        [Conditional("condition")]
        public static void ConditionalMethod(int x, string st) { }
    }
}

To be clear, the conditional symbol "condition" is not defined in this context, so the method invocations are omitted from the MSIL resulting from compilation. This is on par with the spec defined here, so no surprises there. Imagine a more complex scenario:

using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            ConditionalMethod(new Bar());
        }

        [Conditional("condition")]
        public static void ConditionalMethod(Foo foo) { }

        public class Foo { }

#if condition
        public class Bar : Foo { }
#else
        public class Bar { }
#endif
    }
}

Invocations of 'ConditionalMethod' will only be included in the resulting compilation when the conditional symbol "condition" is defined. In that scenario, however, Bar CAN in fact be upcast to Foo. If the compiler knows that invocations to 'ConditionalMethod' will be compiled away, shouldn't it also be aware that in the event that we care about invocations of this method, this code will be legit? Yes, this is a contrived and hair-splitting example, but it helps illustrate my question. I'm asking out of benign curiosity, as this has irked me for quite some time now. Please help, Jon Skeet. :)


Solution

  • Imagine this code

    class Program
    {
        static void Main(string[] args)
        {
            int x = 2;
            string st = "";
            // this invocation compiles fine
            Blah(x, st);
            // this invocation won't compile
            Blah(st, x);
        }
    
        [Conditional("condition")]
        public static void Blah(int x, string st) { }
    
        public static void Blah (string st, int x, int y) { }
    
        public static void Blahh(string st, int x) { }
    }
    

    A method's signature is a critical part of associating a call to the method that should be called. That association must be made before it is known whether the Conditional attribute applies. In the example above, the call does not match any of the methods. The compiler would have to make a leap of faith to guess that you meant the Blah(int,string). But it would be a guess, because the the signature doesn't match (the argument types are in the wrong order). Blah(string,int,int) is also pretty close - you just forgot an argument. And Blahh(string,int) is close too - you just made a typo in the name.

    For a similar example, see this blog post by Eric Lippert (who knows this stuff).

    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!

    and later

    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.