Изменяет ли тип значения из оператора using неопределенное поведение? - PullRequest
11 голосов
/ 12 января 2011

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

Согласно разделу 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);
}

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

Ответы [ 4 ]

3 голосов
/ 12 января 2011

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

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

запрещено - с поводом, который кажется очевидным. Почему для структуры Mutable это не разрешено бьет меня. Потому что для класса код допустим и хорошо компилируется ...

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

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

Mario

2 голосов
/ 21 февраля 2013

Это поведение не определено.В языке программирования C # в конце раздела спецификаций C # 4.0 7.6.4 (доступ к элементам) Питер Сестофт заявляет:

Две маркированные точки, указывающие «если полетолько для чтения ... тогда результатом является значение "немного удивительно, когда поле имеет тип структуры, а этот тип структуры имеет изменяемое поле (не рекомендуемая комбинация - см. другие примечания по этому вопросу).

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

Затем он продолжает:

Несколько странно, если бы вместо s была локальная переменная типа структуры, объявленная в операторе using, что также приводит к созданию 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()
        {

        }
    }    

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

2 голосов
/ 12 января 2011

Подводя итог

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

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

Марио

2 голосов
/ 12 января 2011

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...