AND-combine or negate ConditionaAttribute symbols to avoid MSIL call generation

188 Views Asked by At

I need to do some conditional compilation in C#, but was was also requested to keep global symbols count low. So instead of #if ENABLE_MODULE (And adding a lot of symbols), I mostly do opt-out #if !DISABLE_MODULE.

When profiling core loops I want to have fine control over what things get turned off to have a cleaner look at the results. But even for empty assert calls (#if inside method body), the calls alone sum up significant time. That's why I started using [Conditional] attribute to remove the call altogether and avoid having to enclose everything in #if sections that make code less readable.

But [Conditional] has some caveats:

  1. Can only be OR-ed together.
  2. Negating using not ! inside conditional is not allowed.
  3. There is no way to AND-combine them.
  4. Different from #if, it gets interpreted by the caller's file scope symbol definitions instead of the actual called method file scope symbol definitions.

I have come up with a workaround but it's not pretty and tends to repeat code (see __Convoluted() methods below). Code inside disabled #if sections get treated like comments in Visual Studio (so no name refactors) making it worse, turning into broken commits because of not checking all symbol combinations, etc.

I thought of using some umbrella symbols but that forces me to think everything in positive-OR, and sometimes I want e.g. most asserts but not assert from some module.

Maybe this has a software architecture answer rather than a practical one.

I'm using Unity, and as such can't have fine control over symbols per configuration (debug/release/development build) without doing some building scripts that replace a compiler parameters inside a file (csc.rsp)

My question: Is there a better way to handle this?

Thanks for your time.

I did a bit of homework trying different alternatives and reached some conclusions (following code)

Minimal test case C# .NET console app

Utility file

//ENABLE is a global symbol definition at project level

#if ENABLE && EXTRA
    #define _COMBINED
#endif

#if !ENABLE
    #define _DISABLE
#endif

using System;
using System.Diagnostics;

public static class Utility {

    [Conditional("ENABLE")]
    public static void Call(string msg) {
        Console.WriteLine(msg);
    }

    [Conditional("ENABLE"), Conditional("OTHER")]
    public static void CallOr(string msg) {
        Console.WriteLine(msg);
    }

    [Conditional("_DISABLE")]
    public static void CallNot(string msg) {
        Console.WriteLine(msg);
    }

    public static void CallNotInternal(string msg) {
        CallNot(msg);
    }

    [Conditional("_COMBINED")]
    public static void CallAnd(string msg) {
        Console.WriteLine(msg);
    }

    public static void CallAndInternal(string msg) {
        CallAnd(msg);
    }

#if ENABLE //!_DISABLE
    [Conditional("_NEVER")] // <-- Hack to remove MSIL call.
    public static void CallNotConvoluted(string msg) { }
#else
    public static void CallNotConvoluted(string msg) {
        Console.WriteLine(msg);
    }   
#endif


#if !(ENABLE && EXTRA) //!_COMBINED
    [Conditional("_NEVER")] // <-- Hack to remove MSIL call.
    public static void CallAndConvoluted(string msg) { }
#else
    public static void CallAndConvoluted(string msg) {
        Console.WriteLine(msg);
    }   
#endif


}

Different call combinations

//Works as expected
Utility.Call("Call1");

//Works as expected
Utility.CallOr("CallOr");

//Doesn't work because _DISABLE is not defined in this file
//(although it is defined at Utility)
Utility.CallNot("CallNot");

//Works because _DISABLE is defined at Utility (where CallNotInternal resides)
//BUT would have extra "empty" call
Utility.CallNotInternal("CallNotInternal");

//Doesn't work because _COMBINED is not defined in this file
//(although it is defined at Utility)
Utility.CallAnd("CallAnd");

//Works because _COMBINED is defined at Utility (where CallAndInternal resides)
//BUT would have extra "empty" call
Utility.CallAndInternal("CallAndInternal");

//Works but it seems kind of hacky/bulky
Utility.CallNotConvoluted("CallNotConvoluted");

//Works but it seems kind of hacky/bulky
Utility.CallAndConvoluted("CallAndConvoluted");

The code outputs when global symbols set the following. Notice that CallAnd and CallNot show that Conditional doesn't work as #if does.

ENABLE and EXTRA
----------------
Call1
CallOr
CallAndInternal
CallAndConvoluted



ENABLE
-----
Call1
CallOr



(no symbols)
------------
CallNotInternal
CallNotConvoluted
0

There are 0 best solutions below