Is modifying a value type from within a using statement undefined behavior?

后端 未结 4 983
予麋鹿
予麋鹿 2021-02-08 07:08

This one\'s really an offshoot of this question, but I think it deserves its own answer.

According to section 15.13 of the ECMA-334 (on the using statement,

相关标签:
4条回答
  • 2021-02-08 07:40

    I suspect the reason it compiles and runs is that SetField(int) is a function call, not an assignment or ref or out parameter call. The compiler has no way of knowing (in general) whether SetField(int) is going to mutate the variable or not.

    This appears completely legal according to the spec.

    And consider the alternatives. Static analysis to determine whether a given function call is going to mutate a value is clearly cost prohibitive in the C# compiler. The spec is designed to avoid that situation in all cases.

    The other alternative would be for C# to not allow any method calls on value type variables declared in a using statement. That might not be a bad idea, since implementing IDisposable on a struct is just asking for trouble anyway. But when the C# language was first developed, I think they had high hopes for using structs in lots of interesting ways (as the GetEnumerator() example that you originally used demonstrates).

    0 讨论(0)
  • 2021-02-08 07:50

    To sum it up

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

    So in contrast to what I wrote above, I would recommend to NOT use a function to modify a struct within a using block. This seems wo work, but may stop to work in the future.

    Mario

    0 讨论(0)
  • 2021-02-08 07:53

    I would read the standard in such a way that

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

    is forbidden - with reason that seem obious. Why for the struct Mutable it is not allowed beats me. Because for a class the code is legal and compiles fine...(object type i know..)

    Also I do not see a reason why changing the contents of the value type does endanger the RA. Someone care to explain?

    Maybe someone doing the syntx checking just misread the standard ;-)

    Mario

    0 讨论(0)
  • 2021-02-08 07:55

    This behavior is undefined. In The C# Programming language at the end of the C# 4.0 spec section 7.6.4 (Member Access) Peter Sestoft states:

    The two bulleted points stating "if the field is readonly...then the result is a value" have a slightly surprising effect when the field has a struct type, and that struct type has a mutable field (not a recommended combination--see other annotations on this point).

    He provides an example. I created my own example which displays more detail below.

    Then, he goes on to say:

    Somewhat strangely, if instead s were a local variable of struct type declared in a using statement, which also has the effect of making s immutable, then s.SetX() updates s.x as expected.

    Here we see one of the authors acknowledge that this behavior is inconsistent. Per section 7.6.4, readonly fields are treated as values and do not change (copies change). Because section 8.13 tells us using statements treat resources as read-only:

    the resource variable is read-only in the embedded statement,

    resources in using statements should behave like readonly fields. Per the rules of 7.6.4 we should be dealing with a value not a variable. But surprisingly, the original value of the resource does change as demonstrated in this example:

        //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()
            {
    
            }
        }    
    

    The situation is bizarre. Bottom line, avoid creating readonly mutable fields.

    0 讨论(0)
提交回复
热议问题