Changing the value of an element in a list of structs

C#StructValue Type

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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);
}

Solution 2 - C#

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.

Solution 3 - C#

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.

Solution 4 - C#

There is nothing wrong with structs that have exposed fields, or that allow mutation via property setters. Structs which mutate themselves in response to methods or property getters, however, are dangerous because the system will allow methods or property getters to be called on temporary struct instances; if the methods or getters make changes to the struct, those changes will end up getting discarded.

Unfortunately, as you note, the collections built into .net are really feeble at exposing value-type objects contained therein. Your best bet is usually to do something like:

MyStruct temp = myList[1];
temp.Name = "Albert";
myList[1] = temp;
Somewhat annoying, and not at all threadsafe. Still an improvement over a List of a class type, where doing the same thing might require:
myList[1].Name = "Albert";
but it might also require:
myList[1] = myList[1].Withname("Albert");
or maybe
myClass temp = (myClass)myList[1].Clone();
temp.Name = "Albert";
myList[1] = temp;
or maybe some other variation. One really wouldn't be able to know unless one examined myClass as well as the other code that put things in the list. It's entirely possible that one might not be able to know whether the first form is safe without examining code in assemblies to which one does not have access. By contrast, if Name is an exposed field of MyStruct, the method I gave for updating it will work, regardless of what else MyStruct contains, or regardless of what other things may have done with myList before the code executes or what they may expect to do with it after.

Solution 5 - C#

In addition to the other answers, I thought it could be helpful to explain why the compiler complains.

When you call MyList[1].Name, unlike an array, the MyList[1] actually calls the indexer method behind the scenes.

Any time a method returns an instance of a struct, you're getting a copy of that struct (unless you use ref/out).

So you're getting a copy and setting the Name property on a copy, which is about to be discarded since the copy wasn't stored in a variable anywhere.

This tutorial describes what's going on in more detail (including the generated CIL code).

Solution 6 - C#

As of C#9, I am not aware of any way to pull a struct by reference out of a generic container, including List<T>. As Jason Olson's answer said:

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.

So, this can be pretty inefficient. SuperCat's answer, even though it is correct, compounds that inefficiency by copying the updated struct back into the list.

If you are interested in maximizing the performance of structs, then use an array instead of List<T>. The indexer in an array returns a reference to the struct and does not copy the entire struct out like the List<T> indexer. Also, an array is more efficient than List<T>.

If you need to grow the array over time, then create a generic class that works like List<T>, but uses arrays underneath.

There is an alternative solution. Create a class that incorporates the structure and create public methods to call the methods of that structure for the required functionality. Use a List<T> and specify the class for T. The structure may also be returned via a ref returns method or ref property that returns a reference to the structure.

The advantage of this approach is that it can be used with any generic data structure, like Dictionary<TKey, TValue>. When pulling a struct out of a Dictionary<TKey, TValue>, it also copies the struct to a new instance, just like List<T>. I suspect that this is true for all C# generic containers.

Code example:

public struct Mutable
{
   private int _x;

   public Mutable(int x)
   {
      _x = x;
   }

   public int X => _x; // Property

   public void IncrementX() { _x++; }
}

public class MutClass
{
   public Mutable Mut;
   //
   public MutClass()
   {
      Mut = new Mutable(2);
   }

   public MutClass(int x)
   {
      Mut = new Mutable(x);
   }

   public ref Mutable MutRef => ref Mut; // Property

   public ref Mutable GetMutStruct()
   {
      return ref Mut;
   }
}

private static void TestClassList()
{
   // This test method shows that a list of a class that holds a struct
   // may be used to efficiently obtain the struct by reference.
   //
   var mcList = new List<MutClass>();
   var mClass = new MutClass(1);
   mcList.Add(mClass);
   ref Mutable mutRef = ref mcList[0].MutRef;
   // Increment the x value defined in the struct.
   mutRef.IncrementX();
   // Now verify that the X values match.
   if (mutRef.X != mClass.Mut.X)
      Console.Error.WriteLine("TestClassList: Error - the X values do not match.");
   else
      Console.Error.WriteLine("TestClassList: Success - the X values match!");
}

Output on console window:

TestClassList: Success - the X values match!

For the following line:

ref Mutable mutRef = ref mcList[0].MutRef;

I initially and inadvertently left out the ref after the equal sign. The compiler didn't complain, but it did produce a copy of the struct and the test failed when it ran. After adding the ref, it ran correctly.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDarrenView Question on Stackoverflow
Solution 1 - C#GishuView Answer on Stackoverflow
Solution 2 - C#AndrewView Answer on Stackoverflow
Solution 3 - C#Jason OlsonView Answer on Stackoverflow
Solution 4 - C#supercatView Answer on Stackoverflow
Solution 5 - C#David KlempfnerView Answer on Stackoverflow
Solution 6 - C#Bob BryanView Answer on Stackoverflow