5

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?

NibblyPig
  • 51,118
  • 72
  • 200
  • 356
  • 11
    Might be more worth making `Cat` a `class`. I tend to find the criteria for making `struct` types are quite narrow. Without knowing more about `Cat` my knee-jerk reaction is "Why a struct? Turn it into a class." – Adam Houldsworth Jul 15 '13 at 15:12
  • In my example I'm storing it in the global cache of an mvc website, so I wanted to keep the footprint as small as possible, hence struct. Also I'm now intrigued about structs and want to get them working :) – NibblyPig Jul 15 '13 at 15:31
  • 2
    The memory footprint might be a little smaller, but the cost of accessing might be outweighing that due to the value-type semantics of structs. – Adam Houldsworth Jul 15 '13 at 15:33
  • I would agree, but surely changing a value on a struct isn't that complicated! – NibblyPig Jul 15 '13 at 15:34
  • No it's not, you can easily have mutable structs. The gotcha tends to come in from the fact people index the struct *out* of something, don't realise they're talking to a copy, change it, and don't put it *back* (expecting reference-type semantics). I would make the argument that there are better ways of saving memory, but that is another question ;-) – Adam Houldsworth Jul 15 '13 at 15:35
  • 3
    I can only imagine the footprint between a `struct` and `class` is _negligible_ in your scenario. Moreso: if you have 2 or more references to the same `Cat`, as a `struct` it would _copy_ all its fields rather than just copy the _reference pointer_ to the `Cat` instance. I would imagine using a `struct`, in this practice, would consume _more_ memory. (but still a trivial amount) – Chris Sinclair Jul 15 '13 at 15:36
  • Do you have any metrics to show that storing this data as a class will cause any performance hit to your site? If not, this is premature optimization. The string "Dr Fluffykins" takes several times as much space as you're going to save by storing a value instead of a reference. – StriplingWarrior Jul 15 '13 at 15:40
  • 1
    This appears to be a common trap. Here's a good answer by [Jon #1 Skeet](http://stackoverflow.com/a/12128745/335858). – Sergey Kalinichenko Jul 15 '13 at 15:41
  • I agree with just making it a `class` and avoiding this issue everywhere it's used. – Jonathon Reinhart Jul 15 '13 at 17:41
  • Related: [Changing the value of an element in a list of structs](https://stackoverflow.com/questions/51526/changing-the-value-of-an-element-in-a-list-of-structs) – Theodor Zoulias Nov 23 '22 at 11:36

2 Answers2

2

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";
Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Makes sense, wanted to know if LINQ was out of the question (for update operations). No fancy way to store a reference to a struct in C#? – NibblyPig Jul 15 '13 at 15:21
  • @SLC I do not know of a way to use references to `struct`s outside the context of method calls. However, there is a relatively straightforward way of making LINQ find an index for you, avoiding the loop (see the update). – Sergey Kalinichenko Jul 15 '13 at 15:24
  • I must have missed something. If `Cat` is a `struct`, how can you modify it from the `catList[indexOf]`? Won't that implicitly be making a copy returning it from the indexer method? (I don't think this will even compile) – Chris Sinclair Jul 15 '13 at 15:25
  • Indeed, he is correct, I just tried it and you can't modify it. I think the guy who deleted his answer was correct, and you need a Set method. – NibblyPig Jul 15 '13 at 15:26
  • @SLC: Similar problem. The returned `Cat` is a _copy_. You can _call_ a "SetName" method, but it's still altering the _copy_. The original `Cat` in `catList` is still unmodified. – Chris Sinclair Jul 15 '13 at 15:27
  • @SLC You are right, I somehow missed the fact that `catList` is not an array (duh! I should read what I copy-paste!) As far as I know, arrays are the only data structures that provide the special magic for operating on the `struct`s themselves without making a copy. – Sergey Kalinichenko Jul 15 '13 at 15:37
  • @ChrisSinclair You're right, I missed the fact that `catList` is a list, not an array. Thanks for the correction! – Sergey Kalinichenko Jul 15 '13 at 15:37
2

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.

supercat
  • 77,689
  • 9
  • 166
  • 211