views:

1058

answers:

4

I have a list of structs and I want to change one element. For example :

MyList.Add(new MyStruct("john");
MyList.Add(new MyStruct("peter");

Now I want to change one element:

MyList[1].Name = "bob"

However, whenever I try and do this I get the following error:

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

If I use a list of classes, the problem doesn't occur.

I guess the answer has to do with structs being a value type.

So, if I have a list of structs should I treat them as read-only? If I need to change elements in a list then I should use classes and not structs?

+6  A: 

MyList[1] = new MyStruct("bob");

structs in C# should almost always be designed to be immutable (that is, have no way to change their internal state once they have been created).

In your case, what you want to do is to replace the entire struct in specified array index, not to try to change just a single property or field.

Andrew
This is not the full answer, Gishu's answer is much more complete.
Motti
+7  A: 

Not quite. Designing a type as class or struct shouldn't be driven by your need to store it in collections :) You should look at the 'semantics' needed

The problem you're seeing is due to value type semantics. Each value type variable/reference is a new instance. When you say

Struct obItem = MyList[1];

what happens is that a new instance of the struct is created and all members are copied one by one. So that you have a clone of MyList[1] i.e. 2 instances. Now if you modify obItem, it doesn't affect the original.

obItem.Name = "Gishu";  // MyList[1].Name still remains "peter"

Now bear with me for 2 mins here (This takes a while to gulp down.. it did for me :) If you really need structs to be stored in a collection and modified like you indicated in your question, you'll have to make your struct expose an interface (However this will result in boxing). You can then modify the actual struct via an interface reference, which refers to the boxed object.

The following code snippet illustrates what I just said above

public interface IMyStructModifier
{      String Name { set;        }       }
public struct MyStruct : IMyStructModifier ...

List<Object> obList = new List<object>();
obList.Add(new MyStruct("ABC"));
obList.Add(new MyStruct("DEF"));

MyStruct temp = (MyStruct)obList[1];
temp.Name = "Gishu";
foreach (MyStruct s in obList) // => "ABC", "DEF"
{      Console.WriteLine(s.Name);            }

 IMyStructModifier temp2 = obList[1] as IMyStructModifier;
 temp2.Name = "Now Gishu";
 foreach (MyStruct s in obList) // => "ABC", "Now Gishu"
 {     Console.WriteLine(s.Name);       }

HTH. Good Question.
Update: @Hath - you had me running to check if I overlooked something that simple. (It would be inconsistent if setter properties dont and methods did - the .Net universe is still balanced :)
Setter method doesn't work
obList2[1] returns a copy whose state would be modified. Original struct in list stays unmodified. So Set-via-Interface seems to be only way to do it.

List<MyStruct> obList2 = new List<MyStruct>();
obList2.Add(new MyStruct("ABC"));
obList2.Add(new MyStruct("DEF"));
obList2[1].SetName("WTH");
foreach (MyStruct s in obList2) // => "ABC", "DEF"
{
  Console.WriteLine(s.Name);
}
Gishu
+1  A: 

Am i wrong in thinking you could just use a function to set the Object.Name?

static class Program
{
    struct MyStruct
    {
        private string _name;
        public string Name
        {
            get { return _name; }
        }

        public MyStruct(string name)
        {
            _name = name;
        }

        public void SetName(string name)
        {
            _name = name;
        }
    }


    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        List<MyStruct> list = new List<MyStruct>()
        {
            new MyStruct("peter"),
            new MyStruct("john"),
        };

        list[1].SetName("bob");

    }
Hath
+1  A: 

It's not so much that structs are "immutable."

The real underlying issue is that structs are a Value type, not a Reference type. So when you pull out a "reference" to the struct from the list, it is creating a new copy of the entire struct. So any changes you make on it are changing the copy, not the original version in the list.

Like Andrew states, you have to replace the entire struct. As that point though I think you have to ask yourself why you are using a struct in the first place (instead of a class). Make sure you aren't doing it around premature optimization concerns.

jolson