Search code examples
c#refc#-7.0

Is there a difference between ref T indexer and a get/set indexer?


I've tried both of the following codes and they both seem to work the same way.

// Case 1:  ref T indexer
class Map {
    Tile[,] tiles;

    public ref Tile this[int x, int y]
        => ref tiles[x, y];
}

// Case 2:  get/set indexer
class Map {
    Tile[,] tiles;

    public Tile this[int x, int y] {
        get => tiles[x, y];
        set => tiles[x, y] = value;
    }
}

Assume that tiles is initialized in the constructor, and that Tile is a struct. Is there any notable difference between the two? What if Tile was a class?


Solution

  • Assume that tiles is initialized in the constructor, and that Tile is a struct. Is there any notable difference between the two?

    CASE 1

    // Case 1:  ref T indexer
    class Map {
        Tile[,] tiles;
    
        public ref Tile this[int x, int y]
            => ref tiles[x, y];
    }
    

    You are returning a ref to the indexed Title object so you will be able to modify it and even set it to a different object. This is a new feature of C# 7.0 (as you have tagged) wherein it allows to return a ref and then you can store it as well. In other words it returns the storage location not the value.

    Since you are returning the storage location you can totally assign a new Tile object to the indexed item. Without the ref you will only be able to modify it.

    CASE 2

    class Map {
        Tile[,] tiles;
    
        public Tile this[int x, int y] {
            get => tiles[x, y];
            set => tiles[x, y] = value;
        }
    }
    

    Here is the same but without C# 7.0 for brevity:

    class Case2MapWithoutCSharp7
    {
        Tile[,] tiles;
    
        public Tile this[int x, int y]
        {
            get { return tiles[x, y]; }
            set { tiles[x, y] = value; }
        }
    }
    

    Since Tile is a struct, when you index it you will get a copy and if you try this (assuming Tile has a property X):

     map[0, 0].X = 10;
    

    Error CS1612 Cannot modify the return value of 'Case2MapWithoutCSharp7.this[int, int]' because it is not a variable.

    The compiler is throwing that error to be explicitly clear that what you think you are doing (modifying the indexed item), is not really what you are doing. You are actually modifying the copy so it forces you to do exactly that. So, to be able to set the X, you will need to do it on the copy like this:

    var tile = map[0, 0];
    tile.X = 10;
    

    You can read more on that error here.

    What if Tile was a class?

    In the first case, since you are returning the storage location you can totally assign a new Tile object to the indexed item.

    In the second case, without the ref, since class objects are passed by reference, you will be able to modify it but not set the reference to a whole new object. So you can do this:

    map[0, 0].X = 2;
    

    But if you did this:

    var tile = map[0, 0];
    tile = new Tile();
    tile.X = 5;
    

    You are obviously mutating a brand new Tile and not the one you indexed.