-5

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 ?

 class 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());
    }
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
TakeMeAsAGuest
  • 957
  • 6
  • 11

3 Answers3

11

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?

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    from the "mutate" i understood mutating the variables reference, not the content. in a situation like readonly object obj, the readonly keyword guards the value of reference-variable, not the object itself. but now i see that for value types, it guards the value. and i cant understand why mutating a struct requires making a new copy. and why mutating a readonly variable doesnt throw exception???? thank you very much about your explanation – TakeMeAsAGuest Apr 10 '11 at 18:33
  • @TakeMeAsAGuest: I don't understand what you mean by "mutating a variable's reference, not the content". A variable has content. That content is a reference, if the variable is of reference type, or a value, if the variable is of value type. That's why reference types and value types are called "reference" types and "value" types. Mutating a read-only variable does not throw an exception because mutating a read-only variable is *impossible*. Mutating a read-only variable of reference type is made illegal at compile time, and mutating a read-only variable of value type mutates a copy. – Eric Lippert Apr 10 '11 at 22:14
  • id like to know the motivation behind this decision. and do you copy the "content of reference variable" too? – TakeMeAsAGuest Apr 11 '11 at 07:02
  • 2
    @TakeMeAsAGuest: Re: motivation: The motivation is to make readonly value types actually be readonly; how else would you do it? Re: do we copy the contents of a ref-typed variable? No. As I said once already, mutating a readonly ref-typed variable is *impossible*. The compiler detects attempts to do so and disallows it at compile time, so there is no point in asking what we do at runtime. The situation never arises because it is impossible. – Eric Lippert Apr 11 '11 at 14:10
  • 1
    forgive me but this concludes what strange is about value types and your decision. i would never let mutate a readonly value, "at least produce some warning" the current behaviour is just strange for me. – TakeMeAsAGuest Apr 12 '11 at 05:35
  • 1
    @TakeMeAsAGuest: OK, then tell me how you would design a compiler for the following situation. You have a mutable value type in a read-only variable. The value type has two methods, DoNotMutate() and Mutate(). Describe how you would design an algorithm that decides when to give the warning based on a call to one of those methods. Do calls to both methods get the warning, even though one does not actually mutate the structure? That seems bad for users. Does only one method call get the warning? If so, how do you decide which one gets it and which one does not? – Eric Lippert Apr 12 '11 at 14:08
  • 1
    @Eric Lippert: if the field is not readonly, "you dont copy" and let the value itself get mutated, but if it is readonly, you silently make a copy. is it smart? question here is not how do you determine if the method mutates or not (we know current spec/compiler/jit whatever, takes every method call mutable because of lack of the const keyword anyway. we miss lots of possible optimizations here sadly) but making silent and strange decisions. – TakeMeAsAGuest Apr 12 '11 at 14:51
  • 2
    @TakeMeAsAGuest: You still haven't said what you'd rather us do, only that you don't like what we actually do. I understand that you don't like it. I don't particularly like it either. What's the alternative behaviour that you'd like to see? – Eric Lippert Apr 12 '11 at 15:08
  • 1
    i already answered, its not up to me, you already take every method calls on structs as mutable. i stated it should give at least a warning. what would i do is giving compile time error. as last resort a warning stating: "you call a method on a readonly struct wich will be taken as mutable. a copy of the struct will be created to give you to play with because we dont want you to mutate the readonly field itself." – TakeMeAsAGuest Apr 14 '11 at 11:48
  • 3
    @TakeMeAsAGuest: OK, so for example, should it be a warning, or an error, or what, for a call to ToString() on a readonly struct variable? That will call ToString() on a copy; if ToString() mutates the variable, it will mutate the copy. Should we tell the user that in a warning, or make it illegal to call ToString() on a readonly struct variable, or what? – Eric Lippert Apr 14 '11 at 13:53
  • @TakeMeAsAGuest: If the value type overrides ToString then no, a call to ToString does not box. Since value types are all sealed, the compiler knows the exact method that a call to an overridden ToString on a value type will resolve to, so there is no need to go through the vtable indirection in the boxed struct. We therefore skip the boxing and call directly. If you look up the semantics of the "constrained" prefix to the "callvirt" instruction you'll see how it works. – Eric Lippert Apr 15 '11 at 13:57
  • If the subject of "unusual situations where boxing of mutable value types is or is not avoided" interests you, see my recent article on the subject: http://blogs.msdn.com/b/ericlippert/archive/2011/03/14/to-box-or-not-to-box-that-is-the-question.aspx – Eric Lippert Apr 15 '11 at 13:59
  • good post. , hope you discussed our issue with wes :) im big fan of him although i couldnt understand his all posts. about ToString() : i already said you should give an error, stating that compiler cannot gurantee ToString will mutate the variable or not, therefore he/she should call ToString() on a copy of the variable. The name of the ToString() shouldnt make you think of it different – TakeMeAsAGuest Apr 15 '11 at 23:35
  • 1
    if not being able to call ToString() on a readonly (not mutable value types or similiar statement here) value type member is strange, this is because .net invented boxing and dismissed most of good old the constness in c++ and i dont think its too late or will be a problem to change this behaviour in future versions. i suggest const keyword on methods and property getter-setters and parameters too. i agree with you value types cause more problems then their performance gain (most orf time it will take out performance anyway mostly in the situation of big structs) – TakeMeAsAGuest Apr 15 '11 at 23:40
  • @TakeMeAsAGuest: It irks me that many people think of things like "const correctness" as being a hassle, and thus not worth having in a language. True, ensuring const correctness can be tricky, but *omitting it from a language doesn't eliminate the need for it*. As for the performance of big structs, they perform very badly when used with `readonly` fields, but in some contexts large structs perform much better than class types could, and the advantages *increase* with struct size. – supercat Oct 09 '13 at 16:44
  • @EricLippert: How hard would it be to have the compiler check when attempting to access a member of a read-only struct value whether that member has an attribute which indicates that the compiler should allow the usage [even if the member is a property setter] or disallow it [even if it's a property getter or other method], and whether the struct should be copied? If a struct method falsely says it's not necessary to copy the struct, the worst that could happen would be the value of an instance of a struct *whose code is already defective* gets modified--hardly a big loss. – supercat Oct 10 '13 at 02:00
2

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

ohmusama
  • 4,159
  • 5
  • 24
  • 44
  • still i cant see why NoveNext creates an copy, im not treating a sturct as an object – TakeMeAsAGuest Apr 09 '11 at 10:05
  • and still cant see why removing readonly makes it execute as expected. by the way, if holding the reference of List.Enumerator as Enumerator (an interface reference type) it executes normally too. – TakeMeAsAGuest Apr 09 '11 at 10:15
  • 1
    It appears that it makes this copy for any sort of reference, be it struct or class. :| – ohmusama Apr 09 '11 at 11:15
  • @TakeMeAsAGuest: Imagine that there existed a method `static void DoMoveNext(ref T it) where T: IEnumerator {T.MoveNext();}` Calling `DoMoveNext(ref enumerator);` will be legal, and work as expected, if `enumerator` is not `readonly`. If it is read-only, the compiler will flag an error. Calling `enumerator.MoveNext()` is equivalent to calling `DoMoveNext(ref enumerator);` except for one difference: rather than flagging an error, the compiler will pretend you wrote something that would compile `var temp=enumerator; DoMoveNext(ref temp);` and hope that's what you meant. – supercat Oct 09 '13 at 16:48
2

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • thanks for your explanation, very few person could answer this question. i want const keyword in c# language, compiler could check if method mutates state in trivial cases, if it is unable, it can just give error. this would give huge opportunity to both compiler and jit optimizer to better optimize code. and i will be happy if you give your thoughts about my other question: http://stackoverflow.com/questions/5600623/recursively-calling-method-for-object-reuse-purpose you can answer on the duplicate thread. i cant give you up because my reputation is below 15 – TakeMeAsAGuest Apr 09 '11 at 14:01
  • We can't give you that. Post feature requests to connect.microsoft.com – Hans Passant Apr 09 '11 at 14:03
  • i still think MoveNext doesnt mutate the struct, just its inner state, not the address. – TakeMeAsAGuest Apr 09 '11 at 14:05
  • 2
    It is a struct, it doesn't have an address. This is the way value types behave. Your reasoning is only valid for reference types. – Hans Passant Apr 09 '11 at 14:07
  • i didnt get my answer. they have address. you can take address of it in a unsafe scope with fixed construct and see if it does change. it doesnt. and it you didnt explain why it works as expected if readonly keyword not used. – TakeMeAsAGuest Apr 09 '11 at 14:22
  • Forcing the compiler to emit code that ensures an address is valid is completely different from assuming they always have an address. They don't, value types are often stored in a cpu register. Where they don't have an address. This is veering off topic on the question majorly, I doubt I can help you if you don't think this post answers your question. Bye. – Hans Passant Apr 09 '11 at 14:31
  • 1
    you made your funny thoughts about value types, cpu registers public. so bye. – TakeMeAsAGuest Apr 09 '11 at 14:38