【问题标题】:C# !Conditional attribute?C#!条件属性?
【发布时间】:2012-01-04 00:53:02
【问题描述】:

C# 有没有 Conditional (!Conditional, NotConditional, Conditional(!)) 属性吗?


我知道 C# 有一个 Conditional attribute:

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

相当于1到:

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

但在这种情况下,我想要 inverse 行为(您必须专门选择退出):

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

这让我尝试:

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

无法编译。并且:

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

无法编译。并且:

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

无法编译,因为它只是一厢情愿。

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

【问题讨论】:

  • 不。请注意,[Conditional(...)] 不等同于此;它也会删除调用点。
  • @SLaks:我正在写一个包含这一点的答案:)
  • 这似乎是一个有用的功能。在 GitHub 上打开了一个关于它的功能请求:github.com/dotnet/csharplang/issues/2742

标签: c# conditional-statements conditional-compilation


【解决方案1】:

首先,拥有Conditional 属性等同于在方法中拥有#if。考虑:

ShowDebugString(MethodThatTakesAges());

ConditionalAttribute 的真实行为下,MethodThatTakesAges 不会被调用 - 整个调用包括参数评估都从编译器中移除。

当然,另一点是它依赖于调用者编译时的编译时预处理器符号,而不是方法 :)

但是不,我不相信这里有任何东西可以满足您的需求。我刚刚检查了the C# spec section which deals with conditional methods and conditional attribute classes,里面没有任何迹象表明存在任何此类机制。

【讨论】:

  • 你能干净利落地得到的是下面SLAKS的解决方案。
【解决方案2】:

没有。

相反,你可以写

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

请注意,与 [Conditional] 不同,这将取决于符号在您的程序集中的存在,而不是在调用者的程序集中。

【讨论】:

  • 这实际上是最好的解决方案,但是我强烈建议使用长随机哈希而不是 FALSE,它可以在某些程序集中设置。
  • 大多数情况下它会起作用,但有一点需要注意。在回调中使用属性方法时,此代码将无法编译。
【解决方案3】:

确实我们不能'NOT' ConditionalAttribute,但我们可以'NOT' 如下所示的条件。

// 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"));
    }
  }
}

现在具体方法的编译取决于包含该文件的项目的条件编译符号的定义。

项目属性 -> 构建 -> 条件编译符号

条件编译符号

指定执行条件编译的符号。 用分号 (";") 分隔符号。有关详细信息,请参阅 /define(C# 编译器选项)。

如何使用演示代码:

  • 新建 CSharp 类文件并复制粘贴以上代码;
  • 从您的代码中调用 Demonstration.NotCondition.Test(),最好在 Program.Main() 方法的开头;
  • 并从项目设置中设置或删除“DUMMY”编译条件符号以查看行为差异;

如果我们没有在项目设置中设置编译条件符号“DUMMY”,当项目运行时输出窗口会显示如下:

GetText(!dummy)
called ShowDebugStringNOTDUMMY(!dummy)

否则,如果我们在工程设置中设置了编译条件符号“DUMMY”,工程运行时输出窗口会显示如下:

GetText(dummy)
called ShowDebugStringDUMMY(dummy)
  • 注意:项目条件编译符号仅对该项目内的文件可见,而对某些引用的项目文件中的文件不可见。

希望这可以帮助您解决问题;)

【讨论】:

  • -1 不错的尝试,但这并不总是有效,因为这不取决于调用者的编译时预处理器符号(特别是如果调用者在另一个程序集上,或者在相同的程序集)。
  • 是的,它取决于编译时所有程序集的调用者的编译器上下文(条件 #if 必须在调用者上下文中,如 c# 标准所说)。从来没有说过如果我们将条件 #if 放在另一个程序集中它会起作用,那不是 OP 的问题。所以如果我可以问为什么投反对票?
  • 我知道这是一个很老的问题,但以前没有人回答过你:OP 要求一个否定的ConditionalAttribute,而你的回答不满足这个要求,看来你知道它并不严格,而是决定假设 OP 实际上并不意味着他想要一个否定的ConditionalAttribute,而且很明显他并没有要求一个否定的ConditionalAttribute。也就是说,对于某些人来说,我认为您的回答很有用,但如果您正在寻找否定的ConditionalAttribute,则不是。
  • 请阅读我回答的第一句话。为了让编译器完全从代码中删除对此类函数和函数的调用(并且不更改调用者的原始代码),我们必须否定条件,然后在 Conditional 属性中使用否定条件定义。所有其他选择都不会删除对此类函数的调用或使调用者代码的逻辑复杂化,这违背了 Conditional 属性的目的。即编写了一些调试函数来测试某些东西,但是当我构建发布时,我不希望该函数及其对它的所有引用存在于项目的编译发布版本中。
  • @SoLaR:问题是,除非您在调用上下文中特别指定#define NOT_DUMMY,否则永远不会调用ShowDebugStringNOTDUMMY。我也尝试过您的方法来解决不存在的可否定Conditional,但它不起作用。
【解决方案4】:

只需加上我的 2 美分,三年后 :-) ... 我使用 [Conditional("DEBUG")] 方法设置 IsDebugMode 属性来检查相反的情况。哈克,但它有效:

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

【讨论】:

  • 你到底为什么要这样做?为什么不直接使用 #if DEBUG return true; #else return false; #endif 之类的东西?
  • 看看这个关于使用编译指示标签与条件属性的优点的深入讨论。在一条评论中解释太多了:stackoverflow.com/questions/3788605/…
  • 我本来打算写这样的,很高兴你为我做了,但奇怪的是这个答案在下面。但是,如果此代码位于不同的程序集中,我认为它不起作用,因为是否调用“CheckDebugMode”的决定是在您组装它时做出的,而不是在您调用它时做出的。从这个意义上说,它非常类似于使用#if 来检查它。
  • 有趣的一点,虽然我个人没有遇到过这个问题——我尽量避免单独构建的程序集相互引用以避免 DLL Hell。一种方法是将 SoA 与程序集之间的 WCF 调用一起使用。是的,答案与#if 非常相似,但这里只是作为示例的众多优点之一:如果您进行代码清理,那么 IDE 将删除“未使用”的 using 子句,如果它们仅用于灰色的编译指示部分。
  • CLR 现在内联静态只读原始类型,因此如果您将布尔标志更改为静态只读并在其在任何地方(例如在 Main 或 Application_Start 等中)使用之前通过反射设置它,jit 将完全删除未使用的条件方法中的代码路径。可能只剩下return语句,方法调用将被完全消除!
【解决方案5】:
#ifndef ShowDebugString
#define RemoveSDS
#endif

?

编辑:更多说明。如果 ShowDebugString 被定义 Conditional["ShowDebugString"] 将被调用。如果未定义 ShowDebugString,将调用Conditional["RemoveSDS"]

【讨论】:

  • 这需要在 calling 代码中 - 在需要该功能的每个文件中。这与 IMO 的要求有很大不同。
  • @JonSkeet +1 提到了全局修复的价值,而不是要求所有人更改所有现有代码 (stackoverflow.com/questions/244246/c-alias-a-class-name)
  • 最佳解决方案可能是在项目级别添加一个新定义,该定义具有每个构建配置的正确/预期值。
【解决方案6】:

我喜欢@Heliac 提到的方法,并为它创建了一个辅助类:

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

    static Build()
    {
        CheckDebugMode();
    }

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

虽然DEBUG 的用处不是立竿见影

这可以让你做类似的事情

static readonly bool UseCaching = !Build.IsDebug;

【讨论】:

    【解决方案7】:

    NET 框架标准库注释参考没有说明任何内容。所以恐怕你得自己动手了!

    【讨论】:

    • 实际上我很想这样做,但我不知道如何(甚至可能)从 c# 中的字符串评估预处理器标志。像[Conditional("SOME_FLAG", false)] 这样的东西会很好。
    【解决方案8】:

    受@SLaks 回答的启发,我想出了以下解决方案来解决缺少否定条件的问题。请注意,就像他们的回答一样,[Conditional] 的评估将取决于您定义它的文件中存在的相应预处理器符号,而不是调用符号,即你可能想要也可能不想要的东西。

    这个想法是,您需要一个预处理器符号,它是在项目范围内定义的(或您定义的),例如NET_STANDARD。然后你需要另一个肯定没有定义的预处理器符号,比如THIS_IS_JUST_SOME_RANDOM_STRING_THAT_IS_NEVER_DEFINED。有了这些前提条件,您可以构建以下解决方法:

    #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
    }
    

    现在您有一些const strings 可用于[Conditional] 属性,不需要调用者定义相应的预处理器符号。这意味着这种方法也适用于您在上述代码开头创建的任何#define(这是我需要的)。然后可以将这些用于您的方法:

    [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()
    {
       //...
    }
    

    虽然设置起来有点乏味,但这种方法的一个很好的副作用是,您可以在一个单独的文件中定义所有样板代码一次,以免妨碍您的主代码,或者在多个地方使用它。如果您只需要(或想要)在一个地方使用该功能,您当然也可以在同一个文件或类中定义所有字符串。

    奖励:现在通过在[Conditional("Attribut")] 中输入错误的字符串,不再那么容易搞砸了。

    【讨论】:

      猜你喜欢
      • 2018-09-17
      • 2018-05-03
      • 2011-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-02
      相关资源
      最近更新 更多