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