Search code examples
c#optimizationcompiler-optimizationconditional-compilation

Does conditional compilation optimise away methods that generate input arguments?


In C#, we can perform conditional compilation using #if / #endif statements or with Conditional attributes. For example, the following code will print something only for a debug build:

public static void Main(string[] args)
{
    CheckResult();
}

[Conditional("DEBUG")]
private static void CheckResult() 
{ 
    System.Console.WriteLine("everything is fine");
}

What happens, though, if this CheckResult() method accepts arguments, and we use it like so?

public static void Main(string[] args)
{
    CheckResult(CalculateSomethingExpensive() == 100);
}

private static int CalculateSomethingExpensive()
{
    result = //some sort of expensive operation here
    return result;
}

[Conditional("DEBUG")]
private static void CheckResult(bool resultIsOK) 
{ 
    System.Console.WriteLine(resultIsOK ? "OK" : "not OK");
}

In this case, what compiler rules decide whether the expensive method is executed or optimised away? For example, is it guaranteed to be removed if it makes no changes to the state of any object?

I understand that the uncertainty can be removed by explicitly using #if but when one has a large code base with hundreds of Debug.Assert() statements, this can get unsightly very quickly.


Solution

  • So, with a little modification, here is what is compiled (under Release):

    class Program
    {
        public static void Main(string[] args)
        {
            CheckResult(CalculateSomethingExpensive() == 100);
        }
    
        private static int CalculateSomethingExpensive()
        {
            var result = new Random().Next(100);//some sort of expensive operation here
            return result;
        }
    
        [Conditional("DEBUG")]
        private static void CheckResult(bool resultIsOK)
        {
            System.Console.WriteLine(resultIsOK ? "OK" : "not OK");
        }
    }
    

    Which is pretty much the same as your example except modified to compile. Compiling it and then running it through the decompiler, results in this:

    internal class Program
    {
        public Program()
        {
        }
    
        private static int CalculateSomethingExpensive()
        {
            return (new Random()).Next(100);
        }
    
        [Conditional("DEBUG")]
        private static void CheckResult(bool resultIsOK)
        {
            Console.WriteLine((resultIsOK ? "OK" : "not OK"));
        }
    
        public static void Main(string[] args)
        {
        }
    }
    

    You can see that the only difference is that the CheckResult call is removed from Main. This means that the full call is removed. Even if CheckResult or CalculateSomethingExpensive had side-effects, those will be removed.

    The interesting thing is that the methods are still there in the compiled output, just the calls are removed, so don't use [Conditional(Debug)] to hide secrets used during debugging.