【问题标题】:Is it possible to get an IEnumerator<T> from a T[]?是否可以从 T[] 获取 IEnumerator<T>?
【发布时间】:2010-12-07 01:32:05
【问题描述】:

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

在内部,该类有一个名为 Values 的受保护的 List&lt;T&gt; 属性。

对于初学者来说,让类实现ICollection&lt;T&gt; 是有意义的。这个接口的一些成员很容易实现;例如,Count 返回this.Values.Count

但是实现ICollection&lt;T&gt; 需要我实现IEnumerable&lt;T&gt;IEnumerable(非泛型),这对于线程安全集合来说有点棘手。

当然,我总是可以在 IEnumerable&lt;T&gt;.GetEnumeratorIEnumerable.GetEnumerator 上扔一个 NotSupportedException,但这对我来说就像是逃避现实。

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

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

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

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

我的一个想法似乎可行,就是在调用GetEnumerator 之前将T[] 返回值从getValues 转换为IEnumerable&lt;T&gt;。另一种方法是首先将getValues 更改为返回IEnumerable&lt;T&gt;,然后对于非泛型IEnumerable.GetEnumerator,只需将返回值从getValues 转换为非泛型IEnumerable。但我无法确定这些方法是否草率或完全可以接受。

无论如何,有没有人对如何做这件事有更好的想法?我听说过.Synchronized 方法,但它们似乎只适用于System.Collections 命名空间中的非泛型集合。也许.NET中已经存在一个我根本不知道的通用变体?

【问题讨论】:

  • 你可以使用 SynchronizedCollection... 对吧?
  • 不幸的是,我们暂时被 .NET 2.0 和 VS 2005 困住了。看起来 SynchronizedCollection 在 .NET 3.0 中可用。

标签: .net collections thread-safety ienumerable ienumerator


【解决方案1】:

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

对我来说似乎很奇怪。您是否明确实现了非泛型 IEnumerable? IE。你写了吗

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;
}

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

【讨论】:

  • 因此,您似乎主张对我提出的第二个建议稍作改动——将IEnumerable&lt;T&gt;.GetEnumerator 的结果转换为IEnumerable 以获取IEnumerable.GetEnumerator。我一定会尝试的。否则,我认为 JS Bangs 提出了一个很好的观点,即只要我锁定了内部集合属性,就不需要将值复制到数组中。
  • 是的,在这里转换对我来说似乎很简单,因为生成的 IEnumerator 实现也将是 IEnumerator。 IIRC,编译器甚至不需要在这里显式转换。关于复制与锁定:这取决于您的收藏使用情况。如果集合很小(我认为
【解决方案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。只要那是真的,你应该没问题。

【讨论】:

  • 我必须承认在这些答案之前没有特别了解yield 关键字......我见过它,但从未真正理解它的目的。这很有帮助;谢谢!
  • 这显然是有问题的,因为如果枚举器没有立即得出结论,锁可能会一直存在。请在此处查看 Jon Skeet 的最后回复:eggheadcafe.com/software/aspnet/33035743/…
  • 我认为这里的锁行为正是我们所期望的:在迭代完成或枚举数被释放之前,锁不会被释放。 (特别是,您不希望在调用 MoveNext() 之间释放锁,因为这意味着集合可能会在迭代期间发生变化。)应用程序编写者应该意识到这一点并相应地编程。
【解决方案3】:

类型T[] 与它派生的类型System.Array 有特殊关系。像T[] 这样的数组是在.NET 中引入泛型之前创建的(否则语法可能是Array&lt;T&gt;)。

System.Array 类型有一个GetEnumerator() 实例方法public。此方法返回非泛型 IEnumerator(从 .NET 1 开始)。而System.Array 没有针对GetEnumerator() 的显式接口实现。这解释了您的观察结果。

但是当在 .NET 2.0 中引入泛型时,一个特殊的“hack”被制作为具有 T[] 实现 IList&lt;T&gt; 及其基本接口(@98​​7654334@ 是其中之一)。出于这个原因,我认为使用它是完全合理的:

((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>
    {
    }
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-24
    • 1970-01-01
    • 1970-01-01
    • 2020-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-15
    相关资源
    最近更新 更多