C# !Conditional attribute?
Asked Answered
C

8

77

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. πŸ•—

Cessation answered 22/11, 2011 at 16:31 Comment(3)
Nope. Note that [Conditional(...)] is not equivalent to that; it removes callsites as well. – Fabre
@SLaks: I was writing an answer including that very point :) – Tobar
It seems like a useful feature to add. Opened a feature request about it on GitHub: github.com/dotnet/csharplang/issues/2742 – Chequer
T
61

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.

Tobar answered 22/11, 2011 at 16:35 Comment(1)
The closest you can cleanly get is the solution of SLAKS below. – Partee
F
60

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.

Fabre answered 22/11, 2011 at 17:0 Comment(2)
This is actually the best solution, however I strongly recommend using a long random hash instead of FALSE, which could be set in some assembly. – Partee
Mostly it will work, but there is caveat. This code will fail to compile, when attributed method is used in callback. – Sheryl
W
18

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);
}
Whole answered 7/2, 2014 at 10:49 Comment(5)
Why on earth would you do this? Why not just use something like #if DEBUG return true; #else return false; #endif? – Iglesias
Have a look at this in-depth discussion on the merits of using pragma tags vs conditional attributes. It's too much to explain in one comment: #3789105 – Whole
I was going to write something like this, I'm glad you did it for me, but it's strange that this answer is so below. I don't think it works if this code is in a different assembly though, because the decision if the "CheckDebugMode" is called or not is made when you assemble it, not when you call it. In this sense, it is pretty similar to using an #if to check it. – Classicist
Interesting point, though I haven't personally had this problem - I try to avoid separately built assemblies referencing each other to avoid DLL Hell. One approach is to use SoA with WCF calls between assemblies. Yes, the answer is pretty similar to an #if, but here's just one of many advantages as an example: if you do code cleanup, then the IDE will remove "unused" using clauses if they are only used in if the greyed out pragma section. – Whole
CLR now inlines static readonly primitive types, so if you change bolean flag to static readonly and set it via reflection before its used anywhere (for example in Main or Application_Start etc.) jit will completly remove unused conditonal code paths in your methods. probably there will be left only return statement and method call will be eliminated completely! – Medawar
H
18

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 ;)

Held answered 13/11, 2014 at 0:9 Comment(6)
-1 Nice try, but this won't always work since this does not depends on the compile-time preprocessor symbols of the caller (especially if the caller is on another assembly, or another module inside the same assembly). – Zoila
Yes it depends on the compiler context of caller for all assemblies at the compile time (condition #if has to be in callers context as c# standard says). Never said it will work or not if we put condition #if in another assembly, and that wasn't OP's question. So if I may ask why down vote? – Held
I know this is a really old question, but no one answered you previously: the OP asked for a negated ConditionalAttribute, and your answer doesn't satisfy that request, and it seems that you know it doesn't strictly, but have instead decided to assume OP didn't actually mean that he wanted a negated ConditionalAttribute and it's somehow clear that he wasn't requesting a negated ConditionalAttribute. That said, for some people, I think your answer is useful, but not if you're looking for a negated ConditionalAttribute. – Orthodox
Pls read 1st sentence of my answer. For compiler to remove the calls to such functions and functions entirely from the code, (and without changing callers original code) we have to negate the condition and then use negated condition define in Conditional attribute. All other choices do not remove calls to such functions or complicate the logic of callers code, which defies the purpose of Conditional attribute. ie Wrote some debugging function to test something, but when I build release I do not want that function and all it's references to it to exist in compiled release version of project. – Held
@SoLaR: the problem is, that unless you specifically #define NOT_DUMMY in your calling context, ShowDebugStringNOTDUMMY will never be called. Your approach was something I tried as well to work around the non existing negatable Conditional, but it doesn't work. – Croydon
I've edited answer to cover all possible bases, including the removal of method calls as argument values by removal of calling method in case of FALSE condition (GetText() not called when condition is FALSE is not a bug). Now is more clear how to properly negate the compile condition symbol. I hope you can see what is wrong with your code. GL – Held
P
5
#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.

Proline answered 22/11, 2011 at 16:38 Comment(3)
This would need to be in the calling code though - in every file where the feature was required. It's significantly different to what's been requested, IMO. – Tobar
@JonSkeet +1 for mentioning the value of a global fix, rather than requiring everyone to change all existing code (https://mcmap.net/q/186418/-how-do-i-alias-a-class-name-in-c-without-having-to-add-a-line-of-code-to-every-file-that-uses-the-class) – Cessation
Best solution is likely to add a new define at the project level which has the correct/expected value for each build configuration. – Solitaire
J
1

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;
Jennifer answered 9/5, 2020 at 12:20 Comment(0)
M
0

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

Meyerbeer answered 22/11, 2011 at 16:36 Comment(1)
I'd love to do so actually, but I don't know how to (nor if it's even possible to) evaluate a preprocessor flag from a string in c#. Something like [Conditional("SOME_FLAG", false)] would be nice. – Croydon
C
0

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")].

Croydon answered 23/11, 2021 at 8:17 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.