为修改所述集合的集合中的每个元素调用方法的安全方法?

时间:2011-12-26 21:11:59

标签: c# hashset

我有一个像这样定义的类(显示相关方法):

class ShaderProgram : IDisposable
{
    private HashSet<Shader> _shaders = new HashSet<Shader>();

    public void AttachShader(Shader shader)
    {
        GL.AttachShader(Handle, shader.Handle);
        _shaders.Add(shader);
    }

    public void DetachShader(Shader shader)
    {
        GL.DetachShader(Handle, shader.Handle);
        _shaders.Remove(shader);
    }
}

现在我想弄清楚如何编写Dispose方法,我想我可以这样做:

    public void Dispose()
    {
        foreach(var shader in _shaders)
            DetachShader(shader);
        GL.DeleteProgram(Handle);
    }

但现在我怀疑这是不安全的,因为我正在迭代一个集合并同时从它中删除...如果它是一个队列或堆栈Id只是说“虽然不是空的,弹出一个并删除它” ,但因为它是一个HashSet ......我不确定最好的方法是什么。我该怎么办呢?

编辑:重点是避免代码重复;我想为每个元素调用ShaderProgram.DetachShader。我知道我可以重复该函数内的代码,然后在最后清除整个集合 - 这不是我想要做的。


现在发生在我身上,我根本不需要清空HashSet,是吗?无论如何,ShaderProgram对象即将被销毁,我需要做的就是清理所有非托管资源,而C#GC可以清理其余的资源,对吗?

    public void Dispose()
    {
        foreach (var shader in _shaders)
            GL.DetachShader(Handle, shader.Handle);
        GL.DeleteProgram(Handle);
    }

那就是说,我仍然想要回答原来的问题。我的目标不是解决一个非常简单的问题,而是要学习解决这类问题的最佳方法。

4 个答案:

答案 0 :(得分:2)

一些LINQ怎么样:

while (_shaders.Any()) {
    DetachShader(_shaders.First());
}

但这通常不起作用,因为你最终可能会多次在同一个元素上调用该函数。只有函数从底层集合中删除元素才有意义。

在一般情况下,函数不修改集合,您可以使用以下内容:

while (hashset.Any()) {
    var item = hashset.First();
    Process(item);

    hashset.Remove(item);
}

或者这个:

foreach (var item in hashset) {
    Process(item);
}

hashset.Clear();

更新:正如@SLaks在评论中所指出的,为哈希集中的每个元素调用First(),因为它被清空是O(n ^ 2)。这对于微小的收藏来说并不重要,但如果你有大量的收藏品,它可能会让世界变得不同。

答案 1 :(得分:2)

这样做没有问题,但也许您应该考虑迭代您的设置,将您的着色器从GL变量中分离,然后清除您的HashSet。如果您不想复制自己的分离方法中的代码,则应将其拆分并从这两个地方调用。

答案 2 :(得分:1)

我不明白为什么有17k声望的人会问这个问题。我错过了什么吗?

public void Dispose()
{
    foreach(var shader in _shaders)
        GL.DetachShader( Handle, shader.Handle );
    _shaders.Clear();
}

答案 3 :(得分:1)

如果您的收藏非常小,我会使用:

foreach (var item in set.ToArray())
     set.Remove(item);

另一种解决方案是RemoveWhere()方法:

set.RemoveWhere(item =>
{
     Console.WriteLine(item);
     return true;
});