开发者

self mutating readonly struct field

im aware of Eric Lippert's blog post about situation but i think this is a different situation because the field immutates itself rather then its field. How could you explain calling MoveNext() if Enumerator was readonly doesnt show any effect and output is always 0 ?

 clas开发者_如何学编程s SomeClass
    {
        private List<int> list;
        private [readonly] List<int>.Enumerator enumerator;

        public SomeClass()
        {
            list = new List<int>() { 1, 2, 3 };
            enumerator = list.GetEnumerator();
        }

        public int ReadValue()
        {
            if (enumerator.MoveNext())
                return enumerator.Current;
            return -1;

        }
    }
static void Main()
    {
        SomeClass c = new SomeClass();
        int value;
        while ((value = c.ReadValue()) > -1)
            MessageBox.Show(value.ToString());
    }


I think this is a different situation

You are incorrect. This is exactly the situation I describe in my blog article that you reference.

To repeat my analysis here: every non-static method call on a struct takes a "ref" parameter called "this". We don't show the "ref this" parameter in the parameter list but it is there, generated by the compiler for you. Anything passed by ref must be a variable. Since a readonly variable could be (and in this case, will be) mutated by a call, we must ensure that a readonly variable is never passed by ref. When you call a method on a readonly struct, we make a temporary variable, copy the struct to the temporary variable, pass a ref to the temporary as "this" on the method call, and then discard the temporary. This explains the behaviour you are seeing; every mutation caused by MoveNext is happening on a copy which is then discarded.

Can you explain why this situation -- which is exactly the same as the one I describe in my blog -- is any different? What do you believe is different about enumerators that makes them special?


If I understand Eric Lippert's blog post on this that you posted, the statement

if(enumerator.MoveNext())

first makes a copy of enumerator, which the MoveNext() is executed on the copy. That copy dies and the next line:

return enumerator.Current;

returns the Current of the original enumerator, not the copy, which is why you always get 0


The readonly keyword gives the C# compiler a hard time. It cannot know whether or not MoveNext() and Current have side-effects that violates the readonly contract. MoveNext() certainly does. So to generate valid code, it must create a copy of the iterator value. It happens twice, once for the MoveNext() method call, again when reading the Current property. You can easily see this by running ildasm.exe on your program, the copy is named CS$0$0001 in the Debug build.

It would be nice if the compiler at least generated a warning for this code. Very hard to do accurately though, it really does need to know whether or not the member has a side effect. It doesn't know. There are way too many struct types with property getters that don't have a side effect so just always generating the warning isn't feasible.

The kind of feature that would be required to let it know is the const keyword as it is used in C++. A method can be declared const to indicate that it doesn't alter the state of the object. I seriously doubt that feature will ever make it into the C# language though, writing const-correct code is not so easy and, frankly, a bit of a pita.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜