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:
- Can only be OR-ed together.
- Negating using not ! inside conditional is not allowed.
- There is no way to AND-combine them.
- 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