2011-01-11 10 views
11

Это действительно ответвление this question, но я думаю, что он заслуживает собственного ответа.Изменяет тип значения из неопределенного поведения оператора using.

В соответствии с разделом 15.13 в ECMA-334 (на using заявление, ниже упоминается как ресурсосбережение приобретения):

Локальные переменные, объявленные в ресурсосбережение приобретения являются Read- только, и должен включать инициализатор. ошибка времени компиляции происходит, если внедренных попыток изменить заявление этих локальных переменные (через присвоение или ++ и -- операторов) или передать их в качестве 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); 
} 

ли выше фрагмент кода не определено и/или незаконным в C#? Если это законно, какова связь между этим кодом и выдержкой из вышеприведенной спецификации? Если это незаконно, то почему он работает? Есть ли какая-то тонкая лазейка, которая разрешает это, или же факт, что она работает, относится только к простому удачу (так что никогда не следует полагаться на функциональность такого, казалось бы, безобидного кода)?

+0

Вызывается метод, используя назначение? Использует ли он '' '' '' '' '' 'операторов? Передаёт ли он это как параметр 'ref' или' out'? –

+0

@Anon: Это мой вопрос. Вызов метода по типу значения, который изменяет состояние этого значения, по существу, не отличается от назначения, не так ли? Вот почему изменение поля строго запрещено? –

+0

Куда вы придумали этот материал? Примечательно, что свойство тоже не работает. Это всего лишь вызов метода под капотом, что не позволяет Remoting быть причиной. В любом случае. Эти шарлатаны как ошибка. Ну, недостаток. Упоминание имени Эрика Липперта обычно заставляет его посещать его. Готово. –

ответ

2

Я подозреваю, что он компилирует и запускает то, что SetField(int) - вызов функции, а не назначение или ref или out вызов параметра. Компилятор не знает (вообще), будет ли SetField(int) мутировать переменную или нет.

Это выглядит полностью законным согласно спецификации.

И рассмотрите альтернативы. Статический анализ, чтобы определить, будет ли данный вызов функции мутировать значение, явно недопустим в компиляторе C#. Спецификация предназначена для предотвращения этой ситуации во всех случаях.

Другой альтернативой было бы для C# не допускать вызовы методов для переменных типа значения, объявленных в операторе using. Это может быть не плохая идея, так как реализация IDisposable в структуре просто требует неприятностей. Но когда язык C# был впервые разработан, я думаю, что они возлагали большие надежды на использование структур множеством интересных способов (как пример , который вы изначально использовали).

+2

Это звучит разумный, и я склонен согласиться с вами. Но остается вопрос, действительно ли это поведение определено или нет. –

+2

Значение не изменяется с помощью '++' или '--' или' ref' или 'out', поэтому я бы сказал, что это разрешено. Итог: не объявляйте изменчивые структуры. Они сбивают с толку. – dtb

+1

@dtb: Я могу видеть ваши рассуждения; что я не уверен в том, что упоминание этих конкретных операторов и 'ref' и' out' было включено в формулировку спецификации как просто * примеры * того факта, что локальные переменные должны быть только для чтения или как * единственные случаи *, которые запрещены. В любом случае это кажется немного туманным для меня. И хотя я вообще согласен с вами в том, что вы не используете mutable structs, они * do * существуют в BCL (рассмотрите мой теперь отредактированный пример обертывания «List .Enumerator' в' use'). –

3

Я читал стандарт таким образом, что

using(var m = new Mutable()) 
{ 
    m = new Mutable(); 
} 

запрещено - с разумом, которые кажутся obious. Почему для структуры Mutable мне не разрешено бить меня. Потому что для класса код является законным и компилируется отлично ... (тип объекта я знаю ..)

Также я не вижу причины, по которой изменение содержимого типа значения создает угрозу для RA. Кто-то хочет объяснить?

Может быть, кто-то делает syntx проверку просто неправильно стандарт ;-)

Марио

+2

+1 Вот как я его читал. Он не утверждает, что они «неизменяемы», только локальная переменная сама по себе «доступна только для чтения». Однако я не могу комментировать, почему сбой версии struct. – user7116

+1

Причина в том, что это тип значения. Вызов оператора присваивания по типу значения readonly недопустим. Сообщение об ошибке, выпущенное компилятором, просто вводит в заблуждение. Это означает, что в readonly struct вам не разрешено вызывать оператора-получателя члена. –

+0

Я имел в виду этот стиль назначения 'myStruct.Field = ...', а не 'myStruct = ...'. Я так же потерян, как и вы, почему первый запрещен. – user7116

2

Резюмируя

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

Таким образом, в отличие от того, что я написал выше, я бы рекомендовал НЕ используйте функцию для изменения структуры внутри блока использования. Это, похоже, работает, но может перестать работать в будущем.

Mario

2

Это поведение не определено. В The C# Programming language в конце раздела 7.6.4 спецификации C# 4.0 (Access члена) Питер Сестофт заявляет:

Два Маркированных точек заявляющих «если поле только для чтения ... тогда результата является значением» имеют немного удивительный эффект, когда поле имеет тип структуры, и этот тип структуры имеет изменяемое поле (не рекомендуемое сочетание - см. другие примечания по этому вопросу).

Он приводит пример. Я создал свой собственный пример, который отображает более подробно ниже.

Затем он продолжает говорить:

Несколько странно, если вместо s были локальной переменной структуры типа , объявленного в использовании заявлении, которое также имеет эффект создания s неизменны, то s.SetX() обновляет sx, как ожидалось.

Здесь мы видим, что один из авторов признает, что это поведение непоследовательно. В разделе 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() 
     { 

     } 
    }  

Ситуация странно. В нижней строке, избегайте создания readonly изменяемых полей.

+0

Приобретено. Когда переменная struct рассматривается в контексте «только для чтения», методы экземпляра этой переменной не вызываются напрямую, их вызывают в «защитной копии», как я только что сказал в комментарии к вопросу. –

+0

Интересно, что «ошибка», когда переменная в 'use' недостаточно доступна для чтения, связана с тем, что' (IDisposable) s 'boxes' s'. Когда мы 'foreach' через' List <> ', то формально у нас есть что-то вроде' using (var e = theList.GetEnumerator()) {...} 'где' e' - это структура, которая * должна * быть мутирована чтобы сделать «foreach» прогресс. [Метод 'MoveNext()'] (http://msdn.microsoft.com/en-us/library/a3207y01.aspx) мутирует структуру? Я буду исследовать это больше, когда у меня будет больше времени. –