假设我想创建一个默认情况下是线程安全的集合类。
在内部,该类具有名为List<T>
的受保护Values
属性。
对于初学者来说,让类实现ICollection<T>
是有意义的。这个界面的一些成员很容易实现;例如,Count
会返回this.Values.Count
。
但实现ICollection<T>
要求我实现IEnumerable<T>
以及IEnumerable
(非泛型),这对于线程安全的集合来说有点棘手。
当然,我总是可以在NotSupportedException
和IEnumerable<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中已经存在一个我不知道的泛型变体?
答案 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>
{
}
}
}