Ian Boyd
Ian Boyd

Reputation: 256581

C# !Conditional attribute?

Does C# have a not Conditional (!Conditional, NotConditional, Conditional(!)) attribute?


i know C# has a Conditional attribute:

[Conditional("ShowDebugString")]
public static void ShowDebugString(string s)
{
   ...
}

which is equivalent1 to:

public static void ShowDebugString(string s)
{
#if ShowDebugString
   ...
#endif
}

But in this case i want the inverse behavior (you have to specifically opt out):

public static void ShowDebugString(string s)
{
#if !RemoveSDS
   ...
#endif
}

Which leads me to try:

[!Conditional("RemoveSDS")]
public static void ShowDebugString(string s)
{
   ...
}

which doesn't compile. And:

[Conditional("!RemoveSDS")]
public static void ShowDebugString(string s)
{
   ...
}

which doesn't compile. And:

[NotConditional("RemoveSDS")]
public static void ShowDebugString(string s)
{
   ...
}

which doesn't compile because it's only wishful thinking.

1 Not true, but true enough. Don't make me bring back the Nitpicker's Corner. 🕗

Upvotes: 77

Views: 35348

Answers (8)

SoLaR
SoLaR

Reputation: 886

True we can't 'NOT' ConditionalAttribute, but we can 'NOT' the condition as presented below.

// at the beginning of the code before any using clauses 
// we shall negate the condition.
#if DUMMY
#undef NOT_DUMMY
#else
#define NOT_DUMMY
#endif

using System;
using System.Diagnostics; // required by ConditionalAttribute 

namespace Demonstration
{
  public static class NotCondition
  {

    /// <summary>
    /// The following method is called when 'DUMMY' is defined in project settings
    /// </summary>
    [Conditional("DUMMY")]
    static void ShowDebugStringDUMMY(string s)
    {
      Debug.WriteLine($"called {nameof(ShowDebugStringDUMMY)}({s})");
    }

    /// <summary>
    /// The following method is called when 'DUMMY' is not defined in project settings
    /// </summary>
    [Conditional("NOT_DUMMY")]
    static void ShowDebugStringNOTDUMMY(string s)
    {
      Debug.WriteLine($"called {nameof(ShowDebugStringNOTDUMMY)}({s})");
    }

    /// <summary>
    /// Example that calls to methods that are included in context of conditional method as arguments shall not be executed.
    /// </summary>
    static string GetText(string s)
    {
      Debug.WriteLine($"{nameof(GetText)}({s})");
      return s;
    }

    public static void Test()
    {
      // following method is compiled 
      ShowDebugStringDUMMY(GetText("dummy"));
      ShowDebugStringNOTDUMMY(GetText("!dummy"));
    }
  }
}

Now compile of specific methods depends on definition of Conditional Compilation Symbols for the project that is including this file.

Project Properties -> Build -> Conditional compilation symbols

Conditional compilation symbols

Specifies symbols on which to perform conditional compilation. Separate symbols with a semi-colon (";"). For more information, see /define (C# Compiler Options).

How to use demonstration code:

  • Create new CSharp class file and copy paste above code;
  • Call from your code the Demonstration.NotCondition.Test(), preferably at the beginning of Program.Main() method;
  • And set or remove "DUMMY" compile condition symbol from project setting to see behavioral difference;

If we HAVE NOT set compile condition symbol "DUMMY" in project settings, when project run the Output Window shall display the following:

GetText(!dummy)
called ShowDebugStringNOTDUMMY(!dummy)

Otherwise, if we HAVE set compile condition symbol "DUMMY" in project settings, when project run the Output Window shall display the following:

GetText(dummy)
called ShowDebugStringDUMMY(dummy)
  • NOTE: the project conditional compile symbols are only visible by files inside that project, and not by files in some referenced project files.

hope this helps you solve your problem ;)

Upvotes: 18

v01pe
v01pe

Reputation: 1136

Inspired by @SLaks answer, I came up with the following solution to work around the lack of a negative conditional. Be aware that, like their answer, the evaluation of the [Conditional] will be depending on the respective preprocessor symbol(s) being present in the file where you define this, rather than the calling one, which is something you may or may not want.

The idea is, that you'll need a preprocessor symbol, that is defined project wide (or that you defined), e.g. NET_STANDARD. Then you need another preprocessor symbol that is for sure not defined, like THIS_IS_JUST_SOME_RANDOM_STRING_THAT_IS_NEVER_DEFINED. With these preconditions, you can build the following workaround:

#if ShowDebugString
#undef HideDebugString
#else
#define HideDebugString
#endif

#define SOME_OTHER_CONDITION

public static class Conditional
{
    private const string TRUE = "NET_STANDARD"; //pick one that is always be defined in your context
    private const string FALSE = "THIS_IS_JUST_SOME_RANDOM_STRING_THAT_IS_NEVER_DEFINED";

#if ShowDebugString
    public const string ShowDebugString = TRUE;
#else
    public const string ShowDebugString = FALSE;
#endif

#if HideDebugString
    public const string HideDebugString = TRUE;
#else
    public const string HideDebugString = FALSE;
#endif

#if SOME_OTHER_CONDITION
    public const string SOME_OTHER_CONDITION = TRUE;
#else
    public const string SOME_OTHER_CONDITION = FALSE;
#endif
}

Now you have some const strings that you can use for the [Conditional] attributes, that don't require the caller to have the respective preprocessor symbols defined. This means this approach will also work for any #define that you make at the start of the code above (which is what I needed). These can then be used for your methods:

[Conditional(Conditional.ShowDebugString)]
public static void ShowDebugString(string s)
{
   //...
}

[Conditional(Conditional.HideDebugString)]
public static void ShowReleaseString(string s)
{
   //...
}

[Conditional(Conditional.SOME_OTHER_CONDITION)]
public static void SomeOtherMethod()
{
   //...
}

While a little tedious to set up, a nice side effect of this approach is, that you can define all the boilerplate code in a separate file once, to not obstruct your main code, or to use it in multiple places. If you just need (or want) that functionality in one spot, you can of course also define all the strings in the same file or class.

Bonus: now it's less easy to screw up, by mistyping the string in the [Conditional("Attribut")].

Upvotes: 1

Ramon Smits
Ramon Smits

Reputation: 2618

I liked the approach @Heliac mentioned and made a helper class for it:

class Build
{
    public static bool IsDebug { get; private set; }

    static Build()
    {
        CheckDebugMode();
    }

    [System.Diagnostics.Conditional("DEBUG")]
    static void CheckDebugMode()
    {
        IsDebug = true;
    }
}

Although the usefulness for DEBUG isn't immediately

This allows you to do things like

static readonly bool UseCaching = !Build.IsDebug;

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1499770

First, having the Conditional attribute is not equivalent to having #if inside the method. Consider:

ShowDebugString(MethodThatTakesAges());

With the real behaviour of ConditionalAttribute, MethodThatTakesAges doesn't get called - the entire call including argument evaluation is removed from the compiler.

Of course the other point is that it depends on the compile-time preprocessor symbols at the compile time of the caller, not of the method :)

But no, I don't believe there's anything which does what you want here. I've just checked the C# spec section which deals with conditional methods and conditional attribute classes, and there's nothing in there suggesting there's any such mechanism.

Upvotes: 62

Riegardt Steyn
Riegardt Steyn

Reputation: 5701

Just adding my 2 cents, three years down the line :-) ... I use a [Conditional("DEBUG")] method to set an IsDebugMode property to check the reverse. Hacky, but it works:

private bool _isDebugMode = false;
public bool IsDebugMode
{
    get
    {
        CheckDebugMode();
        return _isDebugMode;
    }
}

[Conditional("DEBUG")]
private void CheckDebugMode()
{
    _isDebugMode = true;
}

private void DisplaySplashScreen()
{
    if (IsDebugMode) return;

    var splashScreenViewModel = new SplashScreenVM(500)
    {
        Header = "MyCompany Deals",
        Title = "Main Menu Test",
        LoadingMessage = "Creating Repositories...",
        VersionString = string.Format("v{0}.{1}.{2}",
            GlobalInfo.Version_Major, GlobalInfo.Version_Minor, GlobalInfo.Version_Build)
    };

    SplashScreenFactory.CreateSplashScreen(splashScreenViewModel);
}

Upvotes: 18

SLaks
SLaks

Reputation: 887195

Nope.

Instead, you can write

#if !ShowDebugString
[Conditional("FALSE")]
#endif

Note that unlike [Conditional], this will be determined by the presence of the symbol in your assembly, not in your caller's assembly.

Upvotes: 60

Kyle W
Kyle W

Reputation: 3752

#ifndef ShowDebugString
#define RemoveSDS
#endif

?

edit: For more clarification. If ShowDebugString is defined Conditional["ShowDebugString"] will be called. If ShowDebugString is not defined, Conditional["RemoveSDS"] will be called.

Upvotes: 5

Roy Dictus
Roy Dictus

Reputation: 33139

The NET framework standard library annotated reference doesn't state any. So I'm afraid you'll have to roll your own!

Upvotes: 0

Related Questions