ConditionalAttribute 是否应该删除整行,或者只是方法调用?

发布于 2024-09-25 04:00:17 字数 1629 浏览 0 评论 0原文

根据 ConditionalAttribute< 上的文档/a> 类:

将 ConditionalAttribute 应用于 方法向编译器表明 不应该调用该方法 编译成微软中间版 语言 (MSIL) 除非有条件 关联的编译符号 定义了 ConditionalAttribute。

对我来说,这意味着 Conditional 属性仅改变单个方法调用级别的行为。但请考虑以下代码片段:

class InstanceType
{
    public InstanceType DoSideEffects()
    {
        Console.WriteLine("Side effects!");
        return this;
    }

    public InstanceType DoMoreSideEffects()
    {
        Console.WriteLine("More side effects!");
        return this;
    }

    [Conditional("DEBUG")]
    public void ConditionalMethod()
    {
        Console.WriteLine("Conditional method run.");
    }
}

class Program
{
    static void Main()
    {
        var x = new InstanceType();

        // The compiler appears to strip out this entire line
        // in a Release build.
        x.DoSideEffects().DoMoreSideEffects().ConditionalMethod();

        var y = new InstanceType();

        // When each method call appears on its own line,
        // the first two methods are included as expected.
        y.DoSideEffects();
        y.DoMoreSideEffects();
        y.ConditionalMethod();
    }
}

比较调试和发布版本的输出:

DEBUG                    RELEASE
Side effects!            Side effects!
More side effects!       More side effects!
Conditional method run.
Side effects!
More side effects!
Conditional method run.

此行为是否在某处指定? 我曾认为,除了读取“条件方法”的行之外,两个版本都应该具有相同的输出跑步。”

According to the documentation on the ConditionalAttribute class:

Applying ConditionalAttribute to a
method indicates to compilers that a
call to the method should not be
compiled into Microsoft intermediate
language (MSIL) unless the conditional
compilation symbol that is associated
with ConditionalAttribute is defined.

To me this is saying that the Conditional attribute only alters behavior at the individual method call level. But consider the following code snippet:

class InstanceType
{
    public InstanceType DoSideEffects()
    {
        Console.WriteLine("Side effects!");
        return this;
    }

    public InstanceType DoMoreSideEffects()
    {
        Console.WriteLine("More side effects!");
        return this;
    }

    [Conditional("DEBUG")]
    public void ConditionalMethod()
    {
        Console.WriteLine("Conditional method run.");
    }
}

class Program
{
    static void Main()
    {
        var x = new InstanceType();

        // The compiler appears to strip out this entire line
        // in a Release build.
        x.DoSideEffects().DoMoreSideEffects().ConditionalMethod();

        var y = new InstanceType();

        // When each method call appears on its own line,
        // the first two methods are included as expected.
        y.DoSideEffects();
        y.DoMoreSideEffects();
        y.ConditionalMethod();
    }
}

Compare the outputs of Debug and Release builds:

DEBUG                    RELEASE
Side effects!            Side effects!
More side effects!       More side effects!
Conditional method run.
Side effects!
More side effects!
Conditional method run.

Is this behavior specified somewhere? I had thought that both builds were supposed to have the same output except for the lines reading "Conditional method run."

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

痕至 2024-10-02 04:00:17

有趣的功能:-)我从来没有注意到这一点。

我看了IL。我相信,这并不能解释行为(编译过程),但它无论如何都记录了结果。

整个 C# 代码行在 IL 中明显被遗漏:

  • 在 DEBUG 编译中创建一个新对象
    创建(x 变量)、存储
    位于位置 0 并已加载。然后
    三种方法的应用
    依次:DoSideEffects()、
    DeMoreSideEffects() 和
    ConditionalMethod()
  • 在 RELEASE 编译中,仍然会创建变量,但由于不需要它,因此会立即弹出。相反,y 变量存储在位置 0 并加载。

对我来说,这看起来真的像一个错误。似乎可以在 IL 中排除 ConditionalMethod() 调用。但看来你是对的,整条线都被遗漏了。

// DEBUG compilation
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       58 (0x3a)
  .maxstack  1
  .locals init (class ConsoleApplication3.InstanceType V_0,
           class ConsoleApplication3.InstanceType V_1)
  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_000d:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_0012:  callvirt   instance void ConsoleApplication3.InstanceType::ConditionalMethod()
  IL_0017:  nop
  IL_0018:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_001d:  stloc.1
  IL_001e:  ldloc.1
  IL_001f:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_0024:  pop
  IL_0025:  ldloc.1
  IL_0026:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_002b:  pop
  IL_002c:  ldloc.1
  IL_002d:  callvirt   instance void ConsoleApplication3.InstanceType::ConditionalMethod()
  IL_0032:  nop
  IL_0033:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_0038:  pop
  IL_0039:  ret
} // end of method Program::Main

// RELEASE compilation
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       33 (0x21)
  .maxstack  1
  .locals init ([0] class ConsoleApplication3.InstanceType y)
  IL_0000:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_0005:  pop
  IL_0006:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_000b:  stloc.0
  IL_000c:  ldloc.0
  IL_000d:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_0012:  pop
  IL_0013:  ldloc.0
  IL_0014:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_0019:  pop
  IL_001a:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_001f:  pop
  IL_0020:  ret
} // end of method Program::Main

Interessting feature :-) I've never noticed that.

I've taken a look at the IL. This doesn't explain the behaviour (the compilation process), but it documents the result anyway, I believe.

The whole C# code line is clearly left out in the IL:

  • In the DEBUG compilation a new object
    is created (the x variable), stored
    at location 0 and loaded. Afterwards
    the three methods are applied
    successively: DoSideEffects(),
    DeMoreSideEffects(), and
    ConditionalMethod()
  • In the RELEASE compilation the variable still gets created, but since it is not needed, it is immediately pop'ed. Instead the y variable is stored at location 0 and loaded.

To me, this looks like a bug, really. It seems that it would have been possible to just exclude the ConditionalMethod() call in the IL. But it seems that you are right, that the whole line is left out.

// DEBUG compilation
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       58 (0x3a)
  .maxstack  1
  .locals init (class ConsoleApplication3.InstanceType V_0,
           class ConsoleApplication3.InstanceType V_1)
  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_000d:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_0012:  callvirt   instance void ConsoleApplication3.InstanceType::ConditionalMethod()
  IL_0017:  nop
  IL_0018:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_001d:  stloc.1
  IL_001e:  ldloc.1
  IL_001f:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_0024:  pop
  IL_0025:  ldloc.1
  IL_0026:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_002b:  pop
  IL_002c:  ldloc.1
  IL_002d:  callvirt   instance void ConsoleApplication3.InstanceType::ConditionalMethod()
  IL_0032:  nop
  IL_0033:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_0038:  pop
  IL_0039:  ret
} // end of method Program::Main

// RELEASE compilation
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       33 (0x21)
  .maxstack  1
  .locals init ([0] class ConsoleApplication3.InstanceType y)
  IL_0000:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_0005:  pop
  IL_0006:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_000b:  stloc.0
  IL_000c:  ldloc.0
  IL_000d:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_0012:  pop
  IL_0013:  ldloc.0
  IL_0014:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_0019:  pop
  IL_001a:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_001f:  pop
  IL_0020:  ret
} // end of method Program::Main
一瞬间的火花 2024-10-02 04:00:17

抱歉拖了这么旧的帖子,但我刚刚遇到了同样的事情,这是我能找到的关于这个问题的唯一讨论。

我有一种预感,知道发生了什么事。 [Conditional] 正在剥离对 ConditionalMethod() 的调用以及作为传递给它的参数的任何表达式(根据文档,以及上面链接的其他线程)。

我的猜测是隐式 this 参数的处理方式完全相同。在 x.DoSideEffects().DoMoreSideEffects().ConditionalMethod(); 行中,作为 this 传递的表达式为 x.DoSideEffects().DoMoreSideEffects( ) 它被尽职地剥离,消除了副作用。

如果我们重写为伪代码,明确地将 this 作为第一个参数传递给每个方法,它就会变得更加清晰:

ConditionalMethod( DoMoreSideEffects( DoSideEffects( x )));

Sorry drag up such an old post, but I just encountered the same thing and this was the only discussion of this issue that I could find.

I have a hunch as to what is going on. The [Conditional] is stripping the call to ConditionalMethod() as well as any expressions that act as parameters passed to it (as per the documentation, and the other thread linked above).

My guess is that the implicit this parameter is being treated exactly the same way. In the line x.DoSideEffects().DoMoreSideEffects().ConditionalMethod(); the expression that is passed as this is x.DoSideEffects().DoMoreSideEffects() which is dutifully stripped, eliminating the side effects.

If we rewrite into pseudo code where we explicitly pass this as the first parameter to each method it becomes much clearer:

ConditionalMethod( DoMoreSideEffects( DoSideEffects( x )));

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文