0

I've got a "Vector" struct which stores a x, y, and z values. In an effort to make them easier to access I added an index operator, like this:

public struct Vector
{
    public double x;
    public double y;
    public double z;

    public double this[int i]
    {
        get
        {
            if(i == 0) return x;
            else if(i == 1) return y;
            else if(i == 2) return z;
            else throw new ArgumentOutOfRangeException("i", i, "Index must be 0, 1, or 2");
        }
        set
        {
            if (i == 0) x = value;
            else if (i == 1) y = value;
            else if (i == 2) z = value;
            else throw new ArgumentOutOfRangeException("i", i, "Index must be 0, 1, or 2");
        }
    }
}

Note: Normally I would use an array instead of x, y, z but I'm working with pre-existing code and it'll be difficult to refactor at this point.

Now if I use a struct as an auto-property in a class and then try to modify its xyz values, I get this

public Vector v{ get; set; }
void funct(){
    for(int i = 0; i < 3; i++)
    {
        v[i] = 3.0;
    }
}

I get a compiler error:

"Cannot modify the return value of "v" because it is not a variable"

I've tried googling for the error, but I don't think anyone has done this the way I did (it's a pretty interesting case I guess, and probably really ugly). What would be the right way to solve this problem? Is it wrong to use the index operator like this?

Note: yes, I know this is a mutable struct. Again, pre-existing code

Jehof
  • 34,674
  • 10
  • 123
  • 155
9a3eedi
  • 696
  • 2
  • 7
  • 18
  • Have your tried `this.v[i] = 3.0;` – Justin Lessard Nov 06 '13 at 02:10
  • @Zirkonix That would give the same error. – Servy Nov 06 '13 at 02:11
  • 2
    "I don't think anyone has done this the way I did" Actually, they have. In fact, it's so common for people to have done this that they added an error to the compiler to specifically cover this exact case, because so many people have been bitten by this bug as a result of not understanding the semantics of mutable structs. Just look at the actual Google results of the related searches, as they are exactly the situation that you're in. Note that using an indexer is no different than any other means of mutating a struct, such as setting a property or field directly. – Servy Nov 06 '13 at 02:12
  • 2
    possible duplicate of [Can't modify XNA Vector components](http://stackoverflow.com/questions/2534574/cant-modify-xna-vector-components) – Servy Nov 06 '13 at 02:16
  • yeah, it's pretty much the same reason as that link. Should've googled better before asking. I still want to be able to iterate nicely through x,y,z in the struct. Is there a better way of doing it? – 9a3eedi Nov 06 '13 at 02:19
  • Implement `IEnumerable`. – Szymon Nov 06 '13 at 02:23
  • @Szymon That wouldn't let you set each value effectively, only fetch them, which he can do effectively enough already. It's the mutating that causes problems. – Servy Nov 06 '13 at 02:26
  • @9a3eedi See the answer within the proposed duplicate for what you need to do. You'll need to do that. – Servy Nov 06 '13 at 02:26
  • @Servy True. OP asked about iterating, that's why I thought about IEnumerable. – Szymon Nov 06 '13 at 02:28
  • @Servy, once again I should've read more. This works. Thanks. – 9a3eedi Nov 06 '13 at 02:49

1 Answers1

2

You got some options:

  1. Make it a class instead of a struct
  2. Obtain a copy of the struct value first, modify it, then put it back:

    Vector temp = v;
    for(int i = 0; i < 3; i++)
    {
        temp[i] = 3.0;
    }
    v = temp;
    
  3. Make it immutable and add helper/factory methods:

    public struct Vector
    {
        ....
    
        public Vector WithX(double x)
        {
            return new Vector(x, this.y, this.z);
        }
    

I would personally go for nbr. 3 as mutable structs (as you've seen) is a pain in the a**.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • I've decided to go for option 2. That works well enough given my needs. Option 1 and 3 would require a lot of refactoring for me at this point. In my case, option 1 is probably not a good idea because I want this to be allocated on the stack when used in a function, so a struct is suitable. Maybe sometime in the future I will make a big refactor and implement option 3. – 9a3eedi Nov 07 '13 at 01:41