在作用域末尾执行清理的结构是否是良好的C#模式?

时间:2018-07-12 00:16:27

标签: c# unity3d struct raii

RAII非常适合确保您不会失败的调用。通常,我会使用 class 实现。我目前正在使用Unity,并且意识到在Update(甚至在编辑器脚本中)中会产生垃圾。我当时在想,为Begin / End操作创建 struct 包装器是避免分配的好模式吗? (自value types don't allocate on the heap起。)

这是一个编辑器脚本示例:

public struct ActionOnDispose : IDisposable
{
    Action m_OnDispose;
    public ActionOnDispose(Action on_dispose)
    {
        m_OnDispose = on_dispose;
    }

    public void Dispose()
    {
        m_OnDispose();
    }
}

EditorGUILayout是一种Unity类型,它公开了需要在代码前后调用的几个函数。我会像这样使用ActionOnDispose:

public static ActionOnDispose ScrollViewScope(ref Vector2 scroll)
{
    scroll = EditorGUILayout.BeginScrollView(scroll);
    return new ActionOnDispose(EditorGUILayout.EndScrollView);
}

private Vector2 scroll;
public void OnGUI() // called every frame
{
    using (EditorHelper.ScrollViewScope(ref scroll))
    {
        for (int i = 0; i < 1000; ++i)
        {
            GUILayout.Label("Item #"+ i);
        }
    }
}

上面的简单示例按预期工作,并且对ScrollViewScope的每次调用都调用一次Dispose,因此它看起来是正确的。 Unity甚至提供了一个有范围的结构:EditorGUI.DisabledScope,但在许多情况下不是这样。所以我想知道这种结构是否有缺点? (我假设如果以某种方式复制了该结构,那么将丢弃旧副本,并且我的结束操作被调用了两次?我看不到这种情况,但是我对C#值类型不是很熟悉。)

编辑:我要特别询问是否使用结构(值类型)是否重要。 Is abusing IDisposable to benefit from “using” statements considered harmful讨论了IDisposable是否很好用。

1 个答案:

答案 0 :(得分:0)

Jeroen Mostert 的评论作为答案:

<块引用>

赋值不会产生垃圾,写新的 ActionOnDispose(EditorGUILayout.EndScrollView) 会。 (是的,这也会在水下分配一个新的 Action,这只是句法简写。)一般来说,如果您使用委托,很难避免分配,但这样做通常也没有回报。生命周期较短的对象在 gen 0 中收集,非常高效。我没有受过教育的猜测是,像 EditorGUILayout.BeginScrollView 这样的方法会自己分配东西,以至于在您的一端再分配一次就没有太大关系了。

<块引用>

回答你的另一个问题,不,复制结构不会导致方法被调用两次。做到这一点的唯一方法是实际将其包装在两个不同的 Dispose 范围内。 C#没有RAII;超出范围的事情什么都不做。真正神奇的是 using 语句。

我的结论:

我可以为我的每个用例制作 ActionOnDispose 的版本,以避免使用产生垃圾的 Action。