Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update the property of a struct within a List in C#

Tags:

c#

struct

I have a List<Cat> where Cat is a struct that has an Id and a Name.

How do I change the name of the cat with id 7?

I did (without thinking)

var myCat = catList.Single(c => c.Id == 7);

mycat.Name = "Dr Fluffykins";

But of course, structs are value types. So is it possible to use a technique like this, or do I have to change .Single into a for loop and store the index to replace it with the updated struct?

like image 332
NibblyPig Avatar asked Oct 22 '22 05:10

NibblyPig


2 Answers

To update the value of the item in the list whose ID is seven, one must first find its location within the list. Once its index is known, one can update it using the normal approach which is applicable to all exposed-field structures:

int index = myList.Find( it => it.ID == 7);
if (index >= 0)
{
  var temp = myList[index];
  temp.Name = "Dr Fluffykins";
  myList[index] = temp;
}

Note that some people think structs are inferior to classes because the above code will never work without the last line. I would argue the opposite: in many cases, they're superior because merely knowing that something is an exposed-field struct is sufficient to know that the last line will be necessary and sufficient. By contrast, if the type in question were a mutable class, the code, with or without the last line, might work as expected or might have unintended side-effects.

Incidentally, if one will be using Id a lot, I would suggest using a Dictionary<int, Cat> rather than a List<Cat>. Alternatively, you could use a Cat[] along with a Dictionary<int,int> to keep track of the locations of different cats within the array. One advantage of this latter approach is that with a Cat[], unlike a List<Cat>, one can simply say:

myArray[index].Name = "Dr Fluffykins";

to update the item in the array directly. Such an approach is very performant. Alternatively, you could write a List<T>-like class which includes a methods "ActOnItem(int index, ref T it) and ActOnItem<TParam1>(int index, ref T it, ref TParam1 param1) method, which could be invoked myList.ActOnItem(index, (ref Cat it)=>it.Name = "Dr Fluffykins"); or--if theNewName is a variable, myList.ActOnItem(index, (ref Cat it, ref string TheNewName)=>it.Name = theNewName); Note that one could store theNewName to the item without passing it as a ref or non-ref parameter, but lambdas which close local variables are much slower than those which don't.

like image 37
supercat Avatar answered Nov 15 '22 06:11

supercat


Since structs are value types, and because value types get copied, myCat ends up with the copy of the cat from the list. You need to operate on the struct itself, not its copy.

Moreover, you can modify fields of structs directly only when they are single variables or parts of an array. List<T>'s indexer returns a copy, so C# compiler produces the "Cannot modify a value type" error.

The only solution that I know (short of making Cat a class or re-assigning a modified copy) is making catList an array:

var indexOf = catArray
    .Select((Cat, Index) => new {Cat, Index})
    .Single(p => p.Cat.Id == 7).Index;
catArray[indexOf].Name = "Dr Fluffykins";
like image 66
Sergey Kalinichenko Avatar answered Nov 15 '22 05:11

Sergey Kalinichenko