Неограниченные аргументы метода без GC

Я пытаюсь сделать функцию,которая может получать неограниченное количество аргументов без создания GC.

Я знаю, что это можно сделать с помощью params ключевое слово, но оно создает GC. Также поймите, что вы можете передать массив функции, но я хочу знать, можно ли передать неограниченные аргументы метода без создания GC и без создания массива или списка и передачи его в список.

это пример с 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[] вызывает его. См. профилировщик ниже для получения дополнительной визуальной информации об этом:

enter image description here

Итак, как я могу создать метод, который принимает неограниченное аргументы без получения ГК?

Пожалуйста, игнорируйте использование . Это просто используется для примера, чтобы получить ссылку на объекты, которые я хочу во время выполнения. У меня есть скрипт, реализованный для обработки этого, но не связанный с тем, что в этом вопросе.

3 ответов


да, можно создать функцию с неограниченными аргументами, не вызывая выделения памяти.

вы можете сделать это с помощью недокументированных __arglist ключевое слово и упаковка наш неограниченный params внутри него.

изменить moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3) to moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3)).

на


если вы используете 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> это установит все элементы внутреннего буфера в null, но не отбросит буфер-поэтому его можно использовать снова для следующий 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));
        }
        */
    }