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
。两者之间有什么显着区别吗?如果 Tile
是 class
怎么办?
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
而不是你索引的那个。
我已经尝试了以下两种代码,它们的工作方式似乎相同。
// 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
。两者之间有什么显着区别吗?如果 Tile
是 class
怎么办?
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
而不是你索引的那个。