是否可以从T []?</t>获取IEnumerator <t>

时间:2009-10-15 14:34:26

标签: .net collections thread-safety ienumerable ienumerator

假设我想创建一个默认情况下是线程安全的集合类。

在内部,该类具有名为List<T>的受保护Values属性。

对于初学者来说,让类实现ICollection<T>是有意义的。这个界面的一些成员很容易实现;例如,Count会返回this.Values.Count

但实现ICollection<T>要求我实现IEnumerable<T>以及IEnumerable(非泛型),这对于线程安全的集合来说有点棘手。

当然,我总是可以在NotSupportedExceptionIEnumerable<T>.GetEnumerator上抛出IEnumerable.GetEnumerator,但这对我来说就像是一个警察。

我已经有一个线程安全的getValues函数,它锁定Values并以T[]数组的形式返回一个副本。所以我的想法是通过返回GetEnumerator来实现this.getValues().GetEnumerator(),以便下面的代码实际上是线程安全的:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

foreach (T value in coll) {
    // do something with value
}

不幸的是,这个实现似乎只适用于IEnumerable.GetEnumerator,而不是通用版本(因此上面的代码会引发InvalidCastException)。

我的一个想法似乎有用,就是在T[]上将getValues返回值转换为IEnumerable<T>,然后再调用GetEnumerator。另一种方法是首先更改getValues以返回IEnumerable<T>,但是对于非通用IEnumerable.GetEnumerator,只需将返回值从getValues转换为非IEnumerable -generic .Synchronized。但我无法确定这些方法是否感到草率或完全可以接受。

无论如何,有没有人更好地了解如何做到这一点?我听说过System.Collections方法,但它们似乎只适用于{{1}}命名空间中的非泛型集合。也许.NET中已经存在一个我不知道的泛型变体?

3 个答案:

答案 0 :(得分:2)

大多数集合指定在迭代时不能添加或删除集合中的内容。因此,对于线程安全的集合,您希望锁定其他线程,以便在任何线程迭代时修改集合。这应该很容易使用迭代器语法,并且不需要您复制:

public IEnumerator<T> GetEnumerator()
{
    lock (this.Values) // or an internal mutex you use for synchronization
    {
        foreach (T val in this.Values)
        {
            yield return val;
        }
    }
    yield break;
}

这假定可能修改集合的所有其他操作也会锁定this.Values。只要这是真的,你应该没事。

答案 1 :(得分:1)

  

不幸的是,这个实现似乎只适用于IEnumerable.GetEnumerator,而不是通用版本(因此上面的代码会引发InvalidCastException)。

对我来说似乎很奇怪。您是否明确实现了非通用的IEnumerable?即你写过吗

public IEnumerator<T> GetEnumerator() { ...}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator<T>(); }

此外,您是否尝试使用迭代器语法实现IEnumerable。应该很容易:

public IEnumerator<T> GetEnumerator()
{
    T[] values;
    lock(this.Values)
        values = this.Values.ToArray();
    foreach(var value in values)
        yield return value;
}

如果您尝试过,为什么它不适合您的需求?

答案 2 :(得分:0)

类型T[]对其派生的类型System.Array有特殊的影响。像T[]之类的数组是在.NET中引入泛型之前制作的(否则语法可能是Array<T>)。

类型System.Array有一个GetEnumerator()实例方法public。此方法返回非泛型IEnumerator(自.NET 1起)。并且System.Array没有GetEnumerator()的显式接口实现。这解释了你的观察结果。

但是,当在.NET 2.0中引入泛型时,会产生一个特殊的“hack”,即T[]实现IList<T>及其基接口(其中IEnumerable<T>为1)。出于这个原因,我认为使用它是完全合理的:

((IList<T>)(this.getValues())).GetEnumerator()

在我正在检查它的.NET版本中,存在以下类:

namespace System
{
  public abstract class Array
  {
    private sealed class SZArrayEnumerator  // instance of this is returned with standard GetEnumerator() on a T[]
    {
    }
  }

  internal sealed class SZArrayHelper
  {
    private sealed class SZGenericArrayEnumerator<T>  // instance of this is returned with generic GetEnumerator() on a T[] which has been cast to IList<T>
    {
    }
  }
}