2017-04-16 79 views
2

我已经尝试了以下两种代码,它们都似乎以相同的方式工作。ref T索引器和get/set索引器有区别吗?

// 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是在构造函数初始化,并Tilestruct。两者之间有什么明显的区别?如果Tileclass

+0

只有当类型可变时才有区别 –

+0

是的,差别很大。给平铺一个X属性并尝试地图[0,0] .X = 42; –

+0

哦,我明白了。情况2将不允许您修改其属性。谢谢,我会和前者一起去。 :) – Dave

回答

2

假设瓷砖在构造函数中初始化,并且Tile是一个结构。两者之间有什么明显的区别?

CASE 1

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

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

您在返回ref到索引Title对象,所以你可以修改它,甚至将其设置为不同的对象。这是C#7.0的一个新特性(就像你已经标记的那样),其中它允许返回一个ref,然后你可以存储它。换句话说,它返回的存储位置不是值

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

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

由于Tilestruct,当你索引它,你会得到一个副本,如果你试试这个(假设Tile有一个属性X):

map[0, 0].X = 10; 

错误CS1612无法修改'Case2MapWithoutCSharp7.this [int,int]'的返回值,因为它不是变量。

编译器抛出该错误以清楚地明确你认为自己在做什么(修改索引项),这实际上并不是你正在做的。您实际上正在修改副本,以便强制您执行此操作。所以,为了能够设置X,你需要做的是这样的副本:

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

你可以阅读更多关于该错误here

如果瓷砖是class怎么办?

在第一种情况下,由于您要返回存储位置,因此您可以完全将新的Tile对象分配给索引项。

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

map[0, 0].X = 2; 

但是,如果你这样做:

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

显然你是变异了一个全新Tile,而不是一个你索引。