2

Assuming I have a struct:

struct Vector
{
    public int X, Y;

    // ...
    // some other stuff
}

and a class:

class Map
{
    public Vector this[int i]
    {
        get
        {
            return elements[i];
        }
        set
        {
            elements[i] = value;
        }
    }

    private Vector[] elements

    // ...
    // some other stuff
}

I want to be able to do something like: map[index].X = 0; but I can't, because the return value is not a variable.

How do I do this, if at all possible?

Nolonar
  • 5,962
  • 3
  • 36
  • 55
  • 2
    You get that error because you are using a `struct`. So...are you *sure* you want to be using a struct? (generally speaking, the answer is usually "no") – Kirk Woll May 15 '12 at 20:07
  • So, should I use a class instead? – Nolonar May 15 '12 at 20:08
  • 1
    And if the answer is "yes", there is then another "shouldn't that be immutable?" question... – Marc Gravell May 15 '12 at 20:08
  • 1
    @Nolonar, structs have a lot of "gotchas" (particularly *mutable* structs). If I were you I'd use a `class` until I had a good reason not to. – Kirk Woll May 15 '12 at 20:08
  • When you use a struct, you get your data copied every time. So it does not make sense to modify the copy unless you keep it. So you could say: `var v = map[index]; v.X = 0; map[index] = v;` In this example `v` is a copy from your internal array. After modifying the copy you need to assign it back to `map`. – Jeppe Stig Nielsen May 15 '12 at 20:29
  • I agree mutable structs can be problematic. But back in .NET 1 we had them when we used `DictionaryEntry`. It is still in the framework, of course. [MSDN doc](http://msdn.microsoft.com/en-us/library/system.collections.dictionaryentry.aspx) – Jeppe Stig Nielsen May 15 '12 at 20:33

4 Answers4

4

You should avoid mutable structs.

If you want your type to be mutable use a class instead.

class Vector
{
    public int X { get; set; } // Use public properties instead of public fields!
    public int Y { get; set; }

    // ...
    // some other stuff
}

If you want to use a struct, make it immutable:

struct Vector
{
    private readonly int x;  // Immutable types should have readonly fields.
    private readonly int y;

    public int X { get { return x; }} // No setter.
    public int Y { get { return y; }}

    // ...
    // some other stuff
}
Community
  • 1
  • 1
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • A `Vector` *could* be valid as an immutable struct – Marc Gravell May 15 '12 at 20:09
  • The comment "If you want to make it immutable, make it a class instead" is some of the *WORST* advice ever. If one has an array of struct, one can easily change one field of one element without affecting that field in any other element: `MyPointArray[4].X += 3;`. If one has some other type of collection, it's a little harder, but not too bad: `var Temp = MyPointList[4]; Temp.X += 3; MyPointList[4] = Temp;`. If one had an array or `List` of your mutable `Vector` class, how would one safely perform a similar modification on it? – supercat May 17 '12 at 18:50
  • There are sometimes good reasons to build struct whose only publicly-available method of mutation is by whole-struct copying; among other things, if it will ever be necessary to replace a struct with an immutable class, such a change will be easier if the struct is only mutated via whole-struct copying. Both immutable classes and immutable structs make decent "single-record" holders; each can be marginally better than the other in some circumstances. Mutable structs make great single-record holders if there will not be any need for nested mutable classes. Mutable classes are often not good... – supercat May 17 '12 at 19:13
  • ...single-record holders because every storage location of a mutable class type effectively holds two logical pieces of information: the contents of the class object to which it refers, and *the set of all other storage locations which hold the same reference, and will "share" in any mutations made to it*. If what one actually wants is a private copy of the information in the class object, the fact that other references to it might exist would often not be good thing. And if no outside references are ever going to be exposed, it might as well be a mutable struct. – supercat May 17 '12 at 19:22
3

The compiler prevents you from doing this because the indexer returns a copy of an object not a reference (struct is passed by value). The indexer returns a copy, you modify this copy and you simply don't see any result. The compiler helps you avoid this situation.

If you want to handle such situation you should use class instead or change the way you deal with Vector. You shouldn't modify it's value but initialize it's values in constructor, more on this topic: Why are mutable structs “evil”?.

Community
  • 1
  • 1
empi
  • 15,755
  • 8
  • 62
  • 78
1
  • define Vector as class, or
  • store value in a temporary variable

    var v = map[index]; v.X = 0; map[index] = v;

or

  • add function to change map[index] = map[index].Offset()

or

  • let the [] operator return a setter class

    class Setter { Vector[] Data; int Index; public double X { get { return Data[Index]; } set { Data[Index] = new Vector(value, Data[Index].Y); }}}

    public Setter this[int i] { get { return new Setter() { Data = elements, Index= i }; } }

user287107
  • 9,286
  • 1
  • 31
  • 47
1

Although generic classes work pretty well for many purposes, they do not provide any reasonable way to access structs by reference. This is unfortunate since in many cases a collection of structs would offer better performance (both reduced memory footprint and improved cache locality) and clearer semantics than a collection of class objects. When using arrays of structs, one can use a statement like ArrayOfRectangle[5].Width += 3; with very clear effect: it will update field X of ArrayOfRectangle[5] but it will not affect field X of any other storage location of type Rectangle. The only things one needs to know to be certain of that are that ArrayOfRectangle is a Rectangle[], and Rectangle is a struct with a public int field X. If Rectangle were a class, and the instance held in ArrayOfRectangle[5] had ever been exposed to the outside world, could be difficult or impossible to determine whether the instance referred to by ArrayOfRectangle[5] was also held by some other code which was expecting that field X of its instance wouldn't change. Such problems are avoided when using structures.

Given the way .net's collections are implemented, the best one can do is usually to make a copy of a struct, modify it, and store it back. Doing that is somewhat icky, but for structs that aren't too big, the improved memory footprint and cache locality achieved by using value types may outweigh the extra code to explicitly copy objects from and to the data structures. It will almost certainly be a major win compared with using immutable class types.

Incidentally, what I'd like to see would be for collections to expose methods like: OperateOnElement<paramType>(int index, ref T element, ref paramType param, ActionByRef<T,paramType> proc) which would call proc with the appropriate element of the collection along with the passed-in parameter. Such routines could in many cases be called without having to create closures; if such a pattern were standardized, compilers could even use it to auto-generate field-update code nicely.

supercat
  • 77,689
  • 9
  • 166
  • 211