When Structs Go Bad

In tracking down the cause of a compilation error, I was once more reminded why I don't use structs that often.  The situation was as follows:

A simple struct was defined

public struct Demo
{
   public string value;
}

Variables of type Demo were now instantiated and placed into an ArrayList

ArrayList al = new ArrayList();
al.Add(new Demo);

Finally, a field within the struct was assigned a value through the ArrayList variable.

foreach (Demo d in al)
   d.value = “Test”;

Building this code resulted in the compilation error "The left-hand side of an assignment must be a variable, property or indexer."

For anything but a struct, the code just described would work quite nicely.  However, structs are special.  The intent of a struct is to be instantiated on the stack instead of on the heap.  There are (supposedly) speed reasons for doing this.  For example, if you were to create a large number of structs for a short period of time, creating them on the stack means they get disposed of as soon as they go out of scope (instead of at garbage collection time, as would be the case for a class). This would be fine if .NET weren't so nice to developers.

The problem in this situation is that an ArrayList is (basically) a collection of pointers to other objects.  But it is not possible to 'point' to a variable that has been instantiated on the stack.  So when the struct is added to the ArrayList, it is boxed.  That is, space on the heap is allocated and the values on the struct are copied.  Then the pointer in the ArrayList is quite happy.

So why the compilation message?  Because the C# compiler folk wanted the developer to be aware of what is happening under the covers in this particular situation.  If the d.value assignment were actually performed, the entire struct would have to be copied to the stack, the assignment performed and the resulting struct reboxed.  That has to potential to be a lot of work and, therefore, is something that should be explicitly requested. 

So ultimately, the compiler writers have decided that, in a situation such as this, I need protection from myself.  In principal, I don't mind.  As I started out with, the ease (and invisibility) with which structs are boxed usually means that any memory gains are lost on the performance side.  Unless you are very careful about how they are used.  By complaint is that the compiler message is cyptic.  There is nothing inately wrong with the assignment, other than the performance hit.  So tell me than instead of making me puzzle about why a public element in a struct can't be the target of an assignment.

Comments

  • bruce August 19, 2004 9:25 PM

    Hey this is exactly the issue I faced! Damn it was frustrating to understand that compilation error. Great post ...explaining why it happens. Thanx Bruce.

  • bruce August 19, 2004 9:27 PM

    Oh btw, what do I need to code it as, if I would like to make that assigment statement work? Ofcourse I could change the struct to a class. But without doing that, is there a way?

  • bruce August 20, 2004 9:22 AM

    The following code will let you do the assignment

    Demo b = d;
    b.value = "Test";

    These lines explicitly copy the entire struct back to the stack for the assignment. What becomes a lot more difficult to do is to take this modified structure and get it back into the ArrayList. For the reason that the boxed value pointed to by the ArrayList is marked as being 'read-only'. So a direct assignment such as

    d = b;

    throws a compilation error.

    The originally referenced struct needs to be removed from the ArrayList and replaced with this new value. Of course, such a reassignment cannot be done within the foreach statement that is part of the original example. An InvalidOperationException would be thrown with a message of "Collection was modified; enumeration operation may not execute."

    Need any more reasons for avoiding structs? :)

  • bruce May 11, 2005 6:00 AM

    Thanks very much for the explaination Bruce, nice one.

  • bruce June 30, 2005 9:57 PM

    Thank you! The problem now is, never ever forgetting this valuable piece of info. :( Perhaps ill get a tatoo;

  • bruce September 14, 2005 11:19 PM

    excellent post.
    thx for the workaround..

  • bruce February 3, 2006 4:39 AM

    Thanks,
    Great explanation!

  • bruce March 13, 2006 8:46 PM

    To get around the

    <i>An InvalidOperationException would be thrown with a message of "Collection was modified; enumeration operation may not execute." </i>

    message, I find it works to break out of the foreach if you're only modifying one item in the arraylist. For instance:

    foreach (albumDataItem a in albumItems)
    {
    if (a.filename == filename)
    {
    albumItems.Remove(a);
    break;
    }
    }

    if you're looking for a unique item in the collection, this might solve your problem (because if we're only ever looking for one item, breaking out is sensible)
    Hope that helps

  • bruce May 9, 2006 7:06 PM

    or you could just boot the foreach loop for a normal for loop and you wouldn't have this problem at all

    for(int i=0; i<al.count; i++)
    {
    al[i].value = "test";
    }

    and with a little typecasting you can use whatever stuct you want

  • bruce July 24, 2006 1:01 AM

    Thanks a lot!!
    assigning the struct object to new object,
    removin the old object from arrayList and adding the new one after editin worked well:)

  • bruce August 13, 2006 7:25 PM

    Vandana, if I were reviewing the code, I'd be asking why you're bothering to use structs. Structs are intended to be lightweight objects that are useful in scenarios where they are created and destroyed in great numbers. What they are not good for is for use in ArrayLists, because they have to be boxed into a reference object in order to be stored in the ArrayList in the first place. And then, as you discovered, recreated if they need to be updated. In other words, the situation you described removes all of the benefits of structs while imposing some of the penalties. I'm hoping there's a reason (like the struct is being used properly someplace else).

  • bruce November 4, 2006 11:59 AM

    Thank you

Leave a Comment

(required) 
(optional)
(required) 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS