首先我要承认我的无知,我一直不知道Unity的 C#也可以多值返回 (要Unity 2018之后的版本),我写了几年的Golang,最近用Unity做游戏写C#竟然心里还吐槽C#不能像Go一样很方便的就能多值返回,还需要out什么的。。。
直到我今天在逛Gamasutra的时候发现了一篇讲 Unity C# tuples的文章,才知道C#其实也可以通过Tuples来进行方便的多值返回。翻译出来,也算是丰富一下自己的知识了。
以下转自: https://zhuanlan.zhihu.com/p/133857612
废话不多说开始正文:
啥是Unity C# Tuples?
Tuples就是一个简单的C#数据结构,用来包含多个变量(跟python差不多的样子),这种数据结构一般是内联声明,并且包含的变量也不会太多。
通过Tuples,我们可以缩短某些冗长的代码增加可读性。
接下来让我们利用Unity的Raycasting结合tuples来做个例子。
如何使用C# 7.0 之后的 Tuples
在这个例子里,我们假设有一个 RaycastHit 数组来作为免内存分配的那个raycasting 方法的参数,比如最多只会有6个碰撞结果
readonly RaycastHit[] _tmpRaycastHits = new RaycastHit[6];
在C# 7.0之前,常用的办法就是通过用Tuple 这个类来实现Tuple的返回,比如:
return new Tuple<int, RaycastHit[]>(numHits, _tmpRaycastHits)
现在我们来看看在高大上的 C# 7.0里是怎么玩儿的。
基本的 C# Tuple
在 C# 7.0 里,Tuple基本上就长这样:
(int, RaycastHit[]) GetEnemiesInSight()
{
int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _raycastHits);
return (numHits, _tmpRaycastHits);
}
void TestTupleBasic()
{
var enemiesInSight = GetEnemiesInSight();
Debug.Log($"{enemiesInSight.Item1} enemies in sight:");
foreach (var raycastHit in enemiesInSight.Item2)
{
Debug.Log(raycastHit.collider.gameObject.name);
}
}
第1行: 你通过 (type1, type2, ...) 来声明类型,我们的例子就是声明了返回(int, RaycastHit[]) 这个类型。
第4行: 你创建了一个Tuple (var1, var2, ...), 在这里就是 (numHits, _tmpRaycastHits)。
第11, 12行: 你用 myTuple.Item1, myTuple.Item2, ... 的方式来访问返回的结果。
这跟原来的老办法比起来就有很大的提升啦,代码写起来流畅了许多(找回了一点写Go的感觉)。
现在,我感觉你看到这个可能会有一个疑问,那就是:命名
返回值的名字呢?是的,这个例子里完全不存在命名这回事儿,很多人可能会因为这个原因又走回C#结构体的方式了。
但实际上,确实有办法给tuples的变量命名。
有名字的Unity C# Tuples
这就是你给tuples命名的办法:
(int numHits, RaycastHit[] raycastHits) GetEnemiesInSightNamed()
{
int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _tmpRaycastHits);
return (numHits, _tmpRaycastHits);
}
void TestTupleNamed()
{
var enemiesInSight = GetEnemiesInSightNamed();
Debug.Log($"{enemiesInSight.numHits} enemies in sight:");
foreach (var raycastHit in enemiesInSight.raycastHits)
{
Debug.Log($"I see you, {raycastHit.collider.gameObject.name}");
}
}
现在就变成了:
第1行: 你用 (type1 name1, type2 name2, ...) 来声明返回值的类型与名字。
第4行: 和之前一样,返回了 (var1, var2, ...)
第11, 12行: 你通过定义的名字访问返回值的变量。
嗯,现在就好多啦,但是不要停下来,我们还能更近一步。
下一步就是Tuple的解构!
怕你忘了,这是我们一开始的方法。
(int, RaycastHit[]) GetEnemiesInSight()
{
int numHits = Physics.RaycastNonAlloc(transform.position, transform.forward, _raycastHits);
return (numHits, _tmpRaycastHits);
}
现在,新玩法是这样的:这个方法的调用者可以有很多方式“解构”这个tuple,从而更近一步的增强代码可读性,减少冗余的代码。
解构其实只是简单的一种代替命名方式,使得方法的返回值可以更好的符合方法调用者所处的上下文环境。
听起来有点抽象?那就说几个例子吧。
(A) 第一种,调用者可以用这种方式来重命名tuple里的返回值:
(int numEnemiesInSightRenamed, RaycastHit[] tmpRaycastHitsToAggro) enemiesInSight = GetEnemiesInSight();
Debug.Log($"(A) Number of enemies in sight: {enemiesInSight.numEnemiesInSightRenamed}");
(B) 第二种, 我们不用声明tuple了,直接定义它的元素就好:
(int numEnemiesInSightRenamed, RaycastHit[] tmpRaycastHitsToAggro) = GetEnemiesInSight();
Debug.Log($"(B) Enemies in sight: {numEnemiesInSightRenamed}");
(C, D) 或者, 可以直接用个 var ,连元素的类型声明都省了:
var (C_numEnemiesInSightRenamed, C_tmpRaycastHitsToAggro) = GetEnemiesInSight();
Debug.Log($"(C) Enemies in sight: {C_numEnemiesInSightRenamed}");
(var D_numEnemiesInSightRenamed, var D_tmpRaycastHitsToAggro) = GetEnemiesInSight();
Debug.Log($"(D) Enemies in sight: {D_numEnemiesInSightRenamed}");
用tuple还有很多别的方式,这里就暂时先说这些基础的吧(对于我这种程度的。。这些方法就已经很够了)
网友评论