tags:

views:

1420

answers:

5

I have this struct:

struct Map 
{ 
    public int Size; 

    public Map ( int size ) 
    { 
        this.Size = size; 
    } 

    public override string ToString ( ) 
    { 
        return String.Format ( "Size: {0}", this.Size ); 
    } 
}

When using array, it works:

        Map [ ] arr = new Map [ 4 ] { 
            new Map(10), 
            new Map(20), 
            new Map(30), 
            new Map(40)}; 

        arr [ 2 ].Size = 0;

But when using List, it doesn't compile:

        List<Map> list = new List<Map> ( ) { 
            new Map(10), 
            new Map(20), 
            new Map(30), 
            new Map(40)}; 

        list [ 2 ].Size = 0;

Why?

+9  A: 

The C# compiler will give you the following error:

Cannot modify the return value of 'System.Collections.Generic.List.this[int]' because it is not a variable

The reason is that structs are value types so when you access a list element you will in fact access an intermediate copy of the element which has been returned by the indexer of the list.

From MSDN:

Error Message

Cannot modify the return value of 'expression' because it is not a variable

An attempt was made to modify a value type that was the result of an intermediate expression. Because the value is not persisted, the value will be unchanged.

To resolve this error, store the result of the expression in an intermediate value, or use a reference type for the intermediate expression.

Solutions:

  1. Use an array. This gives you direct access to the elements (you are not accessing a copy)
  2. When you make Map a class you can still use a List to store your element. You will then get a reference to a Map object instead of an intermediate copy and you will be able to modify the object.
  3. If you cannot change Map from struct to a class you must save the list item in a temporary variable:

 

List<Map> list = new List<Map>() { 
    new Map(10), 
    new Map(20), 
    new Map(30), 
    new Map(40)
};

Map map = list[2];
map.Size = 42;
list[2] = map;
0xA3
Thanks but I am using XNA framework and most stuff there is implemented as a struct, like Vector3, etc.
Joan Venge
It's just a C# limitation, the CLR has the possibility to return references to structures (managed pointers), but the C# language doesn't implement that.
Pop Catalin
Thanks, but for arrays it returns references?
Joan Venge
Pop Catalin
+8  A: 

Because it is a struct, when using the List your are creating copies.

When using a struct, it is better to make them immutable, this will avoids effects like this.


EDIT

When using an array, you have direct access to the memory structures. Using the List.get_Item you work on a return value, that is, a copy of the structure.

If it was a class, you would get a copy of a pointer to the class, but you would not notice this, since pointers are hidden in C#.

Also using the List.ToArray does not solve it, Because it will create a copy of the internal array, and return this.

And this is not the only place where you will get effects like this when using a struct.

The solution provided by Divo is a very good workaround. But you have to remember to work this way, not only when using the List but everywhere you want to change a field inside the struct.

GvS
How do you make it immutable?
Joan Venge
Don't provide any way of modifying the values inside the struct. Like .NET does with strings and DateTime objects, all operations should return a new instance, rather than changing the current instance.
Joel Mueller
But what about instance methods?
Joan Venge
Change the code for the Size field to (something like): public int Size { get; private set; }
GvS
If you need to change the Size (or other fields) a lot, better to go for a class, and not use sctruct.
GvS
Since the OP requires that this be a struct, couldn't one use 'unsafe' code to directly access the structure?
Mike Rosenblum
That's interesting. Do you think this would be the only way to get a handle of a struct? But even so why the ones in the array works as expected but not in the List<T>? Looks like an overlook of the .NET team?
Joan Venge
Concerning your last paragraph: You actually don't have to remember to work this way. The compiler will throw an error at you when you try to assign to an intermediate result value.
0xA3
A: 
list [ 2 ].Size = 0;

In fact:

//copy of object
Map map = list[2];
map.Size = 2;

Use class in place of struct.

mykhaylo
It would be clearer if you wrote "is" before "in fact"
Hosam Aly
Thanks but I am using XNA framework and most stuff there is implemented as a struct, like Vector3, etc.
Joan Venge
+1  A: 

I'm not an XNA developer, so I cannot comment on how strict is the requirement to use structs vs. classes for XNA. But this does seem to be a much more natural place to use a class in order to get the pass-by-ref semantics you are looking for.

One thought I had is that you could make use of boxing. By creating an interface, say, 'IMap' in your case, to be implanted by your 'Map' struct, and then using a List<IMap>, the list will be holding System.Object objects, which are passed by reference. For example:

interface IMap
{
    int Size { get; set; }
}

struct Map: IMap
{
    public Map(int size)
    {
        _size = size;
    }

    private int _size;

    public int Size
    {
        get { return _size; }
        set { _size = value; }
    }

    public override string ToString()
    {
        return String.Format("Size: {0}", this.Size);
    }

}

Which could then be called by the following:

List<IMap> list = new List<IMap>() { 
    new Map(10), 
    new Map(20), 
    new Map(30), 
    new Map(40)};

    list[2].Size = 4;
    Console.WriteLine("list[2].Size = " + list[2].Size.ToString());

Note that these structs would only be boxed once, when passed into the List in the first place, and NOT when called using code such as 'list[2].Size = 4', so it should be fairly efficient, unless you are taking these IMap objects and casting back to Map (copying it out of the List<IMap>) in other parts of your code.

Although this would achieve your goal of having direct read-write access to the structs within the List<>, boxing the struct is really stuffing the struct into a class (a System.Object) and I would, therefore, think that it might make more sense to make your 'Map' a class in the first place?

Mike

Mike Rosenblum
A: 

I decided to directly replace the result on a copy and reassign the result like:

Map map = arr [ 2 ];
map.Size = 0;
arr [ 2 ] = map;
Joan Venge