Изменяет тип значения из инструкции using undefined?
Это действительно ответвление этого вопроса, но я думаю, что он заслуживает собственного ответа.
В соответствии с разделом 15.13 ECMA-334 (в заявлении using
, ниже называемом приобретением ресурсов):
Локальные переменные, объявленные в получение ресурсов доступно только для чтения и включает инициализатор. ошибка времени компиляции возникает, если встроенный оператор пытается изменить эти локальные переменные (через присвоение или операторы ++
и --
), или передайте их как ref
или out
Параметры.
Это, кажется, объясняет, почему приведенный ниже код является незаконным.
struct Mutable : IDisposable
{
public int Field;
public void SetField(int value) { Field = value; }
public void Dispose() { }
}
using (var m = new Mutable())
{
// This results in a compiler error.
m.Field = 10;
}
Но как насчет этого?
using (var e = new Mutable())
{
// This is doing exactly the same thing, but it compiles and runs just fine.
e.SetField(10);
}
Является ли приведенный выше фрагмент undefined и/или незаконным в С#? Если это законно, какова связь между этим кодом и выдержкой из вышеприведенной спецификации? Если это незаконно, почему это работает? Есть ли какая-то тонкая лазейка, которая разрешает это, или же факт, что она работает, относится только к простому удачу (так что никогда не следует полагаться на функциональность такого, казалось бы, безобидного вида кода)?
Ответы
Ответ 1
Я бы прочитал стандарт таким образом, чтобы
using( var m = new Mutable() )
{
m = new Mutable();
}
запрещено - по той причине, что это кажется подозрительным.
Почему для структуры Mutable мне не разрешено бить меня. Потому что для класса код легален и компилируется отлично... (тип объекта я знаю..)
Также я не вижу причины, по которой изменение содержимого типа значения создает угрозу для RA. Кто-то хочет объяснить?
Возможно, кто-то делает проверку синтаксиса, просто неправильно читает стандарт; -)
Марио
Ответ 2
Я подозреваю, что причина компиляции и запуска заключается в том, что SetField(int)
- вызов функции, а не назначение или ref
или out
вызов параметра. Компилятор не знает (вообще), будет ли SetField(int)
мутировать переменную или нет.
Это выглядит полностью законным в соответствии со спецификацией.
И рассмотрим альтернативы. Статический анализ, чтобы определить, будет ли данный вызов функции мутировать значение, явно недопустим в компиляторе С#. Спецификация предназначена для предотвращения этой ситуации во всех случаях.
Другой альтернативой было бы для С# не разрешать вызовы методов для переменных типа значения, объявленных в инструкции using
. Это может быть не плохая идея, так как реализация IDisposable
в структуре просто требует неприятностей. Но когда язык С# был впервые разработан, я думаю, что они возлагали большие надежды на использование структур множеством интересных способов (как демонстрирует пример GetEnumerator()
, который вы изначально использовали).
Ответ 3
Подводя итог
struct Mutable : IDisposable
{
public int Field;
public void SetField( int value ) { Field = value; }
public void Dispose() { }
}
class Program
{
protected static readonly Mutable xxx = new Mutable();
static void Main( string[] args )
{
//not allowed by compiler
//xxx.Field = 10;
xxx.SetField( 10 );
//prints out 0 !!!! <--- I do think that this is pretty bad
System.Console.Out.WriteLine( xxx.Field );
using ( var m = new Mutable() )
{
// This results in a compiler error.
//m.Field = 10;
m.SetField( 10 );
//This prints out 10 !!!
System.Console.Out.WriteLine( m.Field );
}
System.Console.In.ReadLine();
}
Итак, в отличие от того, что я написал выше, я бы рекомендовал НЕ использовать функцию для изменения структуры в блоке использования. Это похоже на работу, но может перестать работать в будущем.
Марио
Ответ 4
Это поведение undefined. В Язык программирования С# в конце раздела спецификации С# 4.0 7.6.4 (членский доступ) Питер Сестофт утверждает:
Две маркированные точки, указывающие "если поле только для чтения... тогда результат представляет собой значение" имеет слегка удивительный эффект, когда поле имеет тип структуры, и этот тип структуры имеет изменяемое поле (не рекомендуемая комбинация - см. другие примечания по этому вопросу).
Он приводит пример. Я создал свой собственный пример, который отображает более подробно ниже.
Затем он продолжает:
Несколько странно, если вместо этого s была локальной переменной типа struct объявленный в используемом утверждении, что также приводит к неизменяемый, то s.SetX() обновляет s.x, как ожидалось.
Здесь мы видим, что один из авторов признает, что это поведение непоследовательно. В разделе 7.6.4 поля только для чтения рассматриваются как значения и не изменяются (копирование изменяется). Поскольку в разделе 8.13 мы говорим, что использование операторов рассматривает ресурсы как доступные только для чтения:
переменная ресурса доступна только для чтения во встроенной инструкции,
ресурсы в операторах using
должны вести себя как поля для чтения. По правилам 7.6.4 мы должны иметь значение не переменной. Но удивительно, что исходное значение ресурса меняет, как показано в этом примере:
//Sections relate to C# 4.0 spec
class Test
{
readonly S readonlyS = new S();
static void Main()
{
Test test = new Test();
test.readonlyS.SetX();//valid we are incrementing the value of a copy of readonlyS. This is per the rules defined in 7.6.4
Console.WriteLine(test.readonlyS.x);//outputs 0 because readonlyS is a value not a variable
//test.readonlyS.x = 0;//invalid
using (S s = new S())
{
s.SetX();//valid, changes the original value.
Console.WriteLine(s.x);//Surprisingly...outputs 2. Although S is supposed to be a readonly field...the behavior diverges.
//s.x = 0;//invalid
}
}
}
struct S : IDisposable
{
public int x;
public void SetX()
{
x = 2;
}
public void Dispose()
{
}
}
Ситуация странная. В нижней строке избегайте создания readontly изменяемых полей.