是否有可能获得 IEnumerator来自 T[]?

发布于 2024-08-07 16:37:11 字数 1472 浏览 2 评论 0原文

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

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

对于初学者来说,让类实现 ICollection 是有意义的。该接口的一些成员非常容易实现;例如,Count 返回 this.Values.Count

但是实现 ICollection 需要我实现 IEnumerable 以及 IEnumerable (非泛型),这有点棘手用于线程安全的集合。

当然,我总是可以在 IEnumerable.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)。

我的一个想法似乎可行,就是在调用之前将 T[] 返回值从 getValues 转换为 IEnumerable GetEnumerator 就可以了。另一种方法是更改​​ getValues 以首先返回 IEnumerable,然后返回非泛型 IEnumerable.GetEnumerator只需将返回值从 getValues 转换为非泛型 IEnumerable 即可。但我无法真正决定这些方法是否让人感到草率或完全可以接受。

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

Let's say I want to create a collection class that is thread-safe by default.

Internally, the class has a protected List<T> property called Values.

For starters, it makes sense to have the class implement ICollection<T>. Some of this interface's members are quite easy to implement; for example, Count returns this.Values.Count.

But implementing ICollection<T> requires me to implement IEnumerable<T> as well as IEnumerable (non-generic), which is a bit tricky for a thread-safe collection.

Of course, I could always throw a NotSupportedException on IEnumerable<T>.GetEnumerator and IEnumerable.GetEnumerator, but that feels like a cop-out to me.

I already have a thread-safe getValues function which locks on Values and returns a copy in the form of a T[] array. So my idea was to implement GetEnumerator by returning this.getValues().GetEnumerator() so that the following code would actually be thread-safe:

ThreadSafeCollection coll = new ThreadSafeCollection ();

// add some items to coll

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

Unfortunately this implementation only seems to work for IEnumerable.GetEnumerator, not the generic version (and so the above code throws an InvalidCastException).

One idea I had, which seemed to work, was to cast the T[] return value from getValues to an IEnumerable<T> before calling GetEnumerator on it. An alternative would be to change getValues to return an IEnumerable<T> in the first place, but then for the non-generic IEnumerable.GetEnumerator simply cast the return value from getValues to a non-generic IEnumerable. But I can't really decide if these approaches feel sloppy or perfectly acceptable.

In any case, does anyone have a better idea of how to go about doing this? I have heard of the .Synchronized methods, but they seem to only be available for non-generic collections in the System.Collections namespace. Maybe there is a generic variant of this that already exists in .NET that I simply don't know about?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

巨坚强 2024-08-14 16:37:11

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

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。只要这是真的,你应该没问题。

Most collections specify that you cannot add or remove things from the collection while iterating. It follows that for a thread-safe collection, you want to lock out other threads from modifying the collection while any thread is iterating. This should be easy to do with the iterators syntax, and doesn't require you to make a copy:

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 assumes that all of the other operations that might modify the collection also lock this.Values. As long as that's true, you should be fine.

手心的温暖 2024-08-14 16:37:11

不幸的是,这个实现似乎只适用于 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;
}

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

Unfortunately this implementation only seems to work for IEnumerable.GetEnumerator, not the generic version (and so the above code throws an InvalidCastException).

Seems strange for me. Have you implemented non-generic IEnumerable explicitly? I.e. have you written

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

Also, have you tried to implement IEnumerable with iterators syntax. It should be easy:

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

If you've tried, why doesn't it suits your needs?

羁绊已千年 2024-08-14 16:37:11

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

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

但是,当 .NET 2.0 中引入泛型时,进行了一种特殊的“黑客”操作,让 T[] 实现 IList 及其基本接口(其中< code>IEnumerable是一)。出于这个原因,我认为使用它是完全合理的:

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

The type T[] has a special realtionship to the type System.Array from which it derives. Arrays like T[] were made before generics were introduced in .NET (otherwise the syntax could have been Array<T>).

The type System.Array has one GetEnumerator() instance method, public. This method returns non-generic IEnumerator (since .NET 1). And System.Array has no explicit interface implementations for GetEnumerator(). This explains your observations.

But when generics were introduced in .NET 2.0, a special "hack" was made to have a T[] implement IList<T> and its base interfaces (of which IEnumerable<T> is one). For this reason I think it's perfectly reasonable to use:

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

In the version of .NET where I'm checking this, the following classes exist:

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>
    {
    }
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文