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

Хорошо, это очень запутанный случай, на который мы наткнулись, но мне стало любопытно.

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

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

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


person InBetween    schedule 01.11.2017    source источник
comment
Я хотел сказать, что они не равны - ищите ИЛ, но ... ответ там   -  person T.S.    schedule 01.11.2017
comment
Это более короткий случай, когда спецификация компилятора неадекватна. stackoverflow .com/questions/45992172/   -  person Joshua    schedule 01.11.2017


Ответы (2)


В первом примере 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).

person yaakov    schedule 01.11.2017
comment
ах! хорошо, я сделал неправильное предположение, что в выражении присваивания была возвращена левая часть, а что справа. Логично, теперь все ясно. Спасибо. - person InBetween; 01.11.2017
comment
meta.stackoverflow.com/questions/359302/ - person Hans Passant; 14.11.2017

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

person Jon Hanna    schedule 01.11.2017