没有 GC 的无限方法参数
Unlimited method arguments without GC
我正在尝试制作一个可以接收无限量参数而无需装箱 GC 的函数。
我知道这可以使用 params
关键字来完成,但它会创建 GC。还了解您可以将数组传递给函数,但我想知道是否可以传递无限的方法参数 而无需创建 GC 并且无需创建数组或列表并将其传递给 List .
这是带有 param
代码的示例:
void Update()
{
GameObject player1 = GameObject.Find("Player1");
GameObject player2 = GameObject.Find("Player2");
GameObject enemy1 = GameObject.Find("Enemy1");
GameObject enemy2 = GameObject.Find("Enemy2");
GameObject enemy3 = GameObject.Find("Enemy3");
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3);
}
void moveObjects(Vector3 newPos, float duration, params GameObject[] objs)
{
for (int i = 0; i < objs.Length; i++)
{
//StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
}
}
即使注释掉了 StartCoroutine
函数,执行时也会分配 80 字节 。起初,我认为发生这种情况是因为我使用了 foreach
循环,然后我将其更改为 for
循环,但它仍在创建 GC,然后我意识到是 params GameObject[]
引起的。有关这方面的更多视觉信息,请参阅下面的探查器:
那么,如何创建一个可以接受无限参数而不生成 GC 的方法?
请忽略在Update
函数中使用的GameObject.Find
函数。这只是一个示例,用于在 运行 时间内获取我想要的对象的引用。我实现了一个脚本来处理这个问题,但与这个问题中的内容无关。
是的,可以在不引起内存分配的情况下创建具有无限参数的函数。
您可以使用未记录的 __arglist
关键字并将我们的无限 params
包装在其中。
将您的 moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3)
更改为 moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3))
。
在 moveObjects
函数中,将 params GameObject[] objs
替换为 __arglist
。将 __arglist
放入 ArgIterator
然后循环直到 ArgIterator.GetRemainingCount
不再超过 0.
要从循环中的参数中获取每个值,请使用 ArgIterator.GetNextArg
获取 TypedReference
,然后使用 TypedReference.ToObject
将 object
转换为传递的对象类型在您的示例中为 GameObject
的参数中。
整体一起变化:
void Update()
{
GameObject player1 = GameObject.Find("Player1");
GameObject player2 = GameObject.Find("Player2");
GameObject enemy1 = GameObject.Find("Enemy1");
GameObject enemy2 = GameObject.Find("Enemy2");
GameObject enemy3 = GameObject.Find("Enemy3");
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3));
}
void moveObjects(Vector3 newPos, float duration, __arglist)
{
//Put the arguments in ArgIterator
ArgIterator argIte = new ArgIterator(__arglist);
//Iterate through the arguments in ArgIterator
while (argIte.GetRemainingCount() > 0)
{
TypedReference typedReference = argIte.GetNextArg();
object tempObj = TypedReference.ToObject(typedReference);
GameObject obj = (GameObject)tempObj;
//StartCoroutine(moveToNewPos(obj.transform, newPos, duration));
}
}
虽然这应该可以解决您的问题,但值得注意的是它是一个未记录的功能,这意味着它可能有一天会停止工作。如果您关心这一点,那么应该使用数组。
编辑:
John Skeet 提到在某些平台上可能不兼容。我再次进行了测试,它适用于我测试过的所有设备。我在 Windows 和 Android 上都做了测试,它在 Windows 和 Android 上都有效。我也希望它也适用于 iOS。懒得切换到 Mac 然后 fiddle 和 Xcode 来测试,但应该没有问题。
请注意,您必须使用 .NET>=4.6 才能正常工作
这样做:
1。转到播放器设置,将 Scripting Runtime Version 更改为 "Experimental (.Net 4.6 Equivalent)"
2.将 Api 兼容级别更改为 .NET 4.6.
3。将 脚本后端 更改为 Mono 而不是 IL2CPP。不支持 IL2CPP,因为 Unity 没有在其上实现它。
如果您使用 params
并以这种方式指定参数,那么是的,它 将 始终创建一个数组对象。
如果你想避免这种情况,并且不需要担心递归或线程安全,你可以保留一个 List<GameObject>
并多次使用同一个列表。例如:
private readonly List<GameObject> objectListCache = new List<GameObject>();
private void Update()
{
cachedObjectList.Clear();
cachedObjectList.Add(GameObject.Find("Player1"));
cachedObjectList.Add(GameObject.Find("Player2"));
cachedObjectList.Add(GameObject.Find("Enemy1"));
cachedObjectList.Add(GameObject.Find("Enemy2"));
cachedObjectList.Add(GameObject.Find("Enemy3"));
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, cachedObjectList);
cachedObjectList.Clear();
}
void MoveObjects(Vector3 newPos, float duration, List<GameObject> objs)
{
foreach (GameObject obj in objs)
{
// ...
}
}
当您清除 List<T>
时会将内部缓冲区的所有元素设置为空,但不会丢弃缓冲区 - 因此它可以再次用于下一个 Update
调用没有任何分配。
答案就在你面前(Vector3)。做一个超级专业的 struct
:
public struct GameObjectParams3 // 3 only for example, you need more
{
private int NextGet;
public GameObject Object0;
public GameObject Object1;
public GameObject Object2;
public GameObjectParams3(GameObject g0) : this(g0, null, null) { }
public GameObjectParams3(GameObject g0, GameObject g1) : this(g0, g1, null) { }
public GameObjectParams3(GameObject g0, GameObject g1, GameObject g2)
{
this.NextGet = 0;
this.Object0 = g0;
this.Object1 = g1;
this.Object2 = g2;
}
public GameObject Next()
{
GameObject ret;
switch (this.NextGet)
{
case 0: ret = Object0; break;
case 1: ret = Object1; break;
case 2: ret = Object2; break;
default: return null;
}
this.NextGet++;
return ret;
}
/*
public int Length { get { return this.NextPush; } }
private int NextPush;
public void Push(GameObject go)
{
switch (this.NextPush)
{
case 0: Object0 = go; break;
case 1: Object1 = go; break;
case 2: Object2 = go; break;
default: throw new IndexOutOfRangeException();
}
this.NextPush++;
}
*/
}
并像使用 Vector3 一样使用;
void Update()
{
// ...
var objs = new GameObjectParams3(enemy1, enemy2, enemy3);
// Or:
//
// var objs = new GameObjectParams3();
// objs.Object0 = GameObject.Find("Player1");
// Better, make ObjectN private and:
//
// var objs = new GameObjectParams3();
// objs.Push( GameObject.Find("Player1") );
//
// To have .Length afterwards.
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, objs);
}
void moveObjects(Vector3 newPos, float duration, GameObjectParams3 objs)
{
while (true)
{
var obj = objs.Next();
if (obj == null)
return;
//StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
}
/*
var len = objs.Length;
for (int n = 0; n < len; n++)
{
var obj = objs.Next();
StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
}
*/
}
我正在尝试制作一个可以接收无限量参数而无需装箱 GC 的函数。
我知道这可以使用 params
关键字来完成,但它会创建 GC。还了解您可以将数组传递给函数,但我想知道是否可以传递无限的方法参数 而无需创建 GC 并且无需创建数组或列表并将其传递给 List .
这是带有 param
代码的示例:
void Update()
{
GameObject player1 = GameObject.Find("Player1");
GameObject player2 = GameObject.Find("Player2");
GameObject enemy1 = GameObject.Find("Enemy1");
GameObject enemy2 = GameObject.Find("Enemy2");
GameObject enemy3 = GameObject.Find("Enemy3");
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3);
}
void moveObjects(Vector3 newPos, float duration, params GameObject[] objs)
{
for (int i = 0; i < objs.Length; i++)
{
//StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
}
}
即使注释掉了 StartCoroutine
函数,执行时也会分配 80 字节 。起初,我认为发生这种情况是因为我使用了 foreach
循环,然后我将其更改为 for
循环,但它仍在创建 GC,然后我意识到是 params GameObject[]
引起的。有关这方面的更多视觉信息,请参阅下面的探查器:
那么,如何创建一个可以接受无限参数而不生成 GC 的方法?
请忽略在Update
函数中使用的GameObject.Find
函数。这只是一个示例,用于在 运行 时间内获取我想要的对象的引用。我实现了一个脚本来处理这个问题,但与这个问题中的内容无关。
是的,可以在不引起内存分配的情况下创建具有无限参数的函数。
您可以使用未记录的 __arglist
关键字并将我们的无限 params
包装在其中。
将您的 moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3)
更改为 moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3))
。
在 moveObjects
函数中,将 params GameObject[] objs
替换为 __arglist
。将 __arglist
放入 ArgIterator
然后循环直到 ArgIterator.GetRemainingCount
不再超过 0.
要从循环中的参数中获取每个值,请使用 ArgIterator.GetNextArg
获取 TypedReference
,然后使用 TypedReference.ToObject
将 object
转换为传递的对象类型在您的示例中为 GameObject
的参数中。
整体一起变化:
void Update()
{
GameObject player1 = GameObject.Find("Player1");
GameObject player2 = GameObject.Find("Player2");
GameObject enemy1 = GameObject.Find("Enemy1");
GameObject enemy2 = GameObject.Find("Enemy2");
GameObject enemy3 = GameObject.Find("Enemy3");
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3));
}
void moveObjects(Vector3 newPos, float duration, __arglist)
{
//Put the arguments in ArgIterator
ArgIterator argIte = new ArgIterator(__arglist);
//Iterate through the arguments in ArgIterator
while (argIte.GetRemainingCount() > 0)
{
TypedReference typedReference = argIte.GetNextArg();
object tempObj = TypedReference.ToObject(typedReference);
GameObject obj = (GameObject)tempObj;
//StartCoroutine(moveToNewPos(obj.transform, newPos, duration));
}
}
虽然这应该可以解决您的问题,但值得注意的是它是一个未记录的功能,这意味着它可能有一天会停止工作。如果您关心这一点,那么应该使用数组。
编辑:
John Skeet 提到在某些平台上可能不兼容。我再次进行了测试,它适用于我测试过的所有设备。我在 Windows 和 Android 上都做了测试,它在 Windows 和 Android 上都有效。我也希望它也适用于 iOS。懒得切换到 Mac 然后 fiddle 和 Xcode 来测试,但应该没有问题。
请注意,您必须使用 .NET>=4.6 才能正常工作
这样做:
1。转到播放器设置,将 Scripting Runtime Version 更改为 "Experimental (.Net 4.6 Equivalent)"
2.将 Api 兼容级别更改为 .NET 4.6.
3。将 脚本后端 更改为 Mono 而不是 IL2CPP。不支持 IL2CPP,因为 Unity 没有在其上实现它。
如果您使用 params
并以这种方式指定参数,那么是的,它 将 始终创建一个数组对象。
如果你想避免这种情况,并且不需要担心递归或线程安全,你可以保留一个 List<GameObject>
并多次使用同一个列表。例如:
private readonly List<GameObject> objectListCache = new List<GameObject>();
private void Update()
{
cachedObjectList.Clear();
cachedObjectList.Add(GameObject.Find("Player1"));
cachedObjectList.Add(GameObject.Find("Player2"));
cachedObjectList.Add(GameObject.Find("Enemy1"));
cachedObjectList.Add(GameObject.Find("Enemy2"));
cachedObjectList.Add(GameObject.Find("Enemy3"));
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, cachedObjectList);
cachedObjectList.Clear();
}
void MoveObjects(Vector3 newPos, float duration, List<GameObject> objs)
{
foreach (GameObject obj in objs)
{
// ...
}
}
当您清除 List<T>
时会将内部缓冲区的所有元素设置为空,但不会丢弃缓冲区 - 因此它可以再次用于下一个 Update
调用没有任何分配。
答案就在你面前(Vector3)。做一个超级专业的 struct
:
public struct GameObjectParams3 // 3 only for example, you need more
{
private int NextGet;
public GameObject Object0;
public GameObject Object1;
public GameObject Object2;
public GameObjectParams3(GameObject g0) : this(g0, null, null) { }
public GameObjectParams3(GameObject g0, GameObject g1) : this(g0, g1, null) { }
public GameObjectParams3(GameObject g0, GameObject g1, GameObject g2)
{
this.NextGet = 0;
this.Object0 = g0;
this.Object1 = g1;
this.Object2 = g2;
}
public GameObject Next()
{
GameObject ret;
switch (this.NextGet)
{
case 0: ret = Object0; break;
case 1: ret = Object1; break;
case 2: ret = Object2; break;
default: return null;
}
this.NextGet++;
return ret;
}
/*
public int Length { get { return this.NextPush; } }
private int NextPush;
public void Push(GameObject go)
{
switch (this.NextPush)
{
case 0: Object0 = go; break;
case 1: Object1 = go; break;
case 2: Object2 = go; break;
default: throw new IndexOutOfRangeException();
}
this.NextPush++;
}
*/
}
并像使用 Vector3 一样使用;
void Update()
{
// ...
var objs = new GameObjectParams3(enemy1, enemy2, enemy3);
// Or:
//
// var objs = new GameObjectParams3();
// objs.Object0 = GameObject.Find("Player1");
// Better, make ObjectN private and:
//
// var objs = new GameObjectParams3();
// objs.Push( GameObject.Find("Player1") );
//
// To have .Length afterwards.
Vector3 newPos = new Vector3(0, 0, 0);
moveObjects(newPos, 3f, objs);
}
void moveObjects(Vector3 newPos, float duration, GameObjectParams3 objs)
{
while (true)
{
var obj = objs.Next();
if (obj == null)
return;
//StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
}
/*
var len = objs.Length;
for (int n = 0; n < len; n++)
{
var obj = objs.Next();
StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
}
*/
}