Подтвердить что ты не робот

Предупреждение компилятора False С#?

Хорошо, это далеко вытянутый угол, на который мы наткнулись, но мне было любопытно.

Рассмотрим следующий код:

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

Какие компиляции просто прекрасны, так в чем проблема? Обратите внимание, что синтаксис => не является проблемой, и он возвращает выражение присваивания, которое, похоже, запутывает компилятор.

4b9b3361

Ответ 1

В первом примере 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 компилируется для Reset и Reset2.

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

Ответ 2

Чтобы ответить на это в чистых терминах С#, значение присваивания присваивается назначению. Мы можем продемонстрировать это так:

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 никогда не читается на самом деле, это просто пустая трата памяти, чтобы иметь его и времени, чтобы назначить ему, и вы должны удалить его как крутильный. (Или, конечно, продолжать развиваться так, чтобы появился еще не закодированный вариант, когда он используется).