ref T 索引器和 get/set 索引器之间有区别吗?

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

我已经尝试了以下两种代码,它们的工作方式似乎相同。

// 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;
    }
}

假设tiles在构造函数中初始化,Tile是一个struct。两者之间有什么显着区别吗?如果 Tileclass 怎么办?

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

案例 1

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

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

您正在 return 将 ref 指向索引的 Title 对象,这样您就可以修改它,甚至可以将它设置为不同的对象。这是 C# 7.0 的一个新特性(正如你所标记的),它允许 return 一个 ref 然后你也可以存储它。换句话说 它 return 是存储位置而不是值

由于您正在 return 存储位置,因此您完全可以为索引项分配一个新的 Tile 对象。没有 ref 你将只能修改它。

案例 2

class Map {
    Tile[,] tiles;

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

这里是一样的,但为简洁起见没有 C# 7.0:

class Case2MapWithoutCSharp7
{
    Tile[,] tiles;

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

因为 Tile 是一个 struct,当你索引它时你会得到一个副本,如果你尝试这个(假设 Tile 有一个 属性 X):

 map[0, 0].X = 10;

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

编译器抛出该错误是为了明确表明您认为自己正在做的事情(修改索引项)并不是您真正在做的事情。您实际上是在修改副本,因此它会强制您这样做。因此,为了能够设置 X,您需要像这样在副本上进行设置:

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

您可以阅读有关该错误的更多信息 here

What if Tile was a class?

在第一种情况下,由于您正在 return 存储位置,因此您完全可以为索引项分配一个新的 Tile 对象。

在第二种情况下,没有 ref,因为 class 对象是通过引用传递的,您将能够修改它但不能将引用设置为一个全新的对象。所以你可以这样做:

map[0, 0].X = 2;

但是如果你这样做了:

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

你显然是在变异一个全新的 Tile 而不是你索引的那个。