Further comments on the mutable struct/closure/using issue

I’m giving the issue I wrote about in my previous post one last hurrah on Stack Overflow. I have updated the question to make it more concise (I felt that code examples were necessary, though; so it’s still not exactly short) and to-the-point. I did feel that a few additional comments were warranted based on my research, however. So I am including those here.


My best guess as to what’s happening here is that the code in question violates the rules of the using statement and is perhaps considered undefined behavior. Section 15.13 of the ECMA-334 has this to say:

Local variables declared in a resource-acquisition [using statement] are read-only, and shall include an initializer. A compile-time error occurs if the embedded statement attempts to modify these local variables (via assignment or the ++ and -- operators) or pass them as ref or out parameters.

In light of this statement, I could see the argument being made that modifying a variable of a value type within a using statement is undefined and/or illegal in the first place. I only question this argument because the following code not only appears correct but even behaves as I would expect:

var list = new List<int> { 1, 2, 3 };

// The List<T>.Enumerator type is a value type.
using (var e = list.GetEnumerator())
{
    while (e.MoveNext()) { }
}

Perhaps the behavior of the above code is technically undefined; I would just personally find this a bit strange.

Furthermore, in section 14.5.15.5 of the ECMA-334 some example implementations of anonymous methods (meant to serve only as possible implementations, I understand) are provided. In the case of a local variable being captured inside a closure, the example provided in the spec consists of lifting the variable to an instance field of a compiler-generated class. Accesses of the local variable then become field accesses on an instance of this class.

As has been discussed already in some of the comments to my question, it would seem that this particular implementation would still allow a mutable struct to behave as expected, as it would be accessed directly as a field of a class instance, not copied via a method or property access, for example. So I still wonder why introducing a closure specifically from within a using block essentially changes the behavior of a mutable value type.

Advertisements

13 thoughts on “Further comments on the mutable struct/closure/using issue

  1. Christopher says:

    I believe that code IS correct. The standard is talking about what the actual local variable points to/refers to/is. I think the line gets muddied with structs, but I believe it still should allow for field assignment within a struct so long as the struct is not changed in full. I’m no Eric Lippert however.

    • Daniel says:

      My only issue is that I don’t think there’s actually any distinction between “changed in full” and “had one field changed” when it comes to value types. Am I wrong? Either way the data at that particular memory address needs to be overwritten, right? I think particularly in light of the fact that readonly variables don’t allow field assignment of any kind (as LukeH pointed out in a comment to the SO question), if that’s what the spec means by “read-only,” then I’m not so sure the code should work after all. Of course, I’m no Eric Lippert either 😉

      • Christopher says:

        If that is the case, then using a struct for the List enumerator seems like a poor choice!

      • Christopher says:

        Very strange, I built my own List and struct enumerator and cannot get it to die. But I noticed something interesting between my version of GetEnumerator and the builtin:

        – .field public valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator e
        + .field public class [mscorlib]System.Collections.Generic.IEnumerator`1 e2

        It stores the .Net’s codes as the actual type, whereas mine is stored as a generic class instance.

    • Daniel says:

      Christopher: How are you accessing your struct type? If you have a variable typed as IEnumerator<T>, it will be boxed (interfaces are reference types). This is one of the funny things about value types implementing interfaces; you have to be very careful about how you access them if you want to avoid boxing. (Of course, boxing would fix the behavior we’re talking about since suddenly you’re passing around copies of references to the same object, as opposed to copies of values.)

      • Christopher says:

        I actually cannot get it to compile with the same source as List. It complains that using Enumerator as the return type keeps me from implementing IEnumerable.

    • Daniel says:

      (In response to your previous comment): It might seem like a poor choice for some reasons, but it depends on what factors you’re considering. I believe it was decided to use a struct for this type in the BCL because it was (correctly) assumed that developers would use foreach loops over the List<T> type a lot. Making List<T>.Enumerator a struct ensures that this construct does not cause unnecessary boxing. Conceptually, I agree with this since it feels a bit pointless to allocate a new object every time you want to traverse a list (especially considering that the equivalent for loop would create no garbage).

    • Daniel says:

      Maybe upload your code to a site like pastebin.com so I can take a look? I’m curious.

    • Daniel says:

      Chris, the trick is that you need to also explicitly implement the IEnumerable<T> interface (not just the non-generic one), delegating calls to your public GetEnumerator method that passes a value of your Enumerator struct. Note that this is basically the same as what you’ve done by explicitly implementing IEnumerable; your return type is more specific than what the interface requires.

  2. Christopher says:

    The IL differences basically boil down to the use of an extra local variable to hold the enumerator, which is likely where the copy occurs.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: