Home > OS >  In C# does the indexer `[]` of an array of structs return a copy?
In C# does the indexer `[]` of an array of structs return a copy?

Time:02-03

I'm working on a project where we need to use arrays of Vector3 and arrays of Quaternion, which are from System.Numerics, and are implemented as structs. We do in fact need to reach into the arrays and change values of individual items, i.e. they are mutable structs.

I've received the lecture that "mutable structs are evil," but it seems to be unavoidable unless I copy the Quaternion.cs and Vector3.cs source code into my project and change them to classes in a private implementation, or write wrapper classes, or something equally horrible, so I am taking pains to understand the pitfalls of mutable structs. Oft-quoted is Eric Lippert's blog, Mutating Readonly Structs, and I am confident we will have no problem with the example he gave, of classes having readonly fields of mutable structs; but in the comment section, Eric says:

For example, something like: mydictionary[123].blah = 456; is a compiler error if the indexer returns a mutable struct. The result of an indexer is not a variable, and therefore, the write to blah will be on the copy returned by the indexer, not the contents of the dictionary. That's clearly not at all what the author intended. They meant temp = mydictionary[123]; temp.blah = 456; mydictionary[123] = temp;. Since the code is clearly bogus and there is a simple workaround, we give an error.

Granted, this comment is from 2008, and maybe it just doesn't apply anymore in the present day. If so, what changed?

I do in fact need to do this, but Eric says "mydictionary" implying it's a dictionary, and I am using arrays, so maybe the indexer of arrays count as "variables," and the indexer of dictionaries do not, i.e. they work differently from each other, but in my experience, there is no problem:

    var quaternions = new Quaternion[100, 100];
    Console.WriteLine(quaternions[1, 1].X);  // Outputs "0"
    quaternions[1, 1].X = 1.0f;
    Console.WriteLine(quaternions[1, 1].X);  // Outputs "1"

So, does the indexer return a copy, or not? Have they introduced some syntactic sugar such that this code actually compiles to make a local copy, modify it, and copy it back? I would hate to waste cpu cycles like that.

I'm still just trying to make sure I understand the ways mutable structs have caused problems and earned a bad reputation, so we can use them correctly. I do understand "by default, on assignment, passing an argument to a method, and returning a method result, variable values are copied." Value types (C# reference). If this is really all I have to worry about, I think I've got a good handle on it, but the comments in Eric's blog vs my observations in reality give me uncertainty.

In particular spec says in Array access:

The location of the array element given by the index expression(s) is computed, and this location becomes the result of the array access.

Which seem to confirm that indexing of arrays returns reference rather than value.

Edit: Adding some more info.

At https://stackoverflow.com/a/6691740/1726692, there is an example of accessing elements of an array, which works fine just as my example does. But they point out that every Array implements IList, so if you just cast that array to IList and repeat the same operation, you're working on a copy of the element, not accessing the element itself. Between this answer and Mark's answer below, I am guessing the Array indexer does not make a copy, but probably all other indexers (List, Dictionary, etc) probably all return copies. This is still a question; pending any authoritative complete answer.

CodePudding user response:

The language spec says:

9.2.1 General

C# defines seven categories of variables: static variables, instance variables, array elements, value parameters, reference parameters, output parameters, and local variables.

So array elements are variables. The [] notation for an array is not the same as a class indexer, even though they look the same.

Reading an indexer is the result of calling the get_accessor method, and so is a copy (a returned value). An array subscript is computed, and after bounds-checking, the value at the offset is returned.

CodePudding user response:

Like others have said, it makes a difference if [] is an array accessor or an indexer (this[] property of a class).

Look at the code below:

static class Program
{
    static void Main(string[] args)
    {
        Quaternion[] array = new Quaternion[10];
        array[0].W = 1;
        Console.WriteLine(array[0]);
        // {X:0 Y:0 Z:0 W:1}

        List<Quaternion> list = new List<Quaternion>(array);
        list[1].W = 1;  // Error CS1612
        Console.WriteLine(list[1]);
    }
}

Even though mutating a field of structure can lead to bugs, it is allowed to happen unless the structure has the readonly keyword. Writing to individual fields can lead to data inconsistencies as a struct is intended to be used atomically (like a tuple).

My advice is to be careful when writing to individual fields of a struct, and prefer to do a memory copy with the assignment operator = instead. Even if the fields aren't explicitly defined as readonly, you should treat them as such (if possible).

This applies to assigning to this internally to mutate the structure atomically instead of writing to individual fields.

For example, do this:

public struct Vector2
{
  float X;
  float Y;

  Vector2(float x, float y)
  {
    X = x;
    Y = y;
  }
  void MakeZero()
  {
    this = new Vector2(0f,0f); 
  }
}

instead of

  void MakeZero()
  {
    X = 0f; 
    Y = 0f;
  }
  • Related