2009-10-15 59 views
1

比方说,我想创建一个默认情况下线程安全的集合类。是否有可能从T []获得IEnumerator <T>?

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

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

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

当然,我总是可以在IEnumerable<T>.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之前,从getValuesT[]返回值的IEnumerable<T>。另一种方法是将getValues更改为首先返回IEnumerable<T>,但对于非通用IEnumerable.GetEnumerator只是将返回值从getValues转换为非泛型IEnumerable。但我不能真正决定这些方法是否草率或完全可以接受。

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

+0

你可以只使用SynchronizedCollection ...对? – 2009-10-15 14:46:15

+0

不幸的是,目前我们在这里遇到了.NET 2.0和VS 2005。看起来SynchronizedCollection在.NET 3.0中变得可用。 – 2009-10-15 14:54:13

回答

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

+0

我必须承认在这些答案之前没有特别意识到“yield”关键字......我曾看过它,但从未真正理解它的目的。这很有帮助;谢谢! – 2009-10-15 15:06:34

+0

这显然是有问题的,因为如果统计员没有立即得出结论,锁可以继续存在。见Jon Skeet在这里的最后一个回复:http://www.eggheadcafe.com/software/aspnet/33035743/c-20-iterators-and-lock.aspx – AaronSieb 2009-10-15 15:16:01

+0

我认为这里锁的行为正是所期望的:直到迭代完成或枚举器处理完毕,锁才会被释放。 (特别是,您不希望在对MoveNext()的调用之间释放锁,因为这意味着集合可能会在迭代过程中发生更改)。应用程序编写者应该意识到这一点并相应地进行编程。 – 2009-10-15 15:31:03

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

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

+0

因此,您似乎主张对我提出的第二个提案进行细微的修改 - 将“IEnumerable .GetEnumerator”的结果投射到“IEnumerable.GetEnumerator”的“IEnumerable”。我一定会尝试。否则,我认为JS Bangs提出了很好的观点,即只要我锁定内部集合属性,就不需要将值复制到数组中。 – 2009-10-15 15:09:12

+0

是的,在这里施放看起来很简单,因为生成的IEnumerator 实现也是IEnumerator。 IIRC,编译器甚至不需要在此处进行明确的转换。 关于复制与锁定:这取决于您的集合使用情况。如果集合很小(我认为<100000个元素),复制将减少跨线程读取期间的锁定争用。如果预计会更大,最好锁定它。 YMMV,当然。 如果您决定锁定集合,请考虑使用ReaderWriterLockSlim而不是锁定,因为它应该稍微减少锁定争用。 – 2009-10-15 15:37:16

0

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

该类型System.Array有一个GetEnumerator()实例方法,public。此方法返回非通用的IEnumerator(自.NET 1起)。 System.Array没有明确的接口实现GetEnumerator()。这解释了你的观察。

但是,当被引入在泛型.NET 2.0,一个特殊的“黑客”被制成具有T[]实施IList<T>及其基接口(其中IEnumerable<T>是其中之一)。出于这个原因,我认为这是完全合理的使用方法:

((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> 
    { 
    } 
    } 
} 
相关问题