Подтвердить что ты не робот

Изменяет тип значения из инструкции 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 и/или незаконным в С#? Если это законно, какова связь между этим кодом и выдержкой из вышеприведенной спецификации? Если это незаконно, почему это работает? Есть ли какая-то тонкая лазейка, которая разрешает это, или же факт, что она работает, относится только к простому удачу (так что никогда не следует полагаться на функциональность такого, казалось бы, безобидного вида кода)?

4b9b3361

Ответ 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 изменяемых полей.