错误的 C# 编译器警告?

False C# compiler warning?

好吧,这是我们偶然发现的一个牵强的角落案例,但它让我很好奇。

考虑以下代码:

public class Foo
{
    private int foo;
    public int Reset() => foo = 0; //remember, assignment expressions
                                   //return something!
}

这段代码可以编译吗?

不,如果您在所有警告上均未通过,则不会;你会收到 member foo is assigned but never used 警告。

此代码在所有方面都等同于:

public class Foo
{
    private int foo;
    public int Reset() { foo = 0; return foo; }
}

哪个编译正常,那么这里有什么问题呢?请注意,=> 语法不是问题,它返回的赋值表达式似乎让编译器感到困惑。

在第一个示例中,foo 已分配,但从未读取。 0 被分配给 foo,然后 0 被返回,不管 foo 的值是什么(例如,如果另一个线程同时改变它)。

在第二个例子中,foo被赋值,然后从中读取。如果同时有另一个线程修改了foo,则返回修改后的值,而不是0.

您可以通过比较已编译的 IL 来了解实际情况。给定以下代码:

public class Foo
{
    private int foo;
    public int Reset() { return (foo = 0); }
    public int Reset2() { foo = 0; return foo; }
}

以下 IL 是为 ResetReset2.

编译的
.method public hidebysig 
    instance int32 Reset () cil managed 
{
    .maxstack 3
    .locals init (
        [0] int32
    )

    IL_0000: ldarg.0
    IL_0001: ldc.i4.0
    IL_0002: dup
    IL_0003: stloc.0
    IL_0004: stfld int32 Foo::foo
    IL_0009: ldloc.0
    IL_000a: ret
}

.method public hidebysig 
    instance int32 Reset2 () cil managed 
{
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldc.i4.0
    IL_0002: stfld int32 Foo::foo
    IL_0007: ldarg.0
    IL_0008: ldfld int32 Foo::foo
    IL_000d: ret
}

Reset中,我们只存储到Foo::foo(stfld).

Reset2中,你可以看到我们既向它存储又从它加载(stfld + ldfld)。

要用纯 C# 术语回答这个问题,赋值表达式的值就是分配的值。我们可以这样证明:

public class Foo
{
    public int Bar
    {
        get { return 2; }
        set { /* do nothing */ }
    }
}

/* … */

Foo foo = new Foo();
Console.WriteLine(foo.Bar = 23);

这会将 23 打印到控制台,因为 foo.Bar = 23 的值是 23,即使 foo.Bar 的值始终是 2

大多数时候,这样做的唯一效果是性能上的小提升,因为大多数时候 属性 或字段的值将是分配给它的值,但我们可以重用我们已有的本地值,而不是访问字段的 属性。

这里的警告不仅在技术上是正确的,而且很有用:因为 foo 从来没有真正被读取过,拥有它只是浪费内存和分配给它的时间,你应该删除它作为废话。 (或者,当然,继续开发,以便出现尚未编码的情况)。