没有GC的无限方法参数

时间:2018-04-21 07:15:20

标签: c# unity3d memory-management variadic-functions

我正在尝试创建一个可以接收无限量参数而无需创建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[]正在导致它。请参阅下面的Profiler,了解更多关于此的可视信息:

enter image description here

那么,如何在不生成GC的情况下创建一个无限制参数的方法呢?

请忽略使用GameObject.Find函数中使用的Update函数。这只是用于获取运行时期间所需对象的引用的示例。我已经实现了一个脚本来处理这个问题,但没有解决这个问题中的内容。

3 个答案:

答案 0 :(得分:8)

是的,可以创建一个具有无限参数的函数,而不会导致内存分配。

您可以使用未记录的**name department fee_paid id** ---------------------------------------- Name1 Name1 10000 1 Name2 Name2 20000 2 ---------------------------------------- 关键字并在其中包含无限__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不再超过 0

要从循环中的参数获取每个值,请使用ArgIterator.GetRemainingCount获取ArgIterator.GetNextArg,然后使用TypedReferenceTypedReference.ToObject强制转换为传递给参数在您的示例中为object

整体变化:

GameObject

虽然这可以解决您的问题,但值得注意的是,这是一个未记录的功能,这意味着它可能有一天会停止工作。如果你关心它,那么应该使用一个数组。

编辑:

John Skeet提到某些平台可能存在不兼容问题。我再次进行了测试,它适用于我测试过的所有设备。我在Windows和Android上都进行过测试,它可以在Windows和Android上运行。我也希望它也适用于iOS。太懒了切换到Mac然后摆弄Xcode来测试但是应该没有问题。

请注意,您必须使用.NET&gt; = 4.6才能实现此功能

要做到这一点

1 。转到播放器设置,将脚本运行时版本更改为“Experimental(.Net 4.6 Equivalent)”

2 。将Api兼容级别更改为 .NET 4.6

3 。将 Scripting Backend 更改为Mono而不是IL2CPP。不支持IL2CPP,因为Unity没有在其上实现此功能。

答案 1 :(得分:8)

如果你使用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调用分配

答案 2 :(得分:0)

答案就在你面前(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));
        }
        */
    }