没有 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.ToObjectobject 转换为传递的对象类型在您的示例中为 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));
        }
        */
    }